fix(redact): stop reporting .env.local as an internal hostname

`internal.hostname` ends in `.local|.prod|.staging|…`, so `.env.local`
matches on `env.local` and a dotenv FILENAME is reported as a leaked
internal host.

The collision is not exotic. It fires on `--env-file=.env.local` in an npm
script, `.env.staging` in a README, `.env.prod` in a .gitignore — ordinary
lines on branches that leak nothing. Measured on one private repo, three of
four MEDIUM findings in a routine push were this, and the fourth was a
deleted localhost URL. That ratio is the real cost: a scanner that reports
package.json is one people learn to skim, and skimming is how the HIGH
finding it exists for gets missed.

The guard follows the `insideUuid` precedent and stays deliberately narrow —
it exempts only a span beginning `env.` immediately preceded by a dot, i.e.
the literal `.env.<suffix>` form. `api.corp.local`, `build-7.internal` and
`myenv.local` all still report.

The test pins both directions, and the negative controls are the point: an
exemption written as "any span ending .local" would pass the dotenv half
while quietly gutting the pattern for every real host. Verified red/green —
with the validate hook removed, exactly the 6 dotenv cases fail and all 9
real-host controls still pass.
This commit is contained in:
David Park
2026-08-31 20:52:28 +00:00
committed by Garry Tan
parent b77c1923c8
commit cc94bc34ba
2 changed files with 106 additions and 0 deletions
+29
View File
@@ -298,6 +298,33 @@ const UUID_RE = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-
* this stays cheap on a multi-megabyte buffer. */
const UUID_CONTEXT_CHARS = 40;
/**
* True when an `internal.hostname` span is really the tail of a dotenv
* FILENAME (`.env.local`, `.env.staging`, `.env.prod`) rather than a host.
*
* The hostname pattern ends in `.local|.prod|.staging|…`, so `.env.local`
* matches on `env.local` — a false positive on one of the most commonly
* committed filenames there is. It arrives via npm scripts
* (`--env-file=.env.local`), READMEs, `.gitignore` and setup docs, i.e. on
* ordinary branches that leak nothing, which is the noise that teaches people
* to skim past MEDIUM findings.
*
* Deliberately narrow, in the same spirit as `insideUuid`: it exempts ONLY a
* span beginning `env.` that is immediately preceded by a dot — the literal
* `.env.<suffix>` form. A real host still reports, `api.corp` and
* `build-7.internal` included, and so does `myenv.local`, which is not a
* dotenv file.
*/
export function isDotenvFilename(match: RegExpExecArray): boolean {
const input = match.input ?? "";
const span = match[1] ?? match[0];
if (!/^env\./i.test(span)) return false;
// Mirror the engine: capture group 1 when present, else the whole match.
const spanStartInMatch = match[1] !== undefined ? match[0].indexOf(match[1]) : 0;
const spanStart = match.index + Math.max(0, spanStartInMatch);
return spanStart > 0 && input[spanStart - 1] === ".";
}
/**
* True when the matched span sits ENTIRELY inside a UUID.
*
@@ -751,6 +778,8 @@ export const PATTERNS: RedactPattern[] = [
category: "internal",
description: "Internal hostname (*.internal/.corp/.local/.prod/.staging)",
regex: /\b([a-z0-9][a-z0-9\-]*\.(?:internal|corp|local|lan|prod|staging))\b/i,
// `.env.local` and friends are filenames, not hosts. See isDotenvFilename.
validate: (_span, match) => !isDotenvFilename(match),
},
{
id: "internal.url_private",