The Exploit
Attacker needs a repository access token that is valid for repo read operations but does not have the specific download/archive scope.
curl -i -H "Authorization: token REPO_READ_ONLY_TOKEN" \
"https://TARGET/user/repo/archive/refs/heads/main.zip?path=README.md"
The server responds with HTTP/1.1 200 OK and Content-Type: application/zip, delivering a valid archive even though the token is missing download-specific authorization. On a patched instance, the same request is rejected before archive creation.
What the Patch Did
Before:
func Download(ctx *context.Context) {
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*"), ctx.FormStrings("path"))
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
...
func InitiateDownload(ctx *context.Context) {
paths := ctx.FormStrings("path")
if setting.Repository.StreamArchives || len(paths) > 0 {
ctx.JSON(http.StatusOK, map[string]any{
After:
func Download(ctx *context.Context) {
if !checkDownloadTokenScope(ctx) {
return
}
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*"), ctx.FormStrings("path"))
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
...
func InitiateDownload(ctx *context.Context) {
if !checkDownloadTokenScope(ctx) {
return
}
paths := ctx.FormStrings("path")
if setting.Repository.StreamArchives || len(paths) > 0 {
ctx.JSON(http.StatusOK, map[string]any{
The patch added an explicit download-scope authorization check by calling checkDownloadTokenScope(ctx) at the start of both archive download entry points.
Root Cause
This is a missing authorization bug (CWE-862/CWE-285): the repository archive download endpoints accepted requests from a token-authenticated context without verifying that the token included the required download scope. User-controlled input enters as the auth token plus the path parameter and archive route suffix (ctx.PathParam("*")), then reaches archive generation through archiver_service.NewRequest(...) without crossing a scope check. The trust boundary violated is token scope verification for sensitive repository downloads.
Why It Works
The load-bearing change is the added guard if !checkDownloadTokenScope(ctx) { return } in both Download and InitiateDownload. Without that check, the handlers proceed to archive creation regardless of whether the token is authorized for download. The duplicate guard is needed because both handlers are separate entry points for archive delivery: one starts the download flow, the other performs the actual archive request. The patch places the check early so the sensitive operation never reaches archiver_service.NewRequest(...) when token scope is insufficient.
Hardening Checklist
- Enforce scope-specific authorization before any sensitive action, e.g. call
checkDownloadTokenScope(ctx)before archive creation. - Validate token scopes in every handler that exposes a protected resource, not just in shared middleware.
- Keep authorization checks separate from business logic so missing a handler does not bypass them.
- Add regression tests using tokens with
read_repositorybut without download/archive scope to verify access is denied. - Use explicit deny-return patterns (
if !authorized { return }) at the top of request handlers instead of relying on later request flow.
References
- https://nvd.nist.gov/vuln/detail/CVE-2026-27771