Skip to content

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

Closed

Description

@cheeseman-sketch

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 /diagram skill, rendered ENGINE=browse

Activity

  1. capy-ai commented on Oct 4, 2026

    @capy-ai
    Contributor

    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-upgrade in Claude Code, then start a new session.

    If you still see this, please reopen with ./setup --status output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions