Fix artifacts content-type
Ref: https://gitlab.com/gitlab-org/gitlab/-/issues/214707
Description
This MR lets workhorse set the content-type when serving artifacts.
It uses http.DetectContentType and default to text/plain
when necessary.
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.
API | endpoints |
---|---|
Internal REST | ArtifactsController#raw |
External REST | :id/jobs/artifacts/:ref_name/raw/*artifact_path |
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
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.