Set content-type as plain/text for raw artifact endpoint
Ref: https://gitlab.com/gitlab-org/gitlab/-/issues/214707
What does this MR do and why?
Following up !83515 (merged) & !83917 (merged)
This MR is a second attempt to set let workhorse setting content-type
as text/plain
only for our raw endpoint when serving artifacts.
Let workhorse set content-type | endpoints |
---|---|
:id/jobs/:job_id/artifacts/*artifact_path | |
:id/jobs/artifacts/:ref_name/raw/*artifact_path |
It lets workhorse sets the response header itself as decided with the team (this is the same technique used for git blobs) which default content type to text/plain.
Why are we doing this?
When fetching artifact using ArtifactsController#raw method they are served with their "real" MIME type in the Content-Type
headers. This allows an attacker to host a malicious JavaScript payload as an artifact and bypass our CSP.
Testing locally
- Create a job which generate an artifact (in this example I've used a javascript file)
- Here a snippet of the artifact I've created in my
.gitlatb-ci.yml
test-workhorse-header:
script:
- echo "alert('hello!');" > hello.js
artifacts:
paths:
- hello.js
- Verify the headers of the response with curl
$ curl --head -i 127.0.0.1:3000/root/<PROJECT_ID>/-/jobs/<JOB_ID>/artifacts/raw/hello.js
- Verify the headers for our public API response with curl remains the same
$ curl --head -i 127.0.0.1:3000/api/v4/projects/<PROJECT_ID>/jobs/<JOB_ID>/artifacts/hello.js
Screenshots
Before this MR
Content-Type application/javascript
➜ gitlab git:(master) ✗ curl --head -i http://127.0.0.1:3000/root/test-workhorse-header/-/jobs/769/artifacts/raw/hello.js
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Disposition: attachment; filename="hello.js"
Content-Length: 17
Content-Security-Policy: base-uri 'self'; child-src https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com https://www.googletagmanager.com/ns.html http://127.0.0.1:3000/rails/letter_opener/ http://127.0.0.1:3000/admin/ http://127.0.0.1:3000/assets/ http://127.0.0.1:3000/-/speedscope/index.html http://127.0.0.1:3000/-/sandbox/mermaid http://127.0.0.1:3000/assets/ blob: data:; connect-src 'self' http://127.0.0.1:3808 ws://127.0.0.1:3808 ws://127.0.0.1:3000; default-src 'self'; font-src 'self'; form-action 'self' https: http:; frame-ancestors 'self'; frame-src https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com https://www.googletagmanager.com/ns.html http://127.0.0.1:3000/rails/letter_opener/ http://127.0.0.1:3000/admin/ http://127.0.0.1:3000/assets/ http://127.0.0.1:3000/-/speedscope/index.html http://127.0.0.1:3000/-/sandbox/mermaid; img-src 'self' data: blob: http: https:; manifest-src 'self'; media-src 'self'; object-src 'none'; script-src 'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com 'nonce-V95XI2Lo/57qhXOoL9VXLg=='; style-src 'self' 'unsafe-inline'; worker-src http://127.0.0.1:3000/assets/ blob: data:
- Content-Type: application/javascript
Permissions-Policy: interest-cohort=()
Pragma: no-cache
Referrer-Policy: strict-origin-when-cross-origin
Set-Cookie: perf_bar_enabled=true; path=/
X-Accel-Buffering: no
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-Request-Id: 01FY9FF1M0VS8BD3Q6T5PYW8W2
X-Runtime: 0.388220
X-Ua-Compatible: IE=edge
X-Xss-Protection: 1; mode=block
Date: Wed, 16 Mar 2022 13:42:50 GMT
After this MR
Content-Type text/plain
➜ gitlab git:(master) ✗ curl --head -i http://127.0.0.1:3000/root/test-workhorse-header/-/jobs/769/artifacts/raw/hello.js
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Disposition: attachment; filename="hello.js"
Content-Length: 17
Content-Security-Policy: base-uri 'self'; child-src https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com https://www.googletagmanager.com/ns.html http://127.0.0.1:3000/rails/letter_opener/ http://127.0.0.1:3000/admin/ http://127.0.0.1:3000/assets/ http://127.0.0.1:3000/-/speedscope/index.html http://127.0.0.1:3000/-/sandbox/mermaid http://127.0.0.1:3000/assets/ blob: data:; connect-src 'self' http://127.0.0.1:3808 ws://127.0.0.1:3808 ws://127.0.0.1:3000; default-src 'self'; font-src 'self'; form-action 'self' https: http:; frame-ancestors 'self'; frame-src https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com https://www.googletagmanager.com/ns.html http://127.0.0.1:3000/rails/letter_opener/ http://127.0.0.1:3000/admin/ http://127.0.0.1:3000/assets/ http://127.0.0.1:3000/-/speedscope/index.html http://127.0.0.1:3000/-/sandbox/mermaid; img-src 'self' data: blob: http: https:; manifest-src 'self'; media-src 'self'; object-src 'none'; script-src 'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com 'nonce-jbH65kov3LsrnoNS0blojQ=='; style-src 'self' 'unsafe-inline'; worker-src http://127.0.0.1:3000/assets/ blob: data:
+ Content-Type: text/plain; charset=utf-8
Permissions-Policy: interest-cohort=()
Pragma: no-cache
Referrer-Policy: strict-origin-when-cross-origin
Server: thin
Set-Cookie: perf_bar_enabled=true; path=/
X-Accel-Buffering: no
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-Request-Id: 01FY9FJ7SVW03Q1GVZ7NGBSQ5W
X-Runtime: 6.694900
X-Ua-Compatible: IE=edge
X-Xss-Protection: 1; mode=block
Date: Wed, 16 Mar 2022 13:44:41 GMT
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.