fix(pr-artifacts): read GitLab uploads through the token-readable API - #1228
fix(pr-artifacts): read GitLab uploads through the token-readable API#1228yuensunn wants to merge 1 commit into
Conversation
GitLab serves `/uploads/<secret>/<file>` from a Rails web route that only honors session cookies. A `PRIVATE-TOKEN` request is redirected to the sign-in page, so every GitLab upload attachment referenced by an MR was unreadable — and because the sign-in page is HTML served with HTTP 200, it was rendered as the artifact instead of the file. Route upload links through `GET /projects/:id/uploads/:secret/:filename`, which serves the same bytes for a personal access token. Both the bare `/uploads/...` and project-scoped `/<path>/uploads/...` forms are rewritten. Also stop following redirects into a provider sign-in path: fail with 401 and the matching `gh`/`glab auth login` hint so an auth gap is legible instead of surfacing as a corrupt artifact. The uploads API answers every file with `application/octet-stream`, and the content route serves provider types with `nosniff`, so refine an opaque type from the file extension to keep images and video rendering. Verified end-to-end against a self-hosted GitLab 18.8.0-ee instance.
508f00f to
a05a185
Compare
Before / after in the artifact paneReproduced against an MR whose description links a Before — the artifact pane renders the login page's HTML source instead of the file. The pane shows a After — Also confirmed at the API layer on a separate MR — |


Problem
GitLab serves
/uploads/<secret>/<file>from a Rails web route that only honors session cookies. ThePRIVATE-TOKENheader thatpr-artifact-document.tsalready resolves viaglab config get tokenis not accepted there, so every GitLab upload attachment referenced by an MR is unreadable.Worse, the failure is silent. The sign-in hop answers HTTP 200 with an HTML login page, which then gets rendered as the artifact in the artifact pane.
Measured against a self-hosted GitLab 18.8.0-ee instance with a valid token (
/api/v4/user→ 200):Changes
Route GitLab uploads through the uploads REST API.
gitlabUploadApiUrl()rewrites both the bare/uploads/...and project-scoped/<projectPath>/uploads/...forms toGET /projects/:id/uploads/:secret/:filename, wired into the existingproviderContentUrl()alongside the/-/blob/→/-/raw/rewrite. The allowlist and credential plumbing are unchanged — they were already correct.Stop following redirects into a provider sign-in page. Landing on
/users/sign_in,/users/auth/...,/login,/session, or/oauth/authorizenow throwsPRArtifactDocumentError(401)with the matchinggh auth login/glab auth login --hostname <host>hint. An auth gap becomes legible instead of surfacing as a corrupt artifact. This is worth having independently of change 1.Refine the opaque content type. The uploads API answers every file with
application/octet-stream, and/api/pr-artifact-contentserves the provider type withnosniff— without this, images and video would stop rendering. Falls back to an extension lookup only when the provider type isapplication/octet-stream.Verification
bun test packages/shared/— 761 pass, 0 fail (4 new tests)tsc --noEmit -p packages/shared/tsconfig.json— cleanbun test— 2789 pass / 25 fail; unmodifiedmainproduces the identical 25 failures (Pi-extension and AI-runtime suites, unrelated to this change)contentType: text/markdownKnown consideration
GET /projects/:id/uploads/:secret/:filenamewas added in GitLab 17.4. On older self-hosted instances it 404s, which surfaces as a 502 — where today those uploads fail anyway (redirect to sign-in). Happy to add a version gate or a fallback to the web route if you'd prefer explicit handling.Note on CI
The
Testjob failure on this branch isedit-mode Discard repaints the pristine diff (DOM) > pristine content is visible ... promptly after cancelEdit. It reproduces identically on upstreammain(run 31138933804) and is unrelated to this change.