[v14.0/forgejo]: fix: remove second challenge from WWW-Authenticate header (#11625)

**Backport**: #11616

https://codeberg.org/forgejo/forgejo/pulls/11393 introduced a second challenge, one for HTTP Basic Authentication, to the existing `WWW-Authenticate` header sent by Forgejo's container registry in response to missing or invalid credentials. However, that led to unexpected compatibility issues with some clients. For example, it broke Renovate (see https://github.com/renovatebot/renovate/discussions/41774).

To be extra-safe, the decision was taken to revert that particular change without introducing a second header field (i.e., sending two `WWW-Authenticate` headers). That effectively restores the old behaviour.

```
$ curl -v -u andreas --basic http://192.168.178.62:3000/v2
Enter host password for user 'andreas':
*   Trying 192.168.178.62:3000...
* Connected to 192.168.178.62 (192.168.178.62) port 3000
* using HTTP/1.x
* Server auth using Basic with user 'andreas'
> GET /v2 HTTP/1.1
> Host: 192.168.178.62:3000
> Authorization: Basic *****
> User-Agent: curl/8.15.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 401 Unauthorized
< Content-Length: 50
< Content-Type: application/json
< Docker-Distribution-Api-Version: registry/2.0
< Www-Authenticate: Bearer realm="http://192.168.178.62:3000/v2/token",service="container_registry",scope="*"
< Date: Tue, 10 Mar 2026 17:00:21 GMT
<
{"errors":[{"code":"UNAUTHORIZED","message":""}]}
```

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Tests for JavaScript changes

(can be removed for Go changes)

- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

(cherry picked from commit f1a08a7ab1)

Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11625
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
Mathieu Fenniak 2026-03-11 04:00:09 +01:00
parent 021d8b198f
commit c7b4e90106
3 changed files with 5 additions and 6 deletions

View file

@ -126,8 +126,9 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
}
func APIUnauthorizedError(ctx *context.Context) {
ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token",service="container_registry",scope="*",`+
`Basic realm="`+setting.AppURL+`v2",service="container_registry",scope="*"`)
// Do not include more than one challenge in the same header field. That breaks clients even though the HTTP RFC
// allows it.
ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token",service="container_registry",scope="*"`)
apiErrorDefined(ctx, errUnauthorized)
}

View file

@ -63,8 +63,7 @@ func TestPackageContainerCleanupSHA256(t *testing.T) {
Token string `json:"token"`
}
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*",` +
`Basic realm="` + setting.AppURL + `v2",service="container_registry",scope="*"`}
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`}
t.Run("User", func(t *testing.T) {
req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL))

View file

@ -86,8 +86,7 @@ func TestPackageContainer(t *testing.T) {
Token string `json:"token"`
}
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*",` +
`Basic realm="` + setting.AppURL + `v2",service="container_registry",scope="*"`}
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`}
t.Run("Anonymous", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()