Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SCM API

Available since v1.4.0, opt-in per provider — see docs/internals/scm-api-proxy.md.

Extends fogwall past git push into the rest of the contribution lifecycle: proxying gh’s issue/PR create-edit-comment-review traffic and glab’s issue/MR equivalent, reusing fogwall’s existing identity resolution, permission engine, and audit trail. See the administrator guide for the operational model (BYO-token, egress assumption) and the user guide for how a developer points gh/glab at it.

The providers use different wire formats under the hood — GitHub’s is GraphQL with an opaque node ID that must be resolved to owner/repo; GitLab’s and Forgejo’s are REST with the repository addressed directly in the URL, so no resolution step exists for them (node-id-cache-ttl below is a GitHub-only setting, inert for the others). See docs/internals/scm-api-proxy.md for the per-provider wire-format detail. Every dialect shares the same opt-in config shape — a global settings block, and a per-provider enabled flag that actually mounts the path:

scm-api:
  # TTL for GitHub's GraphQL node-ID -> owner/repo resolution cache (ISO-8601 duration). A SECURITY parameter, not
  # just a perf knob: a GraphQL node ID can outlive a repo rename/transfer while what it resolves to changes
  # underneath it. Conservative default; shorten further for a deployment where repo renames/transfers are unusually
  # frequent. Has no effect on GitLab, which addresses its target directly in the URL.
  node-id-cache-ttl: PT5M

providers:
  github:
    enabled: true
    scm-api:
      enabled: true # per-provider opt-in (default false)
      port: 9443 # required when enabled — a dedicated listener, see below
  gitlab:
    enabled: true
    scm-api:
      enabled: true
      port: 9444
  gitea:
    enabled: true
    scm-api:
      enabled: true
      port: 9445
      require-validated-head: false # relax the head-provenance check; default true
      merge-enabled: true # allow merging PR/MRs through this provider; default false

Each enabled provider needs its own port, and fogwall fails to start if one is enabled without it. The dialect is mounted at the root of that listener (/api/graphql, /api/v4/*, /api/v1/*) because that is the only place the CLIs can reach it: gh and fj address the API from the host root and silently discard any path prefix. A single shared listener isn’t an option either — every GitLab claims /api/v4 and every Gitea/Forgejo /api/v1, so two instances of the same platform would collide. Clients are then pointed at a plain host and port (GH_HOST, GITLAB_HOST, tea login add --url, fj -H). See docs/internals/scm-api-proxy.md for the per-CLI evidence.

TLS is inherited from server.tls; there is no per-provider TLS block. When server.tls is set, every SCM API listener serves HTTPS with the same certificate, on its own port. The CLIs only ever address a custom host over HTTPS, so TLS has to terminate either at fogwall this way or at an ingress in front of it — with server.tls unset, fogwall logs a warning naming each plaintext listener. See TLS on the SCM API listeners.

The caller’s User-Agent and the CLI version it advertises are recorded on every audit record — the anchor for noticing a CLI upgrade has changed its wire format, which otherwise surfaces only as an unexplained denial. It is not used to gate: User-Agent is caller-controlled, so nothing branches on it.

require-validated-head refuses a pull/merge request create whose head commit fogwall has no push record for — closing the gap where a contributor pushes straight to their fork, never touching fogwall, then opens the pull request through it. The lookup is keyed on the commit SHA alone, not the repository, since a fork push and the upstream pull/merge request are two different repositories.

It defaults on, so a pull/merge request’s head must trace to a push fogwall saw. Relax it (set false) where these workflows are common: a rebase, amend, or force-push after pushing through fogwall changes the SHA a validated push recorded, and a commit authored in the SCM’s own web UI never went through fogwall at all — each leaves the head with no matching push record. A denial names the remedy — push the branch through fogwall, then reopen — rather than just refusing.

merge-enabled allows merging a pull/merge request through this provider’s SCM API proxy (gh pr merge, glab mr merge, tea/fj pr merge). It defaults off because merge is the highest-consequence operation on this path, so exposing it is an explicit operator decision rather than a side effect of enabling the SCM API proxy. It is independent of the MERGE grant, and both are required: the capability must be enabled here, and the caller must hold the grant (see Permissions). With it off, a merge request is refused even for a caller who holds MERGE.

Per-repo authorization for mutations (issue/PR create, edit, comment, review) goes through the existing RepoPermission grants — see Permissions below — with a dedicated PROPOSE grant kept independent from PUSH/REVIEW, so an operator can permission git-push and SCM API mutations separately. Merging a pull/merge request is its own grant, MERGE, independent again from PROPOSE — see the administrator guide’s merging section:

permissions:
  - username: alice
    provider: github
    match:
      value: /acme/widgets
    grant: PROPOSE

Reads (GraphQL query traffic — an ordinary gh issue list, for example) are not gated by PROPOSE. They are forwarded for any authenticated caller, which keeps the default read cost near pass-through — no allowlist, no node-ID resolution, no extra round-trip.

SCM API properties

PropertyTypeDefaultDescription
scm-api.node-id-cache-ttlstringPT5MISO-8601 duration. See the security note above.
providers.<name>.scm-api.enabledbooleanfalseWhether the SCM API proxy is mounted for this provider.
providers.<name>.scm-api.portintDedicated listener port. Required when enabled; startup fails without it.
providers.<name>.scm-api.require-validated-headbooleantrueRefuse a pull/merge request whose head commit has no fogwall push record. Relax where rebase/amend/force-push/web-UI commits are common.
providers.<name>.scm-api.merge-enabledbooleanfalseAllow merging PR/MRs through this provider. Independent of the MERGE grant; both are required.

Token model

The CLI carries a personal access token the user supplies. fogwall forwards it upstream unchanged after inspecting the request, and never mints or supplies a credential for this path.

SCM OAuth is a separate mechanism, for fogwall-managed operations rather than external tooling. It does not provide a token for a CLI. See the administrator guide for the egress assumption this relies on.