Repository navigation
gstack-redact-prepush: generated vector assets produce ~200 MEDIUM findings per file (SVG path floats are Luhn-valid ~1-in-6 of the time) #2827
Description
Version: gstack 1.81.0.0 · macOS 15.7.9 · bun
Summary
Committing the output of /diagram trips the pre-push guard hard. A single PR adding five diagrams reported:
gstack-redact-prepush: 1511 MEDIUM finding(s) in pushed diff (PII/internal). Not blocking.
Zero HIGH, nothing sensitive. Every finding is geometry: mermaid emits full-precision floats into SVG path data and excalidraw scene JSON, and long digit runs collide with pii.cc and pii.phone.e164.
The rules are working as designed. The problem is that a generated vector asset is a dense field of exactly the shape these two rules match, and the pre-push path has no way to exclude it.
Reproduction
# any mermaid diagram rendered through /diagram
gstack-redact --from-file docs/diagrams/settlement-mode-routing.svg --json
gstack-redact --from-file docs/diagrams/settlement-mode-routing.excalidraw --json
gstack-redact --from-file docs/diagrams/settlement-mode-routing.mmd --json| File | Bytes | Findings | Rules |
|---|---|---|---|
.mmd (source) |
1,199 | 0 | — |
.svg |
50,484 | 34 MEDIUM | pii.cc ×18, pii.phone.e164 ×16 |
.excalidraw |
42,579 | 177 MEDIUM | pii.phone.e164 ×164, pii.cc ×13 |
Five diagrams in one PR → ~1,500. PNGs contribute nothing (git treats them as binary, so no added lines).
Why pii.cc fires 18 times on a diagram
pii.cc is correctly Luhn-gated (validate: (span, match) => !insideUuid(match) && luhnValid(span), lib/redact-patterns.ts:755). But a random digit run passes Luhn roughly 1 time in 10, and an SVG is full of them:
$ # 13-19 digit runs in one 50KB rendered diagram
13-19 digit candidate runs: 103
of which Luhn-valid: 18 (17.5%)
examples: 58985137939453, 5593795776367, 599998474121094
Those are path d="M …" coordinates at float precision. 18 Luhn-valid runs out of 103 candidates reproduces the tool's 18 pii.cc findings exactly. Luhn cannot help here — at this candidate density it is a 1-in-6 filter, not a 1-in-∞ one.
pii.phone.e164 has no equivalent checksum, which is why it dominates the excalidraw count (164).
Why this matters
This is the failure mode redact-patterns.ts:214-220 already names, in the maintainer's own words, about parcel IDs in land/title repos:
a guardrail that cries wolf on the domain's primary identifier trains people to wave the warning through, which is how a real HIGH finding eventually gets ignored
Closed issue #2304 established the same pressure from the other direction — noise pushes users onto GSTACK_REDACT_PREPUSH=skip, which disables all scanning including HIGH.
Any repo that commits generated diagrams, charts, or vector art hits this on every such PR. /diagram is a gstack skill, so gstack's own output trips gstack's own guard.
Why it can't be configured away today
bin/gstack-redact-prepush deliberately discards file identity while collecting added lines:
// line ~285
if (!inHunk && (line.startsWith("+++") || line.startsWith("---"))) continue;
if (line.startsWith("+")) added.push(line.slice(1));The +++ b/path header is parsed and dropped, so by the time the engine runs, there is no way to say "skip generated assets". The only control is GSTACK_REDACT_PREPUSH=skip, which is all-or-nothing and gives up HIGH detection — the outcome #2304 was closed to avoid.
--allowlist exists on the engine but takes exact spans, so it cannot express "this file is generated" for thousands of coordinates.
Suggested fixes
1. Retain per-file context in the pre-push hook (preferred, small). The +++ b/<path> header is already being parsed at that line — keep it instead of discarding it, and scan per file. That enables a default skip-list for generated/derived assets (*.svg, *.excalidraw, *.min.js, *.map, lockfiles) and, as a bonus, lets findings name the file they came from, which they currently cannot.
2. Add a numeric-field context guard, in the spirit of insideUuid. insideUuid already sets the precedent: a narrow exemption for a digit run in a known non-PII container. The analogue is a digit run inside SVG path data or a bare numeric JSON array. Narrower than (1) but helps every entry point, not just pre-push.
(1) alone would fix the reported case. (1)+(2) together also cover --from-file and /cso runs over the same assets.
What I am not suggesting
Lowering pii.cc/pii.phone.e164 severity, or dropping Luhn. Both rules are behaving correctly; the input is the problem.
Environment
- gstack 1.81.0.0 (
~/.claude/skills/gstack, git install) - macOS 15.7.9, bun
- Assets produced by gstack's own
/diagramskill, renderedENGINE=browse
Activity
v1.91.19.0 fix: severe fix wave (truthful gates, no lost memory, ever…
capy-ai commentedon Oct 4, 2026
The pre-push redaction scan no longer reads SVG path floats, Excalidraw coordinates or seed/nonce/timestamp fields as phone or card numbers (a rendered /diagram went from 115 findings to 0), while real phone and card numbers still flag. Fixed in v1.91.19.0 (#3025).
To upgrade: run
/gstack-upgradein Claude Code, then start a new session.If you still see this, please reopen with
./setup --statusoutput.v1.91.19.0 fix: severe fix wave (truthful gates, no lost memory, ever…