fixyoursecret 0.3.1-developer-preview.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,45 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.4.2] - 2026-03-26
6
+
7
+ ### Fixed
8
+ - Release automation now forces Trusted Publisher OIDC mode in GitHub Actions by unsetting token auth in publish step.
9
+ - Prevents npm auth-token fallback issues (`E404`) when publishing from tag workflow.
10
+
11
+ ## [0.4.1] - 2026-03-26
12
+
13
+ ### Improved
14
+ - Tightened residual generic-noise filtering for large real-world corpora:
15
+ - better `.test/.spec` context detection
16
+ - stronger URL/base64/tutorial-data suppression for generic detector paths
17
+ - additional non-production placeholder filtering for provider fixtures
18
+ - Reduced quick 500-corpus findings from 117 to 51 while preserving quality gates.
19
+
20
+ ### CI/Release
21
+ - Switched release workflow to Trusted Publisher OIDC mode for npm publish.
22
+ - Kept tag-based automated publish via `.github/workflows/release-publish.yml`.
23
+
24
+ ## [0.4.0] - 2026-03-26
25
+
26
+ ### Added
27
+ - Verification v2 provider-safe checks for:
28
+ - OpenAI
29
+ - GitHub
30
+ - Slack
31
+ - Stripe
32
+ - 500-repo regression quality gate script:
33
+ - `scripts/check-tuning-regression.js`
34
+ - `fixtures/tuning/regression-thresholds.json`
35
+ - Weekly CI now runs:
36
+ - `tune:500:quick`
37
+ - regression gate fail-on-quality-drop
38
+
39
+ ### Improved
40
+ - Stronger suppression for obvious fake/placeholder secrets in tests/docs.
41
+ - Reduced large-corpus noise while preserving high-risk detections.
42
+ - Stable release channel metadata for npm publish.
43
+
5
44
  ## [0.3.1-developer-preview.1] - 2026-03-26
6
45
 
7
46
  ### Added
package/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  # FixYourSecret
4
4
 
5
- **Developer Preview**
6
-
7
5
  An ESLint-style CLI that finds leaked credentials, flags frontend exposure, suggests fixes, and helps rotate keys safely.
8
6
 
9
7
  [![Node >= 20](https://img.shields.io/badge/node-%3E%3D20-2ea44f)](https://nodejs.org/)
@@ -25,7 +23,8 @@ FixYourSecret helps teams catch these mistakes early and fix them with clear nex
25
23
  - Expanded provider detector coverage significantly.
26
24
  - Added benchmark-driven quality gates (`npm run benchmark`) so quality is measured every release.
27
25
  - Added CI threshold enforcement for recall/precision.
28
- - Added optional local verification mode for higher-confidence findings.
26
+ - Added Verification v2 provider-safe checks for higher-confidence findings.
27
+ - Added weekly 500-repo regression quality gate.
29
28
 
30
29
  ---
31
30
 
@@ -112,6 +111,32 @@ Run multi-repo tuning report:
112
111
  npm run tune:multi
113
112
  ```
114
113
 
114
+ Run large-scale corpus tuning (parallel clone + scan):
115
+
116
+ ```bash
117
+ npm run tune:large
118
+ ```
119
+
120
+ Generate and run 500-repo corpus:
121
+
122
+ ```bash
123
+ npm run corpus:generate
124
+ npm run tune:500
125
+ ```
126
+
127
+ Quick large-scale pass:
128
+
129
+ ```bash
130
+ npm run tune:large:quick
131
+ ```
132
+
133
+ Weekly regression check sequence (same as CI):
134
+
135
+ ```bash
136
+ npm run tune:500:quick
137
+ npm run regression:check
138
+ ```
139
+
115
140
  CI quality gate thresholds (defaults):
116
141
  - Recall >= 0.95
117
142
  - Precision >= 0.95
@@ -122,6 +147,7 @@ These can be tuned via env vars:
122
147
 
123
148
  Tuning workflow docs:
124
149
  - [./docs/tuning/process.md](./docs/tuning/process.md)
150
+ - Large corpus list: [./fixtures/tuning/repos.large.json](./fixtures/tuning/repos.large.json)
125
151
 
126
152
  ---
127
153
 
@@ -159,7 +185,7 @@ fixyoursecret scan [options]
159
185
  ---
160
186
 
161
187
  ## Verification Mode (Optional)
162
- `--verify safe` performs local structure checks for supported detectors (no external API calls).
188
+ `--verify safe` performs provider-safe local structure checks for supported detectors (no external API calls), including tighter checks for OpenAI, GitHub, Slack, and Stripe.
163
189
 
164
190
  Use `--verify-strict` to drop findings that fail verification.
165
191
 
@@ -168,7 +194,7 @@ Use `--verify-strict` to drop findings that fail verification.
168
194
  ## Config (`.fixyoursecretrc.json`)
169
195
  ```json
170
196
  {
171
- "ignorePaths": ["node_modules/**", ".git/**", "dist/**", "build/**", ".next/**", "coverage/**"],
197
+ "ignorePaths": ["node_modules/**", ".git/**", ".cache/**", "dist/**", "build/**", ".next/**", "coverage/**", "vendor/**", "tmp/**"],
172
198
  "allowedExtensions": [".js", ".ts", ".jsx", ".tsx", ".env", ".swift"],
173
199
  "maxFileSizeKB": 256,
174
200
  "entropyThreshold": 3.8,
@@ -201,6 +227,10 @@ Workflow file included:
201
227
 
202
228
  It runs tests, benchmark gate, scan, and uploads SARIF.
203
229
 
230
+ Automated npm release workflow:
231
+ - [./.github/workflows/release-publish.yml](./.github/workflows/release-publish.yml)
232
+ - Triggered by pushing version tags like `v0.4.1`
233
+
204
234
  ---
205
235
 
206
236
  ## Publish
@@ -1,4 +1,4 @@
1
- const TOKEN_REGEX = /[A-Za-z0-9_\-]{24,}/g;
1
+ const TOKEN_REGEX = /[A-Za-z0-9_\-+=]{28,}/g;
2
2
 
3
3
  export function detectGenericSecrets(content, options = {}) {
4
4
  const threshold = Number.isFinite(options.entropyThreshold) ? options.entropyThreshold : 3.8;
@@ -7,6 +7,8 @@ export function detectGenericSecrets(content, options = {}) {
7
7
  const value = match[0];
8
8
  if (!looksLikeHighEntropy(value, threshold)) continue;
9
9
  if (looksSafeCommonWord(value)) continue;
10
+ if (looksLikeCodeIdentifier(value)) continue;
11
+ if (!looksLikeSecretToken(value)) continue;
10
12
 
11
13
  findings.push({
12
14
  rule: "generic-high-entropy",
@@ -23,12 +25,14 @@ export function detectGenericSecrets(content, options = {}) {
23
25
  function looksLikeHighEntropy(value, threshold) {
24
26
  const entropy = shannonEntropy(value);
25
27
  const hasLower = /[a-z]/.test(value);
26
- const hasUpperOrSymbol = /[A-Z]/.test(value) || /[_-]/.test(value);
28
+ const hasUpperOrSymbol = /[A-Z]/.test(value) || /[_\-+/=]/.test(value);
27
29
  const hasDigit = /\d/.test(value);
28
- return entropy >= threshold && hasLower && hasUpperOrSymbol && hasDigit;
30
+ const adjustedThreshold = value.length >= 40 ? threshold + 0.25 : threshold;
31
+ return entropy >= adjustedThreshold && hasLower && hasUpperOrSymbol && hasDigit;
29
32
  }
30
33
 
31
34
  function looksSafeCommonWord(value) {
35
+ const lower = value.toLowerCase();
32
36
  return (
33
37
  value.startsWith("sk-") ||
34
38
  value.startsWith("AIza") ||
@@ -36,11 +40,59 @@ function looksSafeCommonWord(value) {
36
40
  value.startsWith("pk_live_") ||
37
41
  value.startsWith("http") ||
38
42
  value.includes("localhost") ||
39
- value.toLowerCase().includes("component") ||
40
- value.toLowerCase().includes("configuration")
43
+ lower.includes("component") ||
44
+ lower.includes("configuration") ||
45
+ lower.includes("diagnostics") ||
46
+ lower.includes("typescript")
41
47
  );
42
48
  }
43
49
 
50
+ function looksLikeSecretToken(value) {
51
+ if (/[/.:]/.test(value)) return false;
52
+ if (value.includes("://")) return false;
53
+ if (value.startsWith("www")) return false;
54
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)) return false;
55
+
56
+ const classes = [
57
+ /[a-z]/.test(value),
58
+ /[A-Z]/.test(value),
59
+ /\d/.test(value),
60
+ /[_\-+/=]/.test(value),
61
+ ].filter(Boolean).length;
62
+
63
+ const digits = (value.match(/\d/g) || []).length;
64
+ const hasLongHexOnly = /^[a-f0-9]{24,}$/i.test(value);
65
+ const hasOnlyAlphaNum = /^[A-Za-z0-9]+$/.test(value);
66
+ const symbolCount = (value.match(/[_\-+=]/g) || []).length;
67
+
68
+ if (hasLongHexOnly && digits < 6) return false;
69
+ if (classes < 3) return false;
70
+ if (value.length >= 32 && digits < 2) return false;
71
+ if (hasOnlyAlphaNum && value.length < 36) return false;
72
+ if (value.length >= 36 && symbolCount === 0 && digits < 4) return false;
73
+ return true;
74
+ }
75
+
76
+ function looksLikeCodeIdentifier(value) {
77
+ if (/^[A-Za-z_][A-Za-z0-9_]{30,}$/.test(value)) return true;
78
+ if (/^[A-Z][A-Za-z0-9]{20,}$/.test(value)) return true;
79
+ if (/^[_A-Za-z0-9-]+$/.test(value) && value.includes("__")) return true;
80
+ if (/^[A-Za-z]+(?:[A-Z][a-z0-9]+){2,}\d+$/.test(value)) return true;
81
+ if (/(Api|Context|Request|Response|Migration|Oauth2|OAuth2|V1alpha1|Agentflow)/.test(value)) return true;
82
+
83
+ const parts = value.split(/[_-]/).filter(Boolean);
84
+ if (parts.length >= 4) {
85
+ const alphaWords = parts.filter((p) => /^[A-Za-z]{3,}$/.test(p)).length;
86
+ if (alphaWords / parts.length >= 0.6) return true;
87
+ }
88
+
89
+ const vowelCount = (value.match(/[aeiou]/gi) || []).length;
90
+ const vowelRatio = vowelCount / value.length;
91
+ if (vowelRatio > 0.42 && !/[_\-+/=]/.test(value)) return true;
92
+
93
+ return false;
94
+ }
95
+
44
96
  function shannonEntropy(value) {
45
97
  const map = new Map();
46
98
  for (const ch of value) {
@@ -1,13 +1,17 @@
1
- const OPENAI_REGEX = /sk-(?:proj-)?[A-Za-z0-9_-]{20,}/g;
1
+ const OPENAI_REGEX = /(^|[^A-Za-z0-9_])(sk-(?:proj-)?[A-Za-z0-9_-]{24,})(?![A-Za-z0-9_-])/g;
2
2
 
3
3
  export function detectOpenAI(content) {
4
4
  const findings = [];
5
5
  for (const match of content.matchAll(OPENAI_REGEX)) {
6
+ const value = match[2];
7
+ if (!value) continue;
8
+ if (!/\d/.test(value)) continue;
9
+ if (value.includes("--")) continue;
6
10
  findings.push({
7
11
  rule: "openai-key",
8
12
  issue: "OpenAI key exposed",
9
- index: match.index ?? 0,
10
- value: match[0],
13
+ index: (match.index ?? 0) + (match[1]?.length || 0),
14
+ value,
11
15
  type: "openai",
12
16
  confidence: "high",
13
17
  });
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "fixyoursecret",
3
- "version": "0.3.1-developer-preview.1",
4
- "description": "Developer Preview: CLI tool to detect leaked secrets, frontend exposure, and generate safe fixes.",
3
+ "version": "0.4.2",
4
+ "description": "CLI tool to detect leaked secrets, frontend exposure, and generate safe fixes.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "fixyoursecret": "bin/index.js",
8
- "secretlint": "bin/index.js"
7
+ "fixyoursecret": "./bin/index.js",
8
+ "secretlint": "./bin/index.js"
9
9
  },
10
10
  "files": [
11
11
  "bin/",
@@ -30,10 +30,16 @@
30
30
  "history": "node ./bin/index.js history 20",
31
31
  "ci": "node ./bin/index.js ci",
32
32
  "benchmark": "node ./scripts/benchmark.js",
33
+ "regression:check": "node ./scripts/check-tuning-regression.js --report ./docs/tuning/report-500.json --thresholds ./fixtures/tuning/regression-thresholds.json",
34
+ "corpus:generate": "node ./scripts/generate-corpus.js --output ./fixtures/tuning/repos.500.json --count 500",
33
35
  "quality": "npm test && npm run benchmark",
34
36
  "test": "node --test",
35
37
  "tune:multi": "node ./scripts/multi-repo-tune.js --repos-file ./fixtures/tuning/repos.default.json --output ./docs/tuning/multi-repo-report.json --fp-review ./docs/tuning/false-positive-review.md",
36
- "tune:weekly": "npm run benchmark && npm run tune:multi"
38
+ "tune:large": "node ./scripts/multi-repo-tune.js --repos-file ./fixtures/tuning/repos.large.json --output ./docs/tuning/large-report.json --fp-review ./docs/tuning/false-positive-review-large.md --verify safe",
39
+ "tune:large:quick": "node ./scripts/multi-repo-tune.js --repos-file ./fixtures/tuning/repos.large.json --output ./docs/tuning/large-report.json --fp-review ./docs/tuning/false-positive-review-large.md --verify safe --max-repos 25",
40
+ "tune:500": "node ./scripts/multi-repo-tune.js --repos-file ./fixtures/tuning/repos.500.json --output ./docs/tuning/report-500.json --fp-review ./docs/tuning/false-positive-review-500.md --verify safe --concurrency 12 --max-repos 500",
41
+ "tune:500:quick": "node ./scripts/multi-repo-tune.js --repos-file ./fixtures/tuning/repos.500.json --output ./docs/tuning/report-500.json --fp-review ./docs/tuning/false-positive-review-500.md --verify safe --concurrency 8 --max-repos 100",
42
+ "tune:weekly": "npm run benchmark && npm run tune:500:quick && npm run regression:check"
37
43
  },
38
44
  "keywords": [
39
45
  "security",
@@ -53,8 +59,7 @@
53
59
  },
54
60
  "license": "MIT",
55
61
  "publishConfig": {
56
- "access": "public",
57
- "tag": "preview"
62
+ "access": "public"
58
63
  },
59
64
  "dependencies": {
60
65
  "chalk": "^5.4.1",
package/utils/config.js CHANGED
@@ -5,7 +5,7 @@ export const CONFIG_FILENAMES = [".fixyoursecretrc.json", ".secretlintrc.json"];
5
5
  export const BASELINE_FILENAMES = [".fixyoursecret-baseline.json", ".secretlint-baseline.json"];
6
6
 
7
7
  export const DEFAULT_CONFIG = {
8
- ignorePaths: ["node_modules/**", ".git/**", "dist/**", "build/**", ".next/**", "coverage/**"],
8
+ ignorePaths: ["node_modules/**", ".git/**", ".cache/**", "dist/**", "build/**", ".next/**", "coverage/**", "vendor/**", "tmp/**"],
9
9
  allowedExtensions: [".js", ".ts", ".jsx", ".tsx", ".env", ".swift"],
10
10
  maxFileSizeKB: 256,
11
11
  entropyThreshold: 3.8,
package/utils/verifier.js CHANGED
@@ -1,35 +1,52 @@
1
1
  export function verifyFinding(match, fileContent = "", snippet = "") {
2
+ const value = String(match.value || "");
3
+ const lowerSnippet = String(snippet || "").toLowerCase();
4
+
2
5
  switch (match.rule) {
3
- case "openai-key":
4
- return formatResult(/^(?:sk-(?:proj-)?)?[A-Za-z0-9_-]{24,}$/.test(match.value) && /\d/.test(match.value), "format");
6
+ case "openai-key": {
7
+ const formatOk = /^sk-(?:proj-)?[A-Za-z0-9_-]{24,}$/.test(value);
8
+ const hasDiversity = hasDiversityScore(value, 3);
9
+ const verified = formatOk && hasDiversity && !isLikelyPlaceholderToken(value, lowerSnippet);
10
+ return formatResult(verified, "provider-safe-v2");
11
+ }
5
12
  case "google-key":
6
- return formatResult(/^AIza[0-9A-Za-z_-]{35}$/.test(match.value), "format");
13
+ return formatResult(/^AIza[0-9A-Za-z_-]{35}$/.test(value), "provider-safe-v2");
7
14
  case "aws-access-key-id":
8
- return formatResult(/^(AKIA|ASIA)[A-Z0-9]{16}$/.test(match.value), "format");
9
- case "stripe-secret-key":
10
- return formatResult(/^sk_live_[0-9A-Za-z]{16,}$/.test(match.value), "format");
11
- case "slack-token":
12
- return formatResult(/^xox(?:b|p|a|r|s)-[0-9A-Za-z-]{10,}$/.test(match.value), "format");
13
- case "github-token":
14
- return formatResult(/^(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}$|^github_pat_[A-Za-z0-9_]{20,}$/.test(match.value), "format");
15
+ return formatResult(/^(AKIA|ASIA)[A-Z0-9]{16}$/.test(value), "provider-safe-v2");
16
+ case "stripe-secret-key": {
17
+ const formatOk = /^sk_live_[0-9A-Za-z]{20,}$/.test(value);
18
+ const verified = formatOk && !isLikelyPlaceholderToken(value, lowerSnippet);
19
+ return formatResult(verified, "provider-safe-v2");
20
+ }
21
+ case "slack-token": {
22
+ const formatOk = /^xox(?:b|p|a|r|s)-[0-9A-Za-z-]{10,}$/.test(value);
23
+ const hasSegments = value.split("-").length >= 3;
24
+ const verified = formatOk && hasSegments && !isLikelyPlaceholderToken(value, lowerSnippet);
25
+ return formatResult(verified, "provider-safe-v2");
26
+ }
27
+ case "github-token": {
28
+ const formatOk = /^(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}$|^github_pat_[A-Za-z0-9_]{40,}$/.test(value);
29
+ const verified = formatOk && !isLikelyPlaceholderToken(value, lowerSnippet);
30
+ return formatResult(verified, "provider-safe-v2");
31
+ }
15
32
  case "gitlab-token":
16
- return formatResult(/^glpat-[A-Za-z0-9_-]{20,}$/.test(match.value), "format");
33
+ return formatResult(/^glpat-[A-Za-z0-9_-]{20,}$/.test(value), "provider-safe-v2");
17
34
  case "twilio-api-key":
18
- return formatResult(/^SK[0-9a-fA-F]{32}$/.test(match.value), "format");
35
+ return formatResult(/^SK[0-9a-fA-F]{32}$/.test(value), "provider-safe-v2");
19
36
  case "sendgrid-api-key":
20
- return formatResult(/^SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}$/.test(match.value), "format");
37
+ return formatResult(/^SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}$/.test(value), "provider-safe-v2");
21
38
  case "mailgun-api-key":
22
- return formatResult(/^key-[A-Za-z0-9]{32}$/.test(match.value), "format");
39
+ return formatResult(/^key-[A-Za-z0-9]{32}$/.test(value), "provider-safe-v2");
23
40
  case "anthropic-api-key":
24
- return formatResult(/^sk-ant-[A-Za-z0-9_-]{20,}$/.test(match.value), "format");
41
+ return formatResult(/^sk-ant-[A-Za-z0-9_-]{20,}$/.test(value), "provider-safe-v2");
25
42
  case "cohere-api-key":
26
- return formatResult(/^co_[A-Za-z0-9]{30,}$/.test(match.value), "format");
43
+ return formatResult(/^co_[A-Za-z0-9]{30,}$/.test(value), "provider-safe-v2");
27
44
  case "huggingface-token":
28
- return formatResult(/^hf_[A-Za-z0-9]{30,}$/.test(match.value), "format");
45
+ return formatResult(/^hf_[A-Za-z0-9]{30,}$/.test(value), "provider-safe-v2");
29
46
  case "telegram-bot-token":
30
- return formatResult(/^\d{8,10}:[A-Za-z0-9_-]{35}$/.test(match.value), "format");
47
+ return formatResult(/^\d{8,10}:[A-Za-z0-9_-]{35}$/.test(value), "provider-safe-v2");
31
48
  case "npm-token":
32
- return formatResult(/^npm_[A-Za-z0-9]{36}$/.test(match.value), "format");
49
+ return formatResult(/^npm_[A-Za-z0-9]{36}$/.test(value), "provider-safe-v2");
33
50
  case "private-key-block": {
34
51
  const hasEnd = /-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/.test(fileContent.slice(match.index));
35
52
  return formatResult(hasEnd, "pem-structure");
@@ -56,18 +73,112 @@ export function normalizeVerifyMode(mode) {
56
73
  export function shouldSkipAsNonSecret(match, snippet = "", filePath = "", hints = []) {
57
74
  const lowerSnippet = snippet.toLowerCase();
58
75
  const lowerPath = filePath.toLowerCase();
76
+ const value = String(match.value || "");
77
+ const isNonProdPath = (
78
+ ["/test/", "/tests/", "/__tests__/", "/fixtures/", "/docs/", "/examples/", "/spec/"]
79
+ .some((segment) => lowerPath.includes(segment)) ||
80
+ /\.test\.[a-z0-9]+$/i.test(lowerPath) ||
81
+ /\.spec\.[a-z0-9]+$/i.test(lowerPath)
82
+ );
59
83
 
60
84
  const builtinHints = ["example", "dummy", "fake", "sample", "not_secret", "replace_in_runtime_only", "docs_only"];
61
85
  const allHints = [...builtinHints, ...hints.map((h) => String(h).toLowerCase())];
62
86
 
63
87
  if (allHints.some((hint) => lowerSnippet.includes(hint))) return true;
88
+ if (isNonProdPath && isLikelyPlaceholderToken(value, lowerSnippet)) return true;
89
+
90
+ if (
91
+ isNonProdPath &&
92
+ /(?:ghp_|github_pat_|xox[bpars]-|sk_live_|sk-)/.test(value) &&
93
+ /(?:x{6,}|y{6,}|z{6,}|123456|example|dummy|placeholder|mock|test[_-]?key|your[_-]?key)/i.test(lowerSnippet + " " + value.toLowerCase())
94
+ ) {
95
+ return true;
96
+ }
64
97
 
65
98
  if (
66
99
  match.rule === "generic-high-entropy" &&
67
- ["/test/", "/tests/", "/__tests__/", "/fixtures/", "/docs/"].some((segment) => lowerPath.includes(segment))
100
+ [
101
+ "/test/",
102
+ "/tests/",
103
+ "/__tests__/",
104
+ "/fixtures/",
105
+ "/docs/",
106
+ "/spec/",
107
+ "/bench/",
108
+ "/benchmark/",
109
+ "/examples/",
110
+ "/migrations/",
111
+ "/generated/",
112
+ "/api-client/",
113
+ "/fonts/",
114
+ "/vendor/"
115
+ ].some((segment) => lowerPath.includes(segment))
116
+ ) {
117
+ return true;
118
+ }
119
+
120
+ if (match.rule === "generic-high-entropy") {
121
+ if (/(?:https?:\/\/|url:|href=|source:|fileName:|filename:|data:image|base64)/.test(lowerSnippet)) return true;
122
+ const genericNoiseHints = [
123
+ "canvasrenderingcontext2d",
124
+ "axios parameter creator",
125
+ "sourcemappingurl=data:",
126
+ "base64,",
127
+ "images.unsplash.com",
128
+ ".woff2",
129
+ "oauth2",
130
+ "requestparameters",
131
+ "data-cy=",
132
+ "uuid",
133
+ "v1alpha1",
134
+ "openapi",
135
+ "migration",
136
+ "model:",
137
+ "anthropiccontext1m",
138
+ "bigint64arraybytes_per_element",
139
+ "claude-sonnet",
140
+ "gemini-",
141
+ "oauth/callback?code=",
142
+ "audio-16khz-16bit",
143
+ "i18next-browser-languagedetector",
144
+ "msapplication-square70x70logo",
145
+ "apps.googleusercontent.com",
146
+ "downloaded-logs-",
147
+ "webkiformboundary",
148
+ "gpt-4o-realtime-preview"
149
+ ];
150
+ if (genericNoiseHints.some((hint) => lowerSnippet.includes(hint))) return true;
151
+ }
152
+
153
+ if (
154
+ match.rule === "private-key-block" &&
155
+ isNonProdPath &&
156
+ /(?:example|dummy|placeholder|mock|do not share|xxxxx|\.{3})/.test(lowerSnippet)
68
157
  ) {
69
158
  return true;
70
159
  }
71
160
 
72
161
  return false;
73
162
  }
163
+
164
+ function isLikelyPlaceholderToken(value, lowerSnippet = "") {
165
+ const lowerValue = String(value || "").toLowerCase();
166
+ const joined = `${lowerValue} ${lowerSnippet}`;
167
+ if (!lowerValue) return false;
168
+
169
+ if (/(?:x{8,}|0{8,}|12345678)/.test(lowerValue)) return true;
170
+ if (/(?:example|dummy|placeholder|mock|redacted|sanitized|masked)/.test(joined)) return true;
171
+ if (/(?:ghp_x+|xox[bpars]-x+|sk_live_x+|sk-[a-z]*x{8,})/.test(lowerValue)) return true;
172
+ if (/(?:your[_-]?token|your[_-]?key|replace[_-]?me|insert[_-]?key)/.test(joined)) return true;
173
+ return false;
174
+ }
175
+
176
+ function hasDiversityScore(value, minClasses) {
177
+ const classes = [
178
+ /[a-z]/.test(value),
179
+ /[A-Z]/.test(value),
180
+ /\d/.test(value),
181
+ /[_-]/.test(value),
182
+ ].filter(Boolean).length;
183
+ return classes >= minClasses;
184
+ }