From 2ed0622edcd5eaf441e30ac1561830502a314013 Mon Sep 17 00:00:00 2001 From: garrytan Date: Mon, 2 Nov 2026 21:51:50 +0000 Subject: [PATCH] fix: pre-landing review fixes (GST-1064: passphrases passed to a call still report; only file names and short snake_case keys are names) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- changelog.d/gst-1064-secrets-call-literal.md | 2 +- lib/redact-patterns.ts | 13 +++++++------ test/redact-context-exemptions.test.ts | 6 +++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/changelog.d/gst-1064-secrets-call-literal.md b/changelog.d/gst-1064-secrets-call-literal.md index b43df3d5c..e64d6f09a 100644 --- a/changelog.d/gst-1064-secrets-call-literal.md +++ b/changelog.d/gst-1064-secrets-call-literal.md @@ -1,2 +1,2 @@ ### Fixed -- **The secrets check no longer reads a file name or dict key inside a call as a credential (GST-1064).** `key = json.loads(files.get("key.json", ""))` failed pregate with `PREGATE_SECRETS` (`env.kv`): eight distinct letters in `"key.json"` reach the entropy bar, so the call's argument looked like a secret. So did `token = resp.json().get("access_token")` and a `getenv` default like `"change-me-later"`. A quoted argument made only of lowercase words joined by `_`, `.` or `-` now counts as a name. A real key in quotes, in a `.env` line, or passed to a call (`decrypt("")`) still reports, and the entropy bar is unchanged. +- **The secrets check no longer reads a file name or dict key inside a call as a credential (GST-1064).** `key = json.loads(files.get("key.json", ""))` failed pregate with `PREGATE_SECRETS` (`env.kv`): eight distinct letters in `"key.json"` reach the entropy bar, so the call's argument looked like a secret. So did `token = resp.json().get("access_token")`. A quoted argument that is a lowercase file name or a snake_case key of two or three words now counts as a name. A real key in quotes, in a `.env` line, or passed to a call still reports, and so does a passphrase (`decrypt("prod-signing-key")`, a `getenv` default like `"correct-horse-battery-staple"`). The entropy bar is unchanged. diff --git a/lib/redact-patterns.ts b/lib/redact-patterns.ts index 9f1e8be67..a2c2eea82 100644 --- a/lib/redact-patterns.ts +++ b/lib/redact-patterns.ts @@ -629,13 +629,14 @@ function isBareEnvRead(span: string, match: RegExpExecArray): boolean { /** * True when `text` holds a quoted, non-placeholder, high-entropy literal. - * GST-1064: lowercase letter words joined by `_`, `.` or `-` name a file, dict - * key or default (`"key.json"`, `"access_token"`, `"change-me-later"`); eight - * distinct letters already reach 3.0 bits, so entropy alone cannot tell them - * from a secret. A literal with a digit, an uppercase letter or one unbroken - * run still counts. + * GST-1064: a lowercase file name (`"key.json"`) or a snake_case key of two or + * three words (`"access_token"`) names what the call reads; eight distinct + * letters already reach 3.0 bits, so entropy alone cannot tell them from a + * secret. A passphrase still counts: kebab or dotted words + * (`"correct-horse-battery-staple"`), four or more snake_case words, a digit, + * an uppercase letter or one unbroken run. */ -const NAME_SHAPED_LITERAL = /^[a-z]+(?:[_.-][a-z]+)+$/; +const NAME_SHAPED_LITERAL = /^(?:[a-z]+(?:_[a-z]+){1,2}|[a-z]+(?:[_-][a-z]+)*\.[a-z]{2,5})$/; function carriesSecretLiteral(text: string): boolean { for (const [, literal] of text.matchAll(/["']([^\s'"]{8,})["']/g)) if (!isPlaceholderSpan(literal) && !NAME_SHAPED_LITERAL.test(literal) && shannonEntropy(literal) >= 3.0) return true; diff --git a/test/redact-context-exemptions.test.ts b/test/redact-context-exemptions.test.ts index 8a82b76bc..3ccbd5148 100644 --- a/test/redact-context-exemptions.test.ts +++ b/test/redact-context-exemptions.test.ts @@ -116,7 +116,7 @@ describe("env.kv: a name-shaped literal inside a call is a file or dict key, not String.raw`key = json.loads(files.get("key.json", ""))`, String.raw`token = resp.json().get("access_token")`, String.raw` session = store.load('session.cookie')`, - String.raw`password = os.getenv("DB_PASSWORD", "change-me-later")`, + String.raw`secret = vault.read("client_secret")`, ], [ String.raw`key = "${FAKE_KEY}"`, @@ -125,6 +125,10 @@ describe("env.kv: a name-shaped literal inside a call is a file or dict key, not String.raw`token = resp.json().get("access_token", "${FAKE_KEY}")`, String.raw`password = os.getenv("DB_PASSWORD", "hunter2.Zx9Qw8")`, String.raw`key = json.loads(files.get("key.json")) or "${FAKE_KEY}"`, + String.raw`password = os.getenv("DB_PASSWORD", "correct-horse-battery-staple")`, + String.raw`api_key = decrypt("prod-signing-key")`, + String.raw`token = decrypt("correct_horse_battery_staple")`, + String.raw`key = decrypt("qwhzxkvbnmplor")`, ], ); });