FORGE-WP-ADHOC-2026-10-27: allow filename content digests in the receive scanner
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
This commit is contained in:
parent
2afdff68b1
commit
67f1cd044f
5 changed files with 75 additions and 4 deletions
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
| Kind | ID | Status | Lane | Source |
|
| 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-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-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 |
|
| 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-0010 | finished | — | workplans/FORGE-WP-0010-receive-scanner-budget.md |
|
||||||
| workplan | FORGE-WP-0011 | active | — | workplans/FORGE-WP-0011-forgejo-resource-requests.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 |
|
| 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-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-T02 | done | — | workplans/FORGE-WP-0006-independent-secret-detection.md |
|
||||||
| task | FORGE-WP-0006-T03 | cancel | — | workplans/FORGE-WP-0006-independent-secret-detection.md |
|
| task | FORGE-WP-0006-T03 | cancel | — | workplans/FORGE-WP-0006-independent-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
|
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
|
`unverified`. Both nonzero statuses must refuse the complete push. There is no
|
||||||
implicit baseline/ignore or override switch. A false-positive exception needs a
|
implicit baseline/ignore or override switch. Repository configuration cannot
|
||||||
separate reviewed mechanism with exact scope, approval and expiry.
|
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
|
## Bounds and remaining coverage
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,9 +159,14 @@ func TestSmallInputParity(t *testing.T) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
config := t.TempDir()
|
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)
|
write(t, filepath.Join(config, "ignore"), nil)
|
||||||
privateKey := key(t)
|
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
|
var random [32]byte
|
||||||
if _, err := rand.Read(random[:]); err != nil {
|
if _, err := rand.Read(random[:]); err != nil {
|
||||||
t.Fatal("cannot generate synthetic token")
|
t.Fatal("cannot generate synthetic token")
|
||||||
|
|
@ -181,6 +186,8 @@ func TestSmallInputParity(t *testing.T) {
|
||||||
{"token", canary, true},
|
{"token", canary, true},
|
||||||
{"binary-token", append([]byte{0, 0}, canary...), true},
|
{"binary-token", append([]byte{0, 0}, canary...), true},
|
||||||
{"inline-suppression", append([]byte("# gitleaks:allow\n"), privateKey...), 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) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
stdin, e := scan(context.Background(), f, config, tc.input)
|
stdin, e := scan(context.Background(), f, config, tc.input)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,21 @@ import (
|
||||||
|
|
||||||
const scannerHash = "88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509"
|
const scannerHash = "88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509"
|
||||||
const scannerVersion = "8.30.1"
|
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 maxInput = 512 * 1024
|
||||||
const maxObject = 8 * 1024 * 1024
|
const maxObject = 8 * 1024 * 1024
|
||||||
const maxTotal = 64 * 1024 * 1024
|
const maxTotal = 64 * 1024 * 1024
|
||||||
|
|
@ -333,7 +348,7 @@ func check(ctx context.Context, updates []update, binary string) (out result) {
|
||||||
}
|
}
|
||||||
return nil
|
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"))
|
return fail(errors.New("scanner-config-unavailable"))
|
||||||
}
|
}
|
||||||
// Ref names can themselves contain recognized tokens. Never echo them.
|
// Ref names can themselves contain recognized tokens. Never echo them.
|
||||||
|
|
|
||||||
40
workplans/ADHOC-2026-10-27.md
Normal file
40
workplans/ADHOC-2026-10-27.md
Normal file
|
|
@ -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.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue