fixyoursecret 0.3.1-developer-preview.1 → 0.4.0
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 +20 -0
- package/README.md +31 -5
- package/detectors/generic.js +57 -5
- package/detectors/openai.js +7 -3
- package/package.json +10 -5
- package/utils/config.js +1 -1
- package/utils/verifier.js +110 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.4.0] - 2026-03-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Verification v2 provider-safe checks for:
|
|
9
|
+
- OpenAI
|
|
10
|
+
- GitHub
|
|
11
|
+
- Slack
|
|
12
|
+
- Stripe
|
|
13
|
+
- 500-repo regression quality gate script:
|
|
14
|
+
- `scripts/check-tuning-regression.js`
|
|
15
|
+
- `fixtures/tuning/regression-thresholds.json`
|
|
16
|
+
- Weekly CI now runs:
|
|
17
|
+
- `tune:500:quick`
|
|
18
|
+
- regression gate fail-on-quality-drop
|
|
19
|
+
|
|
20
|
+
### Improved
|
|
21
|
+
- Stronger suppression for obvious fake/placeholder secrets in tests/docs.
|
|
22
|
+
- Reduced large-corpus noise while preserving high-risk detections.
|
|
23
|
+
- Stable release channel metadata for npm publish.
|
|
24
|
+
|
|
5
25
|
## [0.3.1-developer-preview.1] - 2026-03-26
|
|
6
26
|
|
|
7
27
|
### 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
|
[](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
|
|
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,
|
package/detectors/generic.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const TOKEN_REGEX = /[A-Za-z0-9_
|
|
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) || /[_
|
|
28
|
+
const hasUpperOrSymbol = /[A-Z]/.test(value) || /[_\-+/=]/.test(value);
|
|
27
29
|
const hasDigit = /\d/.test(value);
|
|
28
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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) {
|
package/detectors/openai.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
const OPENAI_REGEX = /sk-(?:proj-)?[A-Za-z0-9_-]{
|
|
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
|
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fixyoursecret",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "CLI tool to detect leaked secrets, frontend exposure, and generate safe fixes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"fixyoursecret": "bin/index.js",
|
|
@@ -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:
|
|
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
|
-
|
|
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(
|
|
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(
|
|
9
|
-
case "stripe-secret-key":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return formatResult(
|
|
13
|
-
|
|
14
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,91 @@ 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 = ["/test/", "/tests/", "/__tests__/", "/fixtures/", "/docs/", "/examples/", "/spec/"]
|
|
78
|
+
.some((segment) => lowerPath.includes(segment));
|
|
59
79
|
|
|
60
80
|
const builtinHints = ["example", "dummy", "fake", "sample", "not_secret", "replace_in_runtime_only", "docs_only"];
|
|
61
81
|
const allHints = [...builtinHints, ...hints.map((h) => String(h).toLowerCase())];
|
|
62
82
|
|
|
63
83
|
if (allHints.some((hint) => lowerSnippet.includes(hint))) return true;
|
|
84
|
+
if (isNonProdPath && isLikelyPlaceholderToken(value, lowerSnippet)) return true;
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
isNonProdPath &&
|
|
88
|
+
/(?:ghp_|github_pat_|xox[bpars]-|sk_live_|sk-)/.test(value) &&
|
|
89
|
+
/(?:x{6,}|y{6,}|z{6,}|123456|example|dummy|placeholder|mock|test[_-]?key|your[_-]?key)/i.test(lowerSnippet + " " + value.toLowerCase())
|
|
90
|
+
) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
64
93
|
|
|
65
94
|
if (
|
|
66
95
|
match.rule === "generic-high-entropy" &&
|
|
67
|
-
[
|
|
96
|
+
[
|
|
97
|
+
"/test/",
|
|
98
|
+
"/tests/",
|
|
99
|
+
"/__tests__/",
|
|
100
|
+
"/fixtures/",
|
|
101
|
+
"/docs/",
|
|
102
|
+
"/spec/",
|
|
103
|
+
"/bench/",
|
|
104
|
+
"/benchmark/",
|
|
105
|
+
"/examples/",
|
|
106
|
+
"/migrations/",
|
|
107
|
+
"/generated/",
|
|
108
|
+
"/api-client/",
|
|
109
|
+
"/fonts/",
|
|
110
|
+
"/vendor/"
|
|
111
|
+
].some((segment) => lowerPath.includes(segment))
|
|
68
112
|
) {
|
|
69
113
|
return true;
|
|
70
114
|
}
|
|
71
115
|
|
|
116
|
+
if (match.rule === "generic-high-entropy") {
|
|
117
|
+
const genericNoiseHints = [
|
|
118
|
+
"canvasrenderingcontext2d",
|
|
119
|
+
"axios parameter creator",
|
|
120
|
+
"sourcemappingurl=data:",
|
|
121
|
+
"base64,",
|
|
122
|
+
"images.unsplash.com",
|
|
123
|
+
".woff2",
|
|
124
|
+
"oauth2",
|
|
125
|
+
"requestparameters",
|
|
126
|
+
"data-cy=",
|
|
127
|
+
"uuid",
|
|
128
|
+
"v1alpha1",
|
|
129
|
+
"openapi",
|
|
130
|
+
"migration",
|
|
131
|
+
"model:",
|
|
132
|
+
"anthropiccontext1m",
|
|
133
|
+
"bigint64arraybytes_per_element",
|
|
134
|
+
"claude-sonnet",
|
|
135
|
+
"gemini-"
|
|
136
|
+
];
|
|
137
|
+
if (genericNoiseHints.some((hint) => lowerSnippet.includes(hint))) return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
72
140
|
return false;
|
|
73
141
|
}
|
|
142
|
+
|
|
143
|
+
function isLikelyPlaceholderToken(value, lowerSnippet = "") {
|
|
144
|
+
const lowerValue = String(value || "").toLowerCase();
|
|
145
|
+
const joined = `${lowerValue} ${lowerSnippet}`;
|
|
146
|
+
if (!lowerValue) return false;
|
|
147
|
+
|
|
148
|
+
if (/(?:x{8,}|0{8,}|12345678)/.test(lowerValue)) return true;
|
|
149
|
+
if (/(?:example|dummy|placeholder|mock|redacted|sanitized|masked)/.test(joined)) return true;
|
|
150
|
+
if (/(?:ghp_x+|xox[bpars]-x+|sk_live_x+|sk-[a-z]*x{8,})/.test(lowerValue)) return true;
|
|
151
|
+
if (/(?:your[_-]?token|your[_-]?key|replace[_-]?me|insert[_-]?key)/.test(joined)) return true;
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function hasDiversityScore(value, minClasses) {
|
|
156
|
+
const classes = [
|
|
157
|
+
/[a-z]/.test(value),
|
|
158
|
+
/[A-Z]/.test(value),
|
|
159
|
+
/\d/.test(value),
|
|
160
|
+
/[_-]/.test(value),
|
|
161
|
+
].filter(Boolean).length;
|
|
162
|
+
return classes >= minClasses;
|
|
163
|
+
}
|