fix: various security fixes (#38406)

Addresses a batch of privately reported security issues, grouped by
area:

- **SSRF** - migration PR-patch/asset fetches, OAuth2 avatar & OpenID
discovery, pull-mirror URL re-validation, and the outbound proxy path.
- **Access-token scope** - prevent scope escalation on token creation;
keep public-only tokens confined (feeds, packages, Actions listings,
star/watch lists, limited/private owners).
- **Access control / disclosure** - go-get default-branch leak, webhook
authorization-header leak, watch clearing on private transitions,
label/attachment scoping.
- **Denial of service** - input bounds for npm dist-tags, Debian control
files, Arch file lists, and SSH keys.

### 📌 Attention for site admins

Not breaking - existing configs keep working - but two changes are worth
a look:

- **New SSRF protection** Outbound requests (migrations, OAuth2 avatars,
OpenID discovery, pull mirrors, proxy path) are now validated against
the allow/block host lists. If your instance legitimately reaches
internal hosts, you may need to add them to
`[security].ALLOWED_HOST_LIST` (and the relevant `ALLOW_LOCALNETWORKS`
settings).
- **Deprecation** `[webhook].ALLOWED_HOST_LIST` is deprecated and will
be removed in a future release. Use `[security].ALLOWED_HOST_LIST`
instead; the old key still works for now.

---------

Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
This commit is contained in:
bircni
2026-07-12 17:14:09 +00:00
committed by GitHub
co-authored by TheFox0x7 techknowlogick Lunny Xiao wxiaoguang Zettat123
parent d2bd1589fe
commit f69e15afe7
93 changed files with 1714 additions and 137 deletions
+3 -2
View File
@@ -49,9 +49,10 @@ type APIContext struct {
}
// TokenCanAccessRepo reports whether the current API token is allowed to access the repository.
// A public-only token cannot reach a private repo; any other token is unrestricted by this check.
// A public-only token cannot reach a private repo or a repo owned by a non-public (limited or
// private) owner; any other token is unrestricted by this check.
func (ctx *APIContext) TokenCanAccessRepo(repo *repo_model.Repository) bool {
return repo == nil || !ctx.PublicOnly || !repo.IsPrivate
return !ctx.PublicOnly || !publicOnlyTokenDeniedRepo(ctx, repo)
}
func init() {
+35 -1
View File
@@ -4,6 +4,7 @@
package context
import (
"context"
"net/http"
"slices"
@@ -12,6 +13,39 @@ import (
"gitea.dev/models/unit"
)
// isOwnerHidden reports whether repo's owner is not publicly visible (a limited or private owner), so
// the owner's repositories must be hidden from callers that may only reach genuinely public resources.
func isOwnerHidden(ctx context.Context, repo *repo_model.Repository) bool {
if err := repo.LoadOwner(ctx); err != nil || repo.Owner == nil {
return true // fail closed if the owner visibility can't be determined
}
return !repo.Owner.Visibility.IsPublic()
}
// publicOnlyTokenDeniedRepo reports whether a public-only API token must be denied access to
// repo. A public-only token may only reach genuinely public resources, so it is denied for
// private repos and for repos owned by a non-public (limited or private) owner.
func publicOnlyTokenDeniedRepo(ctx context.Context, repo *repo_model.Repository) bool {
if repo == nil {
return false
}
return repo.IsPrivate || isOwnerHidden(ctx, repo)
}
// TokenIsPublicOnly reports whether the request is authenticated by a public-only API token. A
// non-token request, or a token with no recorded scope, is not public-only.
func TokenIsPublicOnly(ctx *Context) bool {
if ctx.Data["IsApiToken"] != true {
return false
}
scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
if !ok {
return false
}
publicOnly, _ := scope.PublicOnly()
return publicOnly
}
// CheckTokenScopes checks whether the authenticated API token contains any of the given scopes.
func CheckTokenScopes(ctx *Context, repo *repo_model.Repository, scopes ...auth_model.AccessTokenScope) {
if ctx.Data["IsApiToken"] != true {
@@ -29,7 +63,7 @@ func CheckTokenScopes(ctx *Context, repo *repo_model.Repository, scopes ...auth_
return
}
if publicOnly && repo != nil && repo.IsPrivate {
if publicOnly && publicOnlyTokenDeniedRepo(ctx, repo) {
ctx.HTTPError(http.StatusForbidden)
return
}