diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 0a5ed28..5efb132 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -8,6 +8,7 @@ | Kind | ID | Status | Lane | Source | | --- | --- | --- | --- | --- | +| workplan | FORGE-WP-ADHOC-2026-10-27 | active | — | workplans/ADHOC-2026-10-27.md | | workplan | FORGE-WP-0006 | finished | — | workplans/FORGE-WP-0006-independent-secret-detection.md | | workplan | FORGE-WP-0007 | finished | — | workplans/FORGE-WP-0007-reuse-private-reader-contract-review.md | | workplan | FORGE-WP-0008 | proposed | — | workplans/FORGE-WP-0008-secret-detection-residual-coverage.md | @@ -15,6 +16,8 @@ | workplan | FORGE-WP-0010 | finished | — | workplans/FORGE-WP-0010-receive-scanner-budget.md | | workplan | FORGE-WP-0011 | active | — | workplans/FORGE-WP-0011-forgejo-resource-requests.md | | workplan | RFORGE-WP-0002 | finished | — | workplans/RFORGE-WP-0002-forge-resource-economics-evidence.md | +| task | FORGE-WP-ADHOC-2026-10-27-T01 | done | — | workplans/ADHOC-2026-10-27.md | +| task | FORGE-WP-ADHOC-2026-10-27-T02 | wait | — | workplans/ADHOC-2026-10-27.md | | task | FORGE-WP-0006-T01 | done | — | workplans/FORGE-WP-0006-independent-secret-detection.md | | task | FORGE-WP-0006-T02 | done | — | workplans/FORGE-WP-0006-independent-secret-detection.md | | task | FORGE-WP-0006-T03 | cancel | — | workplans/FORGE-WP-0006-independent-secret-detection.md | diff --git a/docs/receive-secret-detection.md b/docs/receive-secret-detection.md index 26c0afe..99ebc2d 100644 --- a/docs/receive-secret-detection.md +++ b/docs/receive-secret-detection.md @@ -56,8 +56,14 @@ 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. +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 diff --git a/tools/receive-secret-scan/batch_test.go b/tools/receive-secret-scan/batch_test.go index d2b5278..2a82be8 100644 --- a/tools/receive-secret-scan/batch_test.go +++ b/tools/receive-secret-scan/batch_test.go @@ -159,9 +159,14 @@ func TestSmallInputParity(t *testing.T) { } }() config := t.TempDir() - write(t, filepath.Join(config, "scanner.toml"), []byte("[extend]\nuseDefault = true\n")) + write(t, filepath.Join(config, "scanner.toml"), []byte(scannerConfig)) write(t, filepath.Join(config, "ignore"), nil) privateKey := key(t) + var digestRaw [32]byte + if _, err := rand.Read(digestRaw[:]); err != nil { + t.Fatal("cannot generate synthetic digest") + } + digest := hex.EncodeToString(digestRaw[:]) var random [32]byte if _, err := rand.Read(random[:]); err != nil { t.Fatal("cannot generate synthetic token") @@ -181,6 +186,8 @@ func TestSmallInputParity(t *testing.T) { {"token", canary, true}, {"binary-token", append([]byte{0, 0}, canary...), true}, {"inline-suppression", append([]byte("# gitleaks:allow\n"), privateKey...), true}, + {"filename-digest", []byte(" cards/access.md: " + digest + "\n"), false}, + {"bare-keyword-digest", []byte("access: " + digest + "\n"), true}, } { t.Run(tc.name, func(t *testing.T) { stdin, e := scan(context.Background(), f, config, tc.input) diff --git a/tools/receive-secret-scan/main.go b/tools/receive-secret-scan/main.go index ec28e58..5ab22f3 100644 --- a/tools/receive-secret-scan/main.go +++ b/tools/receive-secret-scan/main.go @@ -23,6 +23,21 @@ import ( const scannerHash = "88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509" const scannerVersion = "8.30.1" + +// scannerConfig extends the pinned scanner defaults with one reviewed shape. +// Keep it identical to repo-manager secret_scan.CONFIG. The allowlist targets +// the rule match, which is the filename stem through the digest: a trailing +// newline is sometimes included in the line slice and then a line anchor misses. +// A bare keyword assignment of the same digest still matches generic-api-key. +const scannerConfig = `[extend] +useDefault = true + +[[allowlists]] +description = "Filename key whose value is a lowercase 64-hex content digest" +targetRules = ["generic-api-key"] +regexTarget = "match" +regexes = ['^[\w.-]+\.(?:md|markdown|txt|yaml|yml|json|py|toml):[ \t]+[a-f0-9]{64}$'] +` const maxInput = 512 * 1024 const maxObject = 8 * 1024 * 1024 const maxTotal = 64 * 1024 * 1024 @@ -333,7 +348,7 @@ func check(ctx context.Context, updates []update, binary string) (out result) { } return nil } - if os.WriteFile(filepath.Join(dir, "scanner.toml"), []byte("[extend]\nuseDefault = true\n"), 0600) != nil || os.WriteFile(filepath.Join(dir, "ignore"), nil, 0600) != nil { + if os.WriteFile(filepath.Join(dir, "scanner.toml"), []byte(scannerConfig), 0600) != nil || os.WriteFile(filepath.Join(dir, "ignore"), nil, 0600) != nil { return fail(errors.New("scanner-config-unavailable")) } // Ref names can themselves contain recognized tokens. Never echo them. diff --git a/workplans/ADHOC-2026-10-27.md b/workplans/ADHOC-2026-10-27.md new file mode 100644 index 0000000..23a8b34 --- /dev/null +++ b/workplans/ADHOC-2026-10-27.md @@ -0,0 +1,40 @@ +--- +id: FORGE-WP-ADHOC-2026-10-27 +type: workplan +title: "Allow filename content digests in the receive scanner" +domain: financials +repo: railiance-forge +status: active +owner: grok +created: "2026-10-27" +updated: "2026-10-27" +--- + +## Allow a text-filename digest through generic-api-key + +```task +id: FORGE-WP-ADHOC-2026-10-27-T01 +status: done +priority: high +``` + +The pinned Gitleaks 8.30.1 profile now allowlists one `generic-api-key` match: +a filename with extension `.md`, `.markdown`, `.txt`, `.yaml`, `.yml`, `.json`, +`.py`, or `.toml`, whose value is exactly 64 lowercase hex digits. A bare +keyword assignment of the same digest still blocks, as do uppercase, short, +quoted, and suffixed values. The profile is `scannerConfig` in +`tools/receive-secret-scan` and matches Repo Manager's staged-scan config. +Repository `.gitleaks.toml` files and inline allow comments remain ignored. + +## Admit the updated adapter on the forge + +```task +id: FORGE-WP-ADHOC-2026-10-27-T02 +status: wait +priority: high +``` + +Source and the disposable scanner test do not change the installed pre-receive +adapter. The running Forgejo hook keeps the previous profile until this +executable is rebuilt and admitted. The emergent-security 0.2.7 push stays +blocked until that admission.