H #2784: four-part versions need a version declaration, not a shape

`"version": "1.128.1.0"`, `<Version>`, `AssemblyVersion("1.0.0.0")` and
Keep a Changelog headings raised pii.ip_public MEDIUM on every release,
because a four-part version is byte-for-byte a dotted quad. Exempt only when
the same line declares it a version immediately before the value (a version
key or word, an XML tag, an attribute call, or a `## [x]` heading). The same
digits after host:/server =/"upstream", bare on a line, or after prose that
merely mentions a version still report. Unlike #2784, this does not exempt a
value because the same string is declared a version elsewhere in the text:
that let a real address sharing a diff with a matching version slip. The
pre-push hook's VERSION-file rule (#2856) is unchanged.

Co-authored-by: cdecook23 <decook23@gmail.com>
This commit is contained in:
garrytanandcdecook23 committed 2026-10-03 20:38:34 +00:00
1 parent 907717d7cf
commit 5999ee3bad
2 files changed
+53 -1

No files matched your search

+23 -1
View File
@@ -320,6 +320,28 @@ export function isNumericMetadataValue(span: string, match: RegExpExecArray): bo
return NUMERIC_METADATA_KEY.test(input.slice(Math.max(lineStart, start - 80), start));
}
/**
* A four-part version (MAJOR.MINOR.PATCH.BUILD: .NET assembly versions,
* gstack's own VERSION) is byte-for-byte a dotted quad, and `1.128.1.0` is a
* public address, so `"version": "1.128.1.0"` raised pii.ip_public on every
* release (#2784). The value cannot decide it, so only the declaration
* immediately before it on the same line can: a `version` key or word
* (`"version": "`, `app_version = `, `<Version>`, `AssemblyVersion("`,
* `version `), or a Keep a Changelog heading `## [1.2.3.4]`. The same digits
* after `host:`, `server = ` or in prose that merely mentions a version still
* report, and the pre-push hook's VERSION-file rule (#2856) is unchanged.
*/
const VERSION_DECLARATION_BEFORE =
/(?:\b[Vv]ersion|\bVERSION|[a-z0-9_]Version|[_-][Vv]ersion|_VERSION)["'\]]?(?:[ \t]*[:=(>][ \t]*|[ \t]+)["']?$/;
const CHANGELOG_HEADING_BEFORE = /^#{1,6}[ \t]+\[$/;
export function isDeclaredVersion(match: RegExpExecArray): boolean {
const input = match.input ?? "";
const { start, end } = spanBounds(match);
const lineStart = input.lastIndexOf("\n", start - 1) + 1;
if (VERSION_DECLARATION_BEFORE.test(input.slice(Math.max(lineStart, start - 48), start))) return true;
return CHANGELOG_HEADING_BEFORE.test(input.slice(lineStart, start)) && input[end] === "]";
}
// ── Placeholder suppression (per-matched-span, NOT per-line) ─────────────────
/**
@@ -898,7 +920,7 @@ export const PATTERNS: RedactPattern[] = [
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),
validate: (span, match) => isPublicIPv4(span) && !isDeclaredVersion(match),
},
{
id: "pii.wallet",
+30
View File
@@ -130,3 +130,33 @@ describe("internal.hostname: per-machine config filenames are not hosts (#2962,
],
);
});
describe("pii.ip_public: a four-part version needs a version declaration (#2784)", () => {
table(
"pii.ip_public",
"MEDIUM",
[
' "version": "1.128.1.0",',
"app_version = 1.128.1.0",
"schema-version: 1.128.1.0",
"APP_VERSION=1.128.1.0",
"Released version 1.128.1.0 today",
"## [1.128.1.0] - 2026-09-02",
"<Version>1.128.1.0</Version>",
'[assembly: AssemblyVersion("1.0.0.0")]',
"released v1.128.1.0 today",
],
[
"1.128.1.0",
"host: 1.128.1.0",
"server = 1.128.1.0",
'"upstream": "1.128.1.0"',
"resolver = 8.8.8.8",
"version: 1.2.3.4 via 8.8.8.8",
"the version endpoint moved to 1.128.1.0",
"conversion 1.128.1.0",
"## 1.128.1.0 is our egress",
"## [1.128.1.0 mirror]",
],
);
});