gstack-redact: pii.ip_public fires on 4-digit VERSION strings, so every /ship warns on the version it just wrote
- Dominant language
- TypeScript
- Stars
- 133k
- Forks
- 19.9k
- Avg merge
- 18h 46m
- Merged PRs (30d)
- 26
Description
## Summary
`pii.ip_public` fires on 4-component version strings. A repo using the `MAJOR.MINOR.PATCH.MICRO` VERSION convention that `/ship` itself writes gets a MEDIUM finding on every release, from its own version number.
gstack does this to itself. Its current `VERSION` is `1.84.1.0`:
```
$ printf '1.84.1.0\n' | gstack-redact --repo-visibility public
gstack-redact scan — repo PUBLIC
MEDIUM pii.ip_public 1:1 1.84****
HIGH=0 MEDIUM=1 LOW=0 WARN=0
```
## Repro
Any `/ship` in a repo with a 4-digit VERSION file. The pre-push guard prints:
```
gstack-redact-prepush: 2 MEDIUM finding(s) in pushed diff (PII/internal). Not blocking. Review before this becomes public.
```
The findings are the `VERSION` line and the `## [1.10.0.0] - YYYY-MM-DD` CHANGELOG heading that Step 12 and Step 13 just wrote.
## Root cause
`lib/redact-patterns.ts:758-764`
```ts
{
id: "pii.ip_public",
tier: "MEDIUM",
category: "pii",
description: "Public IPv4 address",
regex: /\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/,
validate: (span) => isPublicIPv4(span),
},
```
`isPublicIPv4` (`lib/redact-patterns.ts:145-160`) excludes RFC1918, loopback, link-local, CGNAT and multicast. A version like `1.84.1.0` or `1.10.0.0` has first octet `1`, which is in none of those ranges, so it validates as a public address.
Version strings are structurally indistinguishable from IPv4 by shape alone. The distinguishing signal is the surrounding context, which the pattern does not see.
## Why `--allowlist` is not the workaround
Two independent reasons:
1. **The pre-push hook never passes one.** `bin/gstack-redact-prepush:420` is the only call site:
```ts
for (const f of scanAddedLines(added, { repoVisibility: "private" })) {
```
No `allowlist` key, and no config lookup anywhere in that file. The `--allowlist` flag exists only on the manual CLI, so nothing a user writes to disk can reach the hook.
2. **Allowlist entries are exact spans.** `lib/redact-engine.ts:368` builds `new Set(opts.allowlist ?? [])` and line 387 tests `allow.has(span)`. Allowlisting `1.10.0.0` does nothing for `1.11.0.0`, so a release-cadence false positive would need a new entry every release.
## Severity
Low impact, and I want to be fair about that: MEDIUM never blocks. `bin/gstack-redact-prepush:421-422` only pushes HIGH onto the blocking path, and the file header says MEDIUM is warn-only by design. Nothing is broken.
The cost is calibration. This is a guardrail whose value depends on people reading its output, and it currently emits a guaranteed false positive on the exact commit `/ship` generates. A warning that is always wrong on release commits is a warning people learn to skip, which is the failure mode #1946 was closed to prevent.
## Possible fixes
No strong preference between these — whichever fits the engine's design:
1. **Context-aware suppression.** Skip `pii.ip_public` when the span is on a line matching a version-file or changelog-heading shape (`^\d+\.\d+\.\d+\.\d+$` as the entire line, or `^## \[...\]`). Narrow and keeps genuine IPs in prose flagged.
2. **Path-aware suppression.** The hook knows the file each added line came from; skip this one pattern in `VERSION` and `CHANGELOG.md`. Needs the engine to accept a path hint, which may be more plumbing than it's worth.
3. **Glob allowlist entries.** Broader value than this bug, but it does not help the hook until the hook reads an allowlist at all.
## Related observation, same shape
`pii.phone.e164` fires on a GitHub numeric repository ID in Terraform:
```hcl
default = "nitishssh@99980274/classmode-studio@1351348662"
```
`1351348662` is 10 digits, so it matches E.164. That is the immutable OIDC subject form GitHub requires, so it is normal content in any repo doing OIDC federation, and it is public data.
Filing as one issue rather than two since both are the same class — numeric identifiers in release and infra config matching PII shapes — but happy to split if you'd rather track them separately.
## Environment
gstack 1.84.1.0, macOS (Darwin 27.0.0), bun 1.3.14.
Contributor guide
Research direction
Start with lib/redact-patterns.ts:145-160 and 758-764 to understand IPv4 validation, then reproduce the false positive with gstack-redact and inspect bin/gstack-redact-prepush:420-422. Trace how added-line context reaches the scanner and compare the proposed context-aware and path-aware options. Done means release VERSION and CHANGELOG headings no longer warn while genuine public IPv4 findings remain detectable.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- security, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 56/100