railiance-forge/docs/receive-secret-detection.md
tegwick ea65606c12
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Batch small receive inputs within the unchanged scan budget
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a14bfc-9643-7872-aa12-98326dcf25e7
2026-10-18 13:25:08 +02:00

130 lines
7.8 KiB
Markdown

# Forge receive-path secret detection
FORGE-WP-0006 implements the forge-owned part of RMGR-WP-0021-T03 and
Custodian CUST-WP-0080 G07. The local Repo Manager scanner remains separate.
The source adapter is not evidence of installed server enforcement.
## Boundary and ownership
The forge-owned, standalone Linux adapter composes with Forgejo's generated
pre-receive dispatcher. It uses pinned Gitleaks 8.30.1 Linux amd64
(SHA256 `88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509`).
It requires `/usr/bin/git`, `/proc`, a private writable tmpfs at `/dev/shm`,
and Linux pidfd support (5.3+ on amd64/arm64) but no Python runtime, issuer credential,
network access, source checkout, or repository-controlled workflow. The current
Forgejo 11.0.3 image has no Python interpreter; this keeps the runtime change to
two reviewed executables and a hook fragment, subject to native admission.
The forge owns tool admission, hook installation and recovery. Repo Manager
coordinates repository-control coverage; enablement owns reusable CI templates;
source repositories own their workflows. Credential classification and
revocation remain with existing incident/custody owners.
Do not overwrite generated `hooks/pre-receive` or `pre-receive.d/gitea`, or enable
arbitrary user Git-hook editing. A reviewed additional executable in the existing
`pre-receive.d/` dispatcher is the proposed integration. Server writers, frontend
merge/edit/import paths, SSH and HTTP pushes, hook resynchronization, pod restart
and new repository creation all need separate native acceptance. No admission
or production coverage is inferred from disposable bare-repository tests.
## What is checked
Input is the exact `old-object new-object ref-name` batch from receive-pack.
Malformed, duplicate, oversized, inconsistent or unsupported updates refuse.
All updated ref names are scanned without echoing them. Ref exclusions are frozen
from the pre-push snapshot; the traversal scans complete introduced reachable
objects across every new tip and every parent, including annotated tags and
direct object tags. A secret added and removed before the tip is still examined.
Blob data, raw commit/tag contents and tree names are scanned. Symlink contents
are read as Git blobs; their filesystem targets are never followed. Changed trees
containing external gitlinks refuse because the referenced repository is not
covered. Git replacement objects and grafts cannot hide history. Partial/shallow
repositories, missing objects and unverified traversal refuse. Receive-pack's
quarantine object locations are preserved only for Git reads; the adapter never
updates refs or copies objects into the primary store.
The scanner executable is opened without following a final symlink, checked for
ownership/mode/regular-file shape and exact SHA256, and executed through its open
file descriptor. Scanner config and ignore files are private temporary files;
repository configuration, inline allow comments and ambient scanner settings do
not disable detection. Child diagnostics/findings are discarded. Output contains
only a fixed status/reason, counts, version and, for an object detection, its ID.
Privately review the identified object; never paste its contents into an issue,
State Hub, CI logs or chat. Standard Git output may independently display ref
names; the adapter does not make Git's transport diagnostics a secret-safe log.
Exit 0 is `clean` in this declared scope; exit 1 is `blocked`; exit 2 is
`unverified`. Both nonzero statuses must refuse the complete push. There is no
implicit baseline/ignore or override switch. A false-positive exception needs a
separate reviewed mechanism with exact scope, approval and expiry.
## Bounds and remaining coverage
Current bounds: 256 updates, 10,000 existing refs, 2,000 introduced objects,
8 MiB per object, 64 MiB total, 512 KiB update input and 90 seconds for the check.
Scanner calls also use the scanner's 15-second timeout. Object sizes and total
bytes are checked before scanning payloads. Limits reject rather
than skip. Large imports need an admitted path; do not disable the gate to pass
an unreviewed import. Git/scanner versions and limits require native load testing.
FORGE-WP-0010 corrects per-small-input scanner startup overhead without changing
these limits. It reuses Repo Manager's bounded profile: at most 64 inputs of at
most 4096 bytes per directory invocation. Larger inputs retain the existing stdin
reader and fragment behavior. Raw inputs use 0700 private tmpfs directories and
0600 files with safe object/ref identifiers; no source path controls a filename.
Missing or nonvolatile storage refuses instead of falling back to ordinary disk.
A separately supervised child pins its receiver with pidfd, acknowledges readiness
before payload writes, and cleans up on pipe EOF. A 180-second backstop terminates
the exact stalled receiver before cleanup. Git/scanner children die when their
creating receiver thread dies; it is pinned to its OS thread during each child.
Normal cleanup and failed cleanup are checked. Directory identity replacement
refuses deletion. This is not protection against a hostile same-UID host process.
Directory scans use the same pinned scanner/default config and explicit empty
ignore file. Bounded reports contain only known safe payload identifiers. Missing,
malformed, oversized or inconsistent reports, unknown diagnostics and scanner or
watchdog failures refuse. Only the exact pinned scanner's positive-finding summary
is accepted as a warning. The original stdin route is otherwise unchanged.
The adapter does not rescan objects already reachable before the push, scan
unreachable extra pack objects, LFS payloads, other repositories, runtime logs,
artifacts or credentials outside Git. It is not an authorization boundary against
a forge administrator or hostile same-UID process. Default scanner rules remain
heuristic. Historical discovery and finding-to-response are independent work;
removing a source value or rejecting a push does not revoke an exposed credential.
CUST-WP-0073-T05's founder-deferred rotation is unchanged.
The 165 observed on-disk repository directories are a discovery count, not an
admitted production-software denominator. The 22 reviewed repositories provide a
named rollout starting scope; the owner must reconcile all production repositories,
wikis, archives, mirrors and new-repository lifecycle before claiming DSM coverage.
## Build and disposable acceptance
Use an admitted Go toolchain. The original deployed revision used Go 1.26.9,
verified against the SHA256 from go.dev release metadata. The FORGE-WP-0010
source candidate is separately tested with installed Go 1.26.7; its new source,
compiler and exact executable still require native admission. No third-party Go
modules are required. Keep binaries and logs outside the repository:
```sh
cd tools/receive-secret-scan
FORGE_TEST_GITLEAKS=/reviewed/gitleaks go test -count=1 -v ./...
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -buildvcs=false -o /tmp/receive-secret-scan .
```
The root `make test-secret-detection` target requires `FORGE_TEST_GITLEAKS`
and runs this module; set `GO` to the admitted compiler path where needed.
Tests use disposable Git servers, actual receive-pack quarantine and generated
keys. Scanner-dependent tests explicitly skip without `FORGE_TEST_GITLEAKS`;
skips are not acceptance. Native activation additionally needs exact original
hook hashes, tool provenance, dry-run/diff, abort owner, rollback and value-free
receipts under the accepted build-mode change rule.
References: [Git receive-pack and quarantine](https://git-scm.com/docs/git-receive-pack),
[Git object traversal](https://git-scm.com/docs/git-rev-list),
[Forgejo 11.0.3 generated hooks](https://codeberg.org/forgejo/forgejo/src/tag/v11.0.3/modules/repository/hooks.go),
[Gitleaks release](https://github.com/gitleaks/gitleaks/releases/tag/v8.30.1).