secretscan attestor
Runs a Gitleaks pattern scan over every product and every prior attestor's JSON, with recursive base64/hex/URL decoding (default 3 layers) so secrets hidden inside encoded blobs still surface.
| Name | secretscan |
|---|---|
| Predicate type | https://aflock.ai/attestations/secretscan/v0.1 |
| Lifecycle | postproduct |
| Default binary? | No |
| Recommended trace | off — no syscall tracing needed |
| Auto-attaches when | Not auto-detected — attach explicitly with -a. |
The facts in this box are generated from the cilock binary's own catalog (cilock tools list). Do not hand-edit — run npm run gen:catalog.
What it captures
The predicate has a single top-level field, findings, which is always an array (empty [] on a clean scan). Each Finding carries:
ruleId— Gitleaks rule that fired, lowercased.description— human-readable rule description from Gitleaks.location— eitherproduct:<path>for product hits orattestation:<attestor-name>for hits inside a prior attestor's JSON (thecommandrunattestor expands toattestation:commandrun:stdout,:stderr, and:json).startLine— line number in the source (Go fieldLine, JSON tagstartLine).secret— acryptoutil.DigestSet(multi-algorithm hashes) of the actual secret value. The raw secret is never stored.match— a redacted snippet with context around the hit, truncated tomaxMatchDisplayLength(40 chars) with[SENSITIVE-VALUE]standing in for the value itself.entropy— Gitleaks' Shannon-entropy score for the match.encodingPath— the decode chain that surfaced the secret, listed outermost to innermost (e.g.,["base64","hex"]means base64-then-hex was peeled off before the secret matched). Empty/absent on direct hits.locationApproximate—truewhenever the finding came from a decoded layer, since line numbers in decoded payloads don't map cleanly back to the source file.
Scanned products are also added as Subjects() under the key product:<path>.
When to use
On every CI build. Pair with --attestor-secretscan-fail-on-detection to fail-closed so a leaked key blocks the run instead of being recorded as evidence of the leak. Without the flag, findings are recorded but the build still passes — useful for triage before turning the guard on.
Recursive decoding
scanBytes in scanner.go walks each layer of content through three encoding scanners defined in encoding.go:
- base64 — matches
[A-Za-z0-9+/]{15,}={0,2}and the URL-safe variant; triesStdEncodingthen falls back toRawURLEncoding. - hex — matches
[0-9a-fA-F]{16,}and requires even length. - url — matches both consecutive
%XXruns (3+) and tokens containing an encoded%3D(equals sign), thenurl.QueryUnescapes.
For each decoded payload that's long enough, the scanner recurses with currentDepth+1. Recursion stops at min(maxDecodeLayers, maxScanRecursionDepth=3) — the hard safety cap in constants.go is 3 regardless of flag value. Each level prepends its codec name to encodingPath and flags findings as locationApproximate=true.
Flags
| Flag | Default | What it does |
|---|---|---|
--attestor-secretscan-fail-on-detection | false | Return an error from Attest() if any finding is recorded OR if any per-file/per-attestor scan errored. |
--attestor-secretscan-max-file-size-mb | 10 | Skip files larger than this; 0 disables the limit. Also passed to Gitleaks as MaxTargetMegaBytes. |
--attestor-secretscan-max-decode-layers | 3 | Maximum encoding layers to peel; capped at the hard recursion limit of 3. |
--attestor-secretscan-config-path | (none) | Path to a custom Gitleaks TOML config (decoded with pelletier/go-toml). When set, manual allowlist flags are ignored. |
--attestor-secretscan-allowlist-regex | (none) | Regex pattern for content to ignore. Repeatable. Ignored when --config-path is set. |
--attestor-secretscan-allowlist-stopword | (none) | Exact string to ignore. Repeatable. Ignored when --config-path is set. |
Output shape
{
"findings": [
{
"ruleId": "aws-access-token",
"description": "AWS Access Token",
"location": "product:dist/config.yaml",
"startLine": 42,
"secret": {
"sha256": "…",
"sha1": "…"
},
"match": "key: [SENSITIVE-VALUE]…",
"entropy": 4.7,
"encodingPath": ["base64"],
"locationApproximate": true
}
]
}
Gotchas
- Fail-closed is strict. With
--fail-on-detection,Attest()errors not only on findings but also on accumulated scan errors (scanErrors). This is intentional (per the source comment): an empty findings list from a crashed Gitleaks call is otherwise indistinguishable from a clean scan, so an attacker who could induce a crash would bypass the guard. config-pathreplaces, not merges. Supplying a Gitleaks TOML disables the manual allowlist entirely and logscommand-line allowlists ignored. If you need both, fold your allowlist into the TOML.- Post-product timing. The lifecycle is
postproduct, so the wrapped command has already run by the time fail-closed triggers. The guard prevents the leak from being signed and shipped, not from being executed locally. Usecilock verifywith a policy that checksfindings == []to gate downstream consumers. - No self-scan, no peer-scan.
shouldSkipAttestorskips thesecretscanattestor itself and any otherpostproductattestor to avoid races and recursion. - Binary products and directories are skipped via
shouldSkipProduct(MIME-type check), so secrets compiled into binaries won't be caught here. - Hard 3-layer recursion cap.
maxScanRecursionDepthinconstants.gois 3 — setting--max-decode-layershigher has no effect.
CLI example
Real secret-pattern scan against the wrapped command's outputs. Surfaces real findings (or none) and gates the build via --attestor-secretscan-fail-on-detection.
# secretscan is a postproduct attestor — it inspects the command's
# captured products and opened files (via the ptrace spy) for secret
# patterns. The wrapped command should be the real workload whose
# output you want scanned (a build, an install, a config render),
# not a synthetic echo.
cilock run --step build \
--signer-file-key-path key.pem --outfile attestation.json \
--attestations secretscan,environment,git \
--attestor-secretscan-fail-on-detection \
-- go build -o bin/myapp ./cmd/myapp
Validated against a real directory tree with --fail-on-detection enabled. See the full real-data example at https://github.com/aflock-ai/attestor-compliance-examples/tree/main/39-secretscan.