fix: add OAUTH_REDIRECT_ALLOWLIST for client registration and waive the open default (SEC-05)

This commit is contained in:
Latte committed 2026-10-14 17:24:41 +02:00
1 parent 79aa4f7fc0
commit 8be859243d
7 files changed
+283 -11

No files matched your search

+6 -3
View File
@@ -60,7 +60,7 @@ Spoofing
| Calling the endpoint without credentials | Non-loopback bind refuses to start without a bearer or OAuth; SDK `RequireAuthMiddleware` answers 401 | `transports.py` (`http_startup_errors`, `build_http_app`) |
| Guessing the bearer | 32 character minimum; constant-time comparison of SHA-256 digests; per-IP and per-token rate limit before auth | `config.py`, `auth.py` (`StaticBearerVerifier`), `rate_limit.py` |
| Using a token minted for another resource | RFC 8707 resource binding and required scope `gitea` enforced by the SDK; `/authorize` with a foreign `resource` gets `invalid_target` | `transports.py` (`_auth_settings`), `oauth/provider.py` |
| Confused deputy through the shared Gitea OAuth app | Own consent page naming client and redirect URI, CSRF double submit with `SameSite=Strict` cookie, browser-binding cookie on the callback | `oauth/callback.py`, `oauth/provider.py` (residual: SEC-05) |
| Confused deputy through the shared Gitea OAuth app | Own consent page naming client and redirect URI, CSRF double submit with `SameSite=Strict` cookie, browser-binding cookie on the callback | `oauth/callback.py`, `oauth/provider.py` (residual: W-10, optional `OAUTH_REDIRECT_ALLOWLIST`) |
| Code or refresh token replay | Single-use codes; replay or reuse of a rotated refresh token revokes the grant | `oauth/provider.py` |
Tampering
@@ -127,8 +127,10 @@ Elevation of privilege
Gitea token committed to a file) is returned unless it matches a pattern.
- SSRF through Gitea features (migrations, webhooks, mirrors) is Gitea's responsibility; this
server only ever contacts `GITEA_URL`.
- The consent page is the only barrier against phishing with a self-registered client
(SEC-05).
- With the default `OAUTH_REDIRECT_ALLOWLIST` (empty), the consent page is the only barrier
against phishing with a self-registered client (waiver W-10). For a public deployment set the
allowlist to the hosted clients you use, for example
`OAUTH_REDIRECT_ALLOWLIST=https://claude.ai/,https://chat.mistral.ai/`.
- Key rotation for `TOKEN_ENCRYPTION_KEY` is not supported: a new key means moving the store
away and signing every user out.
- The audit chain is tamper-evident, not tamper-proof: anyone with write access to the file can
@@ -233,3 +235,4 @@ changeset.
| W-07 | Credential-issuing operations bypass pattern masking | `gitea/executor.py` (`CREDENTIAL_ISSUING_OPERATIONS`) | Their purpose is to return a new credential; exact redaction of the caller's own tokens still applies. |
| W-08 | Unauthenticated `/health` and `/` report version and tool count, `/health` reports Gitea reachability | `transports.py` | Needed by container health checks and clients; reveals no configuration or secret. The Gitea probe is cached for 10 seconds. |
| W-09 | Pattern masking keeps the first and last 4 characters of a matched secret | `security.py` (`mask_secret`) | Applies only to pattern matches, which are at least 16 characters; the remainder keeps enough entropy, and the context helps users recognise the value. |
| W-10 | Any https redirect URI can be registered when `OAUTH_REDIRECT_ALLOWLIST` is empty (SEC-05) | `oauth/provider.py` (`redirect_uri_problem`), `config.py` | Owner decision: the default stays open so any MCP client can register without configuration. The mitigation is the consent page (client name, redirect host and URI, explicit approval, CSRF and browser binding) and ADR 0004. The optional allowlist closes the gap: exact URIs or `https://host/` prefixes, checked at registration and at `/authorize`; recommended for every public deployment. |