Keep CI job token in LFS batch action headers

What does this MR do and why?

Keeps the CI job token in the Authorization header of LFS batch action responses, instead of swapping it for a user-scoped LFS token, when the batch request itself was authenticated with a job token.

Resolves #630868 (closed)

Reviewable LOC: 2 (plus 45 in tests)

Repositories::LfsApiController#lfs_auth_header currently issues a fresh user-scoped Gitlab::LfsToken for every batch action, even when the batch was authenticated with a CI job token. The follow-up object/upload request then gets checked against download_code/push_code (the user's abilities) instead of build_download_code/build_push_code (the abilities the batch itself was authorized with). For actors whose permissions are build-only, such as the GitLab Security Policy Bot, the batch succeeds and every object download 404s.

The fix: when the batch's authentication_result.type == :build, lfs_auth_header returns nil and the existing fallback echoes request.headers['Authorization'] (the job token) back unchanged. The object request is then authorized with the same job token and the same build_* abilities as the batch.

Side effect for all CI jobs using LFS, not just the bot: they no longer get a 2-hour user token carrying the user's download_code/push_code on the project back in the batch response. They get their own job token back instead, scoped the same as the job.

Question for reviewers (Create: Source Code, LFS owners): is there any client that relies on the renewed user token outliving the job (for example an LFS transfer continuing after the job finishes)? We looked and found none.

References

None beyond the linked issue above.

Screenshots or screen recordings

Not applicable: no UI change.

How to set up and validate locally

  1. Enable LFS, create a private project, track a file with LFS.
  2. Enforce a scan execution policy with a rule that runs a policy pipeline as the Security Policy Bot (for example a schedule rule) and a SAST job.
  3. On master: get_sources in the policy pipeline job logs LFS 404s. On this branch: checkout completes.

Alternatively, run the new EE request spec, which exercises this directly: bundle exec rspec ee/spec/requests/lfs_http_spec.rb.

Full suite run for this change: spec/requests/lfs_http_spec.rb + ee/spec/requests/lfs_http_spec.rb, 358 examples, 0 failures. RuboCop clean.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Owed elsewhere

  • Backport to affected versions: deferred, follow-up issue to be created (waiting on Create's review of this approach first).
  • Docs troubleshooting entry for the affected versions: follow-up issue to be created.
Context for LLM agents

Rejected: adding download_code to the bot's YAML allowlist. One line, but it widens the bot's least-privilege model from "a job running as the bot can read code" to "the bot identity can read code" on every project with an enforced policy, and leaves the underlying job-token-to-user-token swap in place for any other build-only identity that hits the same path.

Rejected: documenting the 404 as expected behavior. Docs say the bot can read the repository's content from a CI/CD job. The server contradicts that: the batch says yes, the object download says 404.

Invariant this fix restores: object/upload actions returned by an LFS batch must be authorized by the same credential type that authorized the batch itself. Swapping credential types between the batch and its follow-up requests is what let a build-only actor pass the batch and fail every action it returned.

Merge request reports

Loading
Loading