The pinned Gitleaks profile now ignores one generic-api-key match: a text-extension filename whose value is exactly 64 lowercase hex digits. A bare keyword assignment of that digest still blocks. The installed Forgejo adapter is unchanged. Assistant: grok Assistant-Model: grok-4.7 Assistant-Session: 01a17f2b-8cc3-76f0-9bce-3b3444b9a5e9
8.2 KiB
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. Repository configuration cannot
disable detection. The adapter's private config adds one reviewed
generic-api-key match allowlist: a filename with a normal text extension
(.md, .markdown, .txt, .yaml, .yml, .json, .py, or .toml) whose
value is exactly 64 lowercase hex digits. A bare keyword assignment of that
same digest still blocks. Any wider exception needs a separate reviewed
mechanism with exact scope, approval and expiry. Keep this profile identical
to Repo Manager's staged-scan config.
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:
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, Git object traversal, Forgejo 11.0.3 generated hooks, Gitleaks release.