forgejo: authorized integrations accept any issuer domain, on an open-registration instance #313

Stängd
öppnade 2026-08-01 15:00:27 +00:00 av supernaut · 0 kommentarer
Ägare

Found while evaluating Authorized Integrations for #80. Independent of whether we adopt that feature —
it is reachable today.

The finding

[authorized_integration] is not configured in roles/forgejo/templates/app.ini.j2 at all, so every
value is the upstream default. Forgejo's own v16.0.1 example config says what that means for
ALLOWED_DOMAINS:

;; Allowed domains for authorized integrations. Default is blank which means all domains will be allowed
;; (except local networks, see ALLOW_LOCALNETWORKS).
;ALLOWED_DOMAINS =

And creating an integration is not an admin-only action, despite the admin CLI being the documented
path. In v16.0.1 routers/web/web.go the routes live under user settings:

m.Group("/authorized-integrations", func() {
    m.Group("/{ui}", func() {
        m.Combo("/new").
            Get(user_setting.NewAuthorizedIntegration).
            Post(user_setting.NewAuthorizedIntegrationPost)

Why it matters here specifically

Registration is open (ADR 0029). So any account anyone creates can add an authorized integration
naming an arbitrary HTTPS issuer, and Forgejo will fetch that issuer's discovery document and JWKS. That
turns a signed-in stranger into someone who can make our server issue outbound HTTPS requests to a host
of their choosing.

ALLOW_LOCALNETWORKS blocks the worst version of this — it is not a route to our internal network. What
remains is outbound request generation on demand, attributable to our IP.

Suggested

Set ALLOWED_DOMAINS to the issuers we actually intend to trust. If Authorized Integrations is adopted at
all, the only issuer in the #80 evaluation's GO column is our own Actions issuer, so a single-entry
allowlist covers it and denies everything else — least privilege, and cheap.

Verify before applying: it was not established whether the local Actions issuer is itself subject to
ALLOWED_DOMAINS or exempt from it. If it is subject, it must be listed or the two GO consumers in #80
break. Check that on the local/ 16.0.1 preview rather than on production — the change restarts Forgejo,
so it is not worth two attempts.

Also worth setting REQUEST_TIMEOUT deliberately rather than inheriting 10s, and reviewing the blocklist
knob, while the section is being added.

Note if #80 is ever adopted

ID tokens, unlike Actions secrets, are available to fork pull-request runs. So any claim rule must pin
event_name=push plus an exact ref. A rule matching pull_request on a public repository would let a
fork's workflow authenticate as our bot.

Found while evaluating Authorized Integrations for #80. **Independent of whether we adopt that feature** — it is reachable today. ## The finding `[authorized_integration]` is not configured in `roles/forgejo/templates/app.ini.j2` at all, so every value is the upstream default. Forgejo's own v16.0.1 example config says what that means for `ALLOWED_DOMAINS`: ```ini ;; Allowed domains for authorized integrations. Default is blank which means all domains will be allowed ;; (except local networks, see ALLOW_LOCALNETWORKS). ;ALLOWED_DOMAINS = ``` And creating an integration is **not** an admin-only action, despite the admin CLI being the documented path. In v16.0.1 `routers/web/web.go` the routes live under user settings: ```go m.Group("/authorized-integrations", func() { m.Group("/{ui}", func() { m.Combo("/new"). Get(user_setting.NewAuthorizedIntegration). Post(user_setting.NewAuthorizedIntegrationPost) ``` ## Why it matters here specifically **Registration is open** (ADR 0029). So any account anyone creates can add an authorized integration naming an arbitrary HTTPS issuer, and Forgejo will fetch that issuer's discovery document and JWKS. That turns a signed-in stranger into someone who can make our server issue outbound HTTPS requests to a host of their choosing. `ALLOW_LOCALNETWORKS` blocks the worst version of this — it is not a route to our internal network. What remains is outbound request generation on demand, attributable to our IP. ## Suggested Set `ALLOWED_DOMAINS` to the issuers we actually intend to trust. If Authorized Integrations is adopted at all, the only issuer in the #80 evaluation's GO column is our **own** Actions issuer, so a single-entry allowlist covers it and denies everything else — least privilege, and cheap. **Verify before applying:** it was not established whether the local Actions issuer is itself subject to `ALLOWED_DOMAINS` or exempt from it. If it is subject, it must be listed or the two GO consumers in #80 break. Check that on the `local/` 16.0.1 preview rather than on production — the change restarts Forgejo, so it is not worth two attempts. Also worth setting `REQUEST_TIMEOUT` deliberately rather than inheriting 10s, and reviewing the blocklist knob, while the section is being added. ## Note if #80 is ever adopted ID tokens, unlike Actions secrets, **are** available to fork pull-request runs. So any claim rule must pin `event_name=push` plus an exact ref. A rule matching `pull_request` on a public repository would let a fork's workflow authenticate as our bot.
Logga in för att delta i denna konversation.
Ingen milstolpe
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-infra#313
Ingen beskrivning angiven.