garrytan / garrytan/gstack

gstack-redact-prepush: generated vector assets produce ~200 MEDIUM findings per file (SVG path floats are Luhn-valid ~1-in-6 of the time)

Open
#2,827 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
133k
Forks
19.9k
Avg merge
18h 46m
Merged PRs (30d)
26

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

```bash
# 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:

```js
// 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/` 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`

Contributor guide

Open the contributing guide

Research direction

Start with bin/gstack-redact-prepush around the added-line collection near line 285, then review the relevant matching context in lib/redact-patterns.ts at lines 214-220 and 755. Reproduce with the listed --from-file commands and verify that per-file context can suppress generated assets while HIGH findings remain scanned; the resulting findings should also retain their source file identity.

Written by the indexing model from the issue text.

Assessment

Tech stack
bun, typescript
Domain
security, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.