Skip to content
Security

Security

Threat model

  • Forgejo stays private. Forgejo only makes outgoing webhook calls to forgemirror. forgemirror connects outward to Forgejo (clone) and to the targets (push). Nothing on the internet needs access to Forgejo.
  • forgemirror is not an internet service. Expose /webhook only to Forgejo’s network. The webhook secret authenticates requests, but there is no reason to make the endpoint publicly reachable.
  • Targets get everything. The mirror copies all branches and tags. Use filters to keep repositories with sensitive history from being mirrored at all.

Webhook authentication

Every request must carry an HMAC-SHA256 signature of its body, made with webhook.secret. forgemirror checks the signature before it parses the body. Signatures are compared in constant time, and request bodies are capped at webhook.max_body_bytes. Use a long random secret:

openssl rand -hex 32

Credentials

Tokens are needed for cloning (source.token) and pushing (targets[].token). They are handled so that they cannot leak through the usual channels:

ChannelProtection
Process list (ps, /proc)Tokens are never put in git’s arguments or URLs
DiskThe clone’s .git/config never contains credentials; clones are deleted after each run
Logs and error messagesTokens are redacted from git output and never logged
Configuration dumpsThe secret type renders as [REDACTED] in any formatted output
Other hostsA credential is scoped to its exact remote URL, so it is never sent to another host, including on redirects

Technically, each git command receives its credential as a URL-scoped http.<url>.extraHeader through GIT_CONFIG_COUNT/GIT_CONFIG_KEY_0/ GIT_CONFIG_VALUE_0 environment variables. git runs isolated from the host’s configuration (GIT_CONFIG_NOSYSTEM=1, GIT_CONFIG_GLOBAL=/dev/null, inherited GIT_* variables removed) and never prompts (GIT_TERMINAL_PROMPT=0).

Use tokens with the narrowest scope:

  • Forgejo: read:repository.
  • GitHub: a fine-grained token limited to the mirror repositories, with Contents: read and write.

Store tokens as files (*_file) from a secret manager rather than inline.

Runtime hardening

The container image and both Kubernetes deployments run with:

  • non-root UID 65532 (runAsNonRoot)
  • a read-only root filesystem, with clones on an emptyDir that has a size limit
  • all Linux capabilities dropped and allowPrivilegeEscalation: false
  • the RuntimeDefault seccomp profile
  • no service account token mounted
  • the namespace enforcing the restricted Pod Security Standard (kustomize)

Supply chain

Each release publishes:

  • binaries and checksums.txt, signed with cosign (Sigstore bundle)
  • SPDX SBOMs for archives and the container image
  • a multi-arch image signed with cosign
  • the Helm chart as an OCI artifact signed with cosign

Signatures are recorded in the public Rekor transparency log. Verify them with the public key cosign.pub from the repository:

cosign verify-blob --key cosign.pub \
  --bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txt

cosign verify --key cosign.pub git.bitfoo.de/dvonessen/forgemirror:<version>
cosign verify --key cosign.pub git.bitfoo.de/dvonessen/charts/forgemirror:<version>

Every change goes through govulncheck, the race detector and golangci-lint (the maratori “golden” config) before it can be merged. Renovate keeps dependencies and tools current.

Reporting a vulnerability

Please report vulnerabilities privately to the maintainer rather than in a public issue.

Last updated on