From 6f02a6642c236e239c1464eceb48b73d2f9171f1 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Fri, 13 Mar 2026 14:44:56 +0100 Subject: [PATCH] Always serve repository file entries as application/octet-stream --- app/controllers/repositories_controller.rb | 10 +++++----- spec/controllers/repositories_controller_spec.rb | 12 ++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index dc003420aa9..d05ade3edd2 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -446,11 +446,11 @@ class RepositoriesController < ApplicationController end def send_raw(content, path) - # Force the download - send_opt = { filename: filename_for_content_disposition(path.split("/").last) } - send_type = OpenProject::MimeType.of(path) - send_opt[:type] = send_type.to_s if send_type - send_data content, send_opt + # Force the download as binary to prevent CSP bypass + send_data content, + filename: filename_for_content_disposition(path.split("/").last), + type: "application/octet-stream", + disposition: :attachment end def render_text_entry diff --git a/spec/controllers/repositories_controller_spec.rb b/spec/controllers/repositories_controller_spec.rb index 2b6111d848c..0eca25e3672 100644 --- a/spec/controllers/repositories_controller_spec.rb +++ b/spec/controllers/repositories_controller_spec.rb @@ -291,6 +291,18 @@ RSpec.describe RepositoriesController do end end + describe "#send_raw" do + let(:permissions) { [:browse_repository] } + + it "serves raw files as application/octet-stream attachment" do + get :entry, params: { project_id: project.identifier, repo_path: "subversion_test/textfile.txt", format: "raw" } + + expect(response).to be_successful + expect(response.headers["Content-Type"]).to eq("application/octet-stream") + expect(response.headers["Content-Disposition"]).to match(/attachment/) + end + end + describe "checkout path" do render_views