Allowlists
The principle
Some findings can't be fixed immediately — a third-party vulnerability awaiting a vendor patch, a designed exception with an open tracking issue, a known false positive while a gate's predicate is being refined.
The framework allows these to be suppressed via per-gate allowlist files. But every allowlist entry must have:
- A tracking issue (e.g.
MAT-1234) — proves someone owns the resolution. - A hard expiration date — proves the suppression is temporary.
The runner validates every allowlist on every run. Past-expiry entries become hard failures. The build won't pass until the entry is renewed (with a fresh expiration) or the underlying finding is fixed.
This is the difference between a healthy and an unhealthy gate culture. An unbounded allowlist is just unenforced rules. A bounded one shrinks monotonically.
File location
Sibling to the gate file:
packages/components/src/cards/__gates__/
card-action-targets.gate.ts
card-action-targets.allowlist.jsonThe runner reads the allowlist at <gate-base>.allowlist.json automatically.
File shape
{
"entries": [
{
"key": "<finding-fingerprint>",
"fingerprint": "<finding-fingerprint>",
"reason": "Vendor patch not yet released; switching libs in Q3.",
"tracking_issue": "MAT-1234",
"expires_at": "2026-09-01",
"added_by": "alice",
"added_at": "2026-05-15"
}
]
}| Field | Required | Notes |
|---|---|---|
key | yes | Stable identifier. Usually equals the finding's fingerprint. |
fingerprint | no | If provided, match by fingerprint; otherwise match by key. |
reason | yes | Plain English, ≤240 chars. Future you needs to read this. |
tracking_issue | yes | An issue tracker reference (e.g. MAT-1234, GitHub URL). |
expires_at | yes | ISO date (YYYY-MM-DD). Typically 60–90 days out. |
added_by | yes | Author handle / email. |
added_at | yes | ISO date of when the entry was added. |
How matches work
The runner runs the gate, collects its Finding[], then for each finding:
- Look up the gate's allowlist (if present).
- If any entry has
fingerprint === finding.fingerprintorkey === finding.fingerprint, the finding is suppressed (does not contribute to exit code). - The suppressed count is reported in the gate's summary (
allowlistApplied) so over-suppression is visible.
What goes wrong if you skip a field
- No
tracking_issue— the runner throws on allowlist load with a clear error. Add the issue or remove the entry. - No
expires_at— same. There are no permanent allowlist entries. - Past-expiry
expires_at— the runner emits ablockingFinding ("allowlist entry X expired on Y"). Either renew the entry (with a new tracking issue and a new horizon) or fix the underlying finding.
How a healthy allowlist looks
gate allowlist_size max_age_days expiring_within_30
card-action-targets 0 - -
dependency-audit 1 47 0
secret-scan 0 - -Small, recent, no imminent expirations. The scorecard at /testing/scorecard charts this per gate.