xploitscan-shared-rules 1.13.0 → 1.14.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/dist/index.cjs +287 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +285 -84
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -97,6 +97,7 @@ __export(index_exports, {
|
|
|
97
97
|
hardcodedAnthropicKey: () => hardcodedAnthropicKey,
|
|
98
98
|
hardcodedCloudflareToken: () => hardcodedCloudflareToken,
|
|
99
99
|
hardcodedCohereKey: () => hardcodedCohereKey,
|
|
100
|
+
hardcodedCreditCard: () => hardcodedCreditCard,
|
|
100
101
|
hardcodedDatadogKey: () => hardcodedDatadogKey,
|
|
101
102
|
hardcodedDiscordToken: () => hardcodedDiscordToken,
|
|
102
103
|
hardcodedEncryptionKey: () => hardcodedEncryptionKey,
|
|
@@ -126,6 +127,7 @@ __export(index_exports, {
|
|
|
126
127
|
hardcodedRailwayToken: () => hardcodedRailwayToken,
|
|
127
128
|
hardcodedReplicateKey: () => hardcodedReplicateKey,
|
|
128
129
|
hardcodedResendKey: () => hardcodedResendKey,
|
|
130
|
+
hardcodedSSN: () => hardcodedSSN,
|
|
129
131
|
hardcodedSecrets: () => hardcodedSecrets,
|
|
130
132
|
hardcodedSendGridKey: () => hardcodedSendGridKey,
|
|
131
133
|
hardcodedSentryAuthToken: () => hardcodedSentryAuthToken,
|
|
@@ -264,15 +266,50 @@ __export(index_exports, {
|
|
|
264
266
|
module.exports = __toCommonJS(index_exports);
|
|
265
267
|
|
|
266
268
|
// src/snippet.ts
|
|
267
|
-
|
|
269
|
+
var cacheContent = null;
|
|
270
|
+
var cacheLines = [];
|
|
271
|
+
var cacheOffsets = [];
|
|
272
|
+
function lineIndex(content) {
|
|
273
|
+
if (content === cacheContent) return { lines: cacheLines, offsets: cacheOffsets };
|
|
268
274
|
const lines = content.split("\n");
|
|
275
|
+
const offsets = new Array(lines.length);
|
|
276
|
+
let off = 0;
|
|
277
|
+
for (let i = 0; i < lines.length; i++) {
|
|
278
|
+
offsets[i] = off;
|
|
279
|
+
off += lines[i].length + 1;
|
|
280
|
+
}
|
|
281
|
+
cacheContent = content;
|
|
282
|
+
cacheLines = lines;
|
|
283
|
+
cacheOffsets = offsets;
|
|
284
|
+
return { lines, offsets };
|
|
285
|
+
}
|
|
286
|
+
function lineNumberAt(content, index) {
|
|
287
|
+
const { offsets } = lineIndex(content);
|
|
288
|
+
let lo = 0;
|
|
289
|
+
let hi = offsets.length - 1;
|
|
290
|
+
let ans = 0;
|
|
291
|
+
while (lo <= hi) {
|
|
292
|
+
const mid = lo + hi >> 1;
|
|
293
|
+
if (offsets[mid] <= index) {
|
|
294
|
+
ans = mid;
|
|
295
|
+
lo = mid + 1;
|
|
296
|
+
} else {
|
|
297
|
+
hi = mid - 1;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return ans + 1;
|
|
301
|
+
}
|
|
302
|
+
function getSnippet(content, line, contextLines = 2) {
|
|
303
|
+
const { lines } = lineIndex(content);
|
|
269
304
|
const start = Math.max(0, line - 1 - contextLines);
|
|
270
305
|
const end = Math.min(lines.length, line + contextLines);
|
|
271
|
-
|
|
272
|
-
|
|
306
|
+
const out = [];
|
|
307
|
+
for (let i = start; i < end; i++) {
|
|
308
|
+
const lineNum = i + 1;
|
|
273
309
|
const marker = lineNum === line ? ">" : " ";
|
|
274
|
-
|
|
275
|
-
}
|
|
310
|
+
out.push(`${marker} ${lineNum.toString().padStart(4)} | ${lines[i]}`);
|
|
311
|
+
}
|
|
312
|
+
return out.join("\n");
|
|
276
313
|
}
|
|
277
314
|
|
|
278
315
|
// src/rule-impacts.ts
|
|
@@ -486,7 +523,9 @@ var RULE_IMPACTS = {
|
|
|
486
523
|
VC207: "Model output is attacker-influenceable via prompt injection. Feeding it into eval, new Function, a shell command, a raw SQL string, or a filesystem path turns a crafted or hallucinated response into remote code execution, command injection, SQL injection, or path traversal \u2014 your most dangerous sinks, driven by untrusted text.",
|
|
487
524
|
VC208: "Interpolating a secret into a prompt ships your API key, token, or password to a third-party model provider, where it persists in their request logs and training-eligible data. A credential that leaves your infrastructure in prompt text should be considered compromised and rotated.",
|
|
488
525
|
VC209: "Webhooks are delivered at-least-once. Without de-duplicating on the event id, a retried or replayed delivery re-runs the side effect \u2014 a customer is charged twice, a record is duplicated, or an entitlement is granted again. Stripe and Svix both retry on any non-2xx, so this fires in normal operation, not just under attack.",
|
|
489
|
-
VC210: "If your auth middleware skips /api, those routes run with no gate unless each one re-checks auth itself. It is the most common way a Next.js app ends up with publicly callable API routes that everyone assumed the middleware was protecting."
|
|
526
|
+
VC210: "If your auth middleware skips /api, those routes run with no gate unless each one re-checks auth itself. It is the most common way a Next.js app ends up with publicly callable API routes that everyone assumed the middleware was protecting.",
|
|
527
|
+
VC211: "A real credit card number in source is cardholder data sitting in git history, CI logs, and every backup \u2014 a direct PCI-DSS violation. Anyone with repo access can read it, and it cannot be un-leaked once committed; the card must be treated as compromised.",
|
|
528
|
+
VC212: "A hardcoded Social Security Number is regulated PII permanently embedded in your git history and backups. It exposes a real person to identity theft, and its presence in source can trigger breach-notification and privacy-law obligations the moment the repo is accessed."
|
|
490
529
|
};
|
|
491
530
|
|
|
492
531
|
// src/exposure.ts
|
|
@@ -1080,7 +1119,7 @@ var SERVER_SIDE_PATH_RE = new RegExp(
|
|
|
1080
1119
|
].join("|"),
|
|
1081
1120
|
"i"
|
|
1082
1121
|
);
|
|
1083
|
-
var SERVER_SIDE_CONTENT_RE = /\b(?:req|request)\.(?:body|query|params|headers|cookies)\b|\b(?:app|router)\.(?:get|post|put|patch|delete|use|all)\s*\(|\bctx\.request\b|\bevent\.(?:body|queryStringParameters|pathParameters)\b|\(\s*req\s*,\s*res\b|(?:import|require)\b[^;\n]
|
|
1122
|
+
var SERVER_SIDE_CONTENT_RE = /\b(?:req|request)\.(?:body|query|params|headers|cookies)\b|\b(?:app|router)\.(?:get|post|put|patch|delete|use|all)\s*\(|\bctx\.request\b|\bevent\.(?:body|queryStringParameters|pathParameters)\b|\(\s*req\s*,\s*res\b|(?:import|require)\b[^;\n]{0,200}['"]express['"]|\bNextFunction\b/;
|
|
1084
1123
|
function isServerSideFile(filePath, content) {
|
|
1085
1124
|
if (isTestFile(filePath)) return false;
|
|
1086
1125
|
if (SERVER_SIDE_PATH_RE.test(filePath)) return true;
|
|
@@ -1127,13 +1166,23 @@ function filterSilenced(matches, content, ruleId) {
|
|
|
1127
1166
|
}
|
|
1128
1167
|
function findMatches(content, pattern, rule, filePath, fixTemplate) {
|
|
1129
1168
|
const matches = [];
|
|
1130
|
-
const lines = content.split("\n");
|
|
1131
1169
|
let m;
|
|
1132
1170
|
const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
1171
|
+
let scanned = 0;
|
|
1172
|
+
let lineNum = 1;
|
|
1173
|
+
let lastMatchIndex = -1;
|
|
1174
|
+
const MAX_MATCHES = 500;
|
|
1133
1175
|
while ((m = re.exec(content)) !== null) {
|
|
1176
|
+
if (m.index === lastMatchIndex && m[0].length === 0) {
|
|
1177
|
+
re.lastIndex++;
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
lastMatchIndex = m.index;
|
|
1134
1181
|
if (isCommentLine(content, m.index)) continue;
|
|
1135
1182
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
1136
|
-
|
|
1183
|
+
for (; scanned < m.index; scanned++) {
|
|
1184
|
+
if (content.charCodeAt(scanned) === 10) lineNum++;
|
|
1185
|
+
}
|
|
1137
1186
|
matches.push({
|
|
1138
1187
|
rule: rule.id,
|
|
1139
1188
|
title: rule.title,
|
|
@@ -1144,6 +1193,7 @@ function findMatches(content, pattern, rule, filePath, fixTemplate) {
|
|
|
1144
1193
|
snippet: getSnippet(content, lineNum),
|
|
1145
1194
|
fix: fixTemplate?.(m)
|
|
1146
1195
|
});
|
|
1196
|
+
if (matches.length >= MAX_MATCHES) break;
|
|
1147
1197
|
}
|
|
1148
1198
|
return matches;
|
|
1149
1199
|
}
|
|
@@ -1159,10 +1209,26 @@ function astMatch(content, filePath, line, rule, fix) {
|
|
|
1159
1209
|
fix
|
|
1160
1210
|
};
|
|
1161
1211
|
}
|
|
1212
|
+
var parseCacheContent = null;
|
|
1213
|
+
var parseCachePath = null;
|
|
1214
|
+
var parseCacheResult = null;
|
|
1215
|
+
var parseCacheValid = false;
|
|
1162
1216
|
function tryParse(content, filePath) {
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1217
|
+
if (parseCacheValid && content === parseCacheContent && filePath === parseCachePath) {
|
|
1218
|
+
return parseCacheResult;
|
|
1219
|
+
}
|
|
1220
|
+
let result = null;
|
|
1221
|
+
try {
|
|
1222
|
+
const parsed = parseFile(content, filePath);
|
|
1223
|
+
result = parsed ? { parsed, taint: buildTaintMap(parsed) } : null;
|
|
1224
|
+
} catch {
|
|
1225
|
+
result = null;
|
|
1226
|
+
}
|
|
1227
|
+
parseCacheResult = result;
|
|
1228
|
+
parseCacheContent = content;
|
|
1229
|
+
parseCachePath = filePath;
|
|
1230
|
+
parseCacheValid = true;
|
|
1231
|
+
return result;
|
|
1166
1232
|
}
|
|
1167
1233
|
var hardcodedSecrets = {
|
|
1168
1234
|
id: "VC001",
|
|
@@ -1530,7 +1596,7 @@ var xssVulnerability = {
|
|
|
1530
1596
|
const hasSanitizerBypass = /bypassSecurityTrust(?:Html|Script|Style|Url|ResourceUrl)\s*\(/.test(content);
|
|
1531
1597
|
if (/(?:sanitize|purify|escape|xss)/i.test(filePath)) return [];
|
|
1532
1598
|
if (!hasSanitizerBypass && /DOMPurify\.sanitize|sanitizeHtml|xss\(|escapeHtml/i.test(content)) return [];
|
|
1533
|
-
if (!hasSanitizerBypass && /(?:import|require)\s*\(
|
|
1599
|
+
if (!hasSanitizerBypass && /(?:import|require)\s*\(?[^\n]{0,200}(?:DOMPurify|dompurify|sanitize|sanitizer)/i.test(content)) return [];
|
|
1534
1600
|
const patterns = [
|
|
1535
1601
|
// React dangerouslySetInnerHTML
|
|
1536
1602
|
/dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:/g,
|
|
@@ -1559,9 +1625,9 @@ var xssVulnerability = {
|
|
|
1559
1625
|
() => "Sanitize user input before rendering as HTML. Use a library like DOMPurify: DOMPurify.sanitize(userInput). If this site is intentional (JSON-LD structured data, a const boot script, a sandboxed preview), add an inline `// VC007-OK: <reason>` comment above the line to silence."
|
|
1560
1626
|
);
|
|
1561
1627
|
for (const m of raw) {
|
|
1562
|
-
const lineText = allLines[m.line - 1] || "";
|
|
1628
|
+
const lineText = (allLines[m.line - 1] || "").slice(0, 4e3);
|
|
1563
1629
|
if (/\.innerHTML\s*=\s*['"]/.test(lineText) && !/\$\{/.test(lineText)) continue;
|
|
1564
|
-
if (/\.innerHTML\s*=\s*['"][^'"]
|
|
1630
|
+
if (/\.innerHTML\s*=\s*['"][^'"]{0,2000}['"]\s*$/.test(lineText)) continue;
|
|
1565
1631
|
if (/dangerouslySetInnerHTML/.test(lineText)) {
|
|
1566
1632
|
const windowText = allLines.slice(m.line - 1, m.line + 2).join("\n");
|
|
1567
1633
|
if (/JSON\.stringify/i.test(windowText)) continue;
|
|
@@ -1812,7 +1878,7 @@ var evalUsage = {
|
|
|
1812
1878
|
/new\s+Function\s*\(\s*(?!["'`])/g
|
|
1813
1879
|
];
|
|
1814
1880
|
const hasEvalInString = /["'`]eval(?:-source-map|["'`])/i.test(content);
|
|
1815
|
-
if (hasEvalInString && !/\beval\s*\([^)]
|
|
1881
|
+
if (hasEvalInString && !/\beval\s*\([^)]{0,500}(?:req\.|body\.|input|params|user|data)/i.test(content)) return [];
|
|
1816
1882
|
const matches = [];
|
|
1817
1883
|
for (const p of patterns) {
|
|
1818
1884
|
const rawMatches = findMatches(
|
|
@@ -1853,7 +1919,7 @@ var unvalidatedRedirect = {
|
|
|
1853
1919
|
const isPureLiteral = /^["'`][^"'`]*["'`]$/.test(value);
|
|
1854
1920
|
if (!hasInterpolation && isPureLiteral) continue;
|
|
1855
1921
|
if (isInlineSilenced(content, m.index, "VC016")) continue;
|
|
1856
|
-
const line = content
|
|
1922
|
+
const line = lineNumberAt(content, m.index);
|
|
1857
1923
|
matches.push({
|
|
1858
1924
|
rule: "VC016",
|
|
1859
1925
|
title: unvalidatedRedirect.title,
|
|
@@ -2076,11 +2142,11 @@ var prototypePollution = {
|
|
|
2076
2142
|
];
|
|
2077
2143
|
const hasValidation = /schema|validate|sanitize|whitelist|allowedKeys|pick\(|Object\.freeze|zod|yup|joi|ajv/i.test(content);
|
|
2078
2144
|
if (!hasValidation) {
|
|
2079
|
-
const hasUnsafeMerge = /Object\.assign\s*\([^)]
|
|
2145
|
+
const hasUnsafeMerge = /Object\.assign\s*\([^)]{0,500}JSON\.parse|\.\.\.JSON\.parse|\{[^\n]{0,300}\.\.\.(?:stored|saved|cached|parsed|data)/i.test(content);
|
|
2080
2146
|
if (hasUnsafeMerge) {
|
|
2081
2147
|
matches.push(...findMatches(
|
|
2082
2148
|
content,
|
|
2083
|
-
/Object\.assign\s*\([^)]
|
|
2149
|
+
/Object\.assign\s*\([^)]{0,500}JSON\.parse|\.\.\.JSON\.parse/g,
|
|
2084
2150
|
prototypePollution,
|
|
2085
2151
|
filePath,
|
|
2086
2152
|
() => "Validate parsed data against an expected schema before merging into objects. Use Object.freeze(), a validation library (Zod, Yup), or manually check for __proto__ and constructor keys."
|
|
@@ -3626,7 +3692,7 @@ var dangerousInnerHTML = {
|
|
|
3626
3692
|
if (/^[A-Z][A-Z0-9_]+$/.test(value)) continue;
|
|
3627
3693
|
if (/^["'`][^$]*["'`]$/.test(value)) continue;
|
|
3628
3694
|
if (/JSON\.stringify/i.test(value)) continue;
|
|
3629
|
-
const lineNum = content
|
|
3695
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
3630
3696
|
findings.push({
|
|
3631
3697
|
rule: "VC063",
|
|
3632
3698
|
title: dangerousInnerHTML.title,
|
|
@@ -4368,7 +4434,7 @@ var missingSRI = {
|
|
|
4368
4434
|
const re = new RegExp(scriptPattern.source, scriptPattern.flags);
|
|
4369
4435
|
while ((m = re.exec(content)) !== null) {
|
|
4370
4436
|
if (!m[0].includes("integrity")) {
|
|
4371
|
-
const lineNum = content
|
|
4437
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
4372
4438
|
matches.push({
|
|
4373
4439
|
rule: "VC084",
|
|
4374
4440
|
title: missingSRI.title,
|
|
@@ -5000,8 +5066,13 @@ var complianceMap = {
|
|
|
5000
5066
|
// VC209–VC210: advisory heuristics
|
|
5001
5067
|
VC209: { owasp: "A04:2021", cwe: "CWE-799" },
|
|
5002
5068
|
// webhook missing idempotency
|
|
5003
|
-
VC210: { owasp: "A01:2021", cwe: "CWE-862" }
|
|
5069
|
+
VC210: { owasp: "A01:2021", cwe: "CWE-862" },
|
|
5004
5070
|
// middleware matcher excludes /api
|
|
5071
|
+
// VC211–VC212: hardcoded sensitive personal data (PII) in source
|
|
5072
|
+
VC211: { owasp: "A02:2021", cwe: "CWE-540" },
|
|
5073
|
+
// hardcoded credit card number
|
|
5074
|
+
VC212: { owasp: "A02:2021", cwe: "CWE-540" }
|
|
5075
|
+
// hardcoded US SSN
|
|
5005
5076
|
};
|
|
5006
5077
|
var consoleLogProduction = {
|
|
5007
5078
|
id: "VC097",
|
|
@@ -5239,7 +5310,7 @@ var s3BucketNoEncryption = {
|
|
|
5239
5310
|
const blockEnd = Math.min(m.index + 2e3, content.length);
|
|
5240
5311
|
const blockContent = content.substring(m.index, blockEnd);
|
|
5241
5312
|
if (!/server_side_encryption/.test(blockContent)) {
|
|
5242
|
-
const lineNum = content
|
|
5313
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5243
5314
|
matches.push({
|
|
5244
5315
|
rule: "VC107",
|
|
5245
5316
|
title: s3BucketNoEncryption.title,
|
|
@@ -5270,7 +5341,7 @@ var securityGroupAllInbound = {
|
|
|
5270
5341
|
while ((m = ingressPattern.exec(content)) !== null) {
|
|
5271
5342
|
if (isCommentLine(content, m.index)) continue;
|
|
5272
5343
|
if (/from_port\s*=\s*0/.test(m[0]) || !/from_port/.test(m[0])) {
|
|
5273
|
-
const lineNum = content
|
|
5344
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5274
5345
|
matches.push({
|
|
5275
5346
|
rule: "VC108",
|
|
5276
5347
|
title: securityGroupAllInbound.title,
|
|
@@ -5357,7 +5428,7 @@ var lambdaWithoutVPC = {
|
|
|
5357
5428
|
const blockEnd = Math.min(m.index + 2e3, content.length);
|
|
5358
5429
|
const blockContent = content.substring(m.index, blockEnd);
|
|
5359
5430
|
if (!/vpc_config\s*\{/.test(blockContent)) {
|
|
5360
|
-
const lineNum = content
|
|
5431
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5361
5432
|
matches.push({
|
|
5362
5433
|
rule: "VC111",
|
|
5363
5434
|
title: lambdaWithoutVPC.title,
|
|
@@ -5387,7 +5458,7 @@ var dockerLatestTag = {
|
|
|
5387
5458
|
while ((m = fromPattern.exec(content)) !== null) {
|
|
5388
5459
|
const image = m[1];
|
|
5389
5460
|
if (image.endsWith(":latest") || !image.includes(":") && !image.startsWith("$")) {
|
|
5390
|
-
const lineNum = content
|
|
5461
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5391
5462
|
matches.push({
|
|
5392
5463
|
rule: "VC112",
|
|
5393
5464
|
title: dockerLatestTag.title,
|
|
@@ -5515,17 +5586,26 @@ var pathTraversal = {
|
|
|
5515
5586
|
const findings = [];
|
|
5516
5587
|
const patterns = [
|
|
5517
5588
|
/(?:readFile|readFileSync|createReadStream|writeFile|writeFileSync|appendFile|unlink|unlinkSync|access|stat|statSync|existsSync)\s*\(\s*(?:req\.|request\.|ctx\.|params\.|query\.)/gi,
|
|
5518
|
-
/(?:path\.join|path\.resolve)\s*\([^)]
|
|
5519
|
-
/(?:res\.sendFile|res\.download)\s*\([^)]
|
|
5589
|
+
/(?:path\.join|path\.resolve)\s*\([^)]{0,500}(?:req\.|request\.|ctx\.|params\.|query\.)[^)]{0,500}\)/gi,
|
|
5590
|
+
/(?:res\.sendFile|res\.download)\s*\([^)]{0,500}(?:req\.|request\.)/gi,
|
|
5520
5591
|
/open\s*\(\s*(?:request\.|params\[|args\.)/gi
|
|
5521
5592
|
];
|
|
5593
|
+
const MAX_MATCHES = 500;
|
|
5522
5594
|
for (const pat of patterns) {
|
|
5595
|
+
if (findings.length >= MAX_MATCHES) break;
|
|
5523
5596
|
let m;
|
|
5597
|
+
let lastIdx = -1;
|
|
5524
5598
|
while ((m = pat.exec(content)) !== null) {
|
|
5599
|
+
if (findings.length >= MAX_MATCHES) break;
|
|
5600
|
+
if (m.index === lastIdx && m[0].length === 0) {
|
|
5601
|
+
pat.lastIndex++;
|
|
5602
|
+
continue;
|
|
5603
|
+
}
|
|
5604
|
+
lastIdx = m.index;
|
|
5525
5605
|
if (isCommentLine(content, m.index)) continue;
|
|
5526
5606
|
const surrounding = content.substring(Math.max(0, m.index - 200), m.index + 200);
|
|
5527
5607
|
if (/path\.normalize|sanitize|\.replace\(\s*['"]\.\.|\.startsWith\s*\(|realpath/i.test(surrounding)) continue;
|
|
5528
|
-
const lineNum = content
|
|
5608
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5529
5609
|
findings.push({
|
|
5530
5610
|
rule: "VC117",
|
|
5531
5611
|
title: pathTraversal.title,
|
|
@@ -5556,7 +5636,7 @@ var piiInLogs = {
|
|
|
5556
5636
|
while ((m = logPattern.exec(content)) !== null) {
|
|
5557
5637
|
if (isCommentLine(content, m.index)) continue;
|
|
5558
5638
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
5559
|
-
const lineNum = content
|
|
5639
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5560
5640
|
findings.push({
|
|
5561
5641
|
rule: "VC118",
|
|
5562
5642
|
title: piiInLogs.title,
|
|
@@ -5594,7 +5674,7 @@ var hardcodedOAuthSecret = {
|
|
|
5594
5674
|
while ((m = pat.exec(content)) !== null) {
|
|
5595
5675
|
if (isCommentLine(content, m.index)) continue;
|
|
5596
5676
|
if (/process\.env|os\.environ|ENV\[|getenv|\$\{|<your|CHANGE_ME|xxx|placeholder/i.test(m[0])) continue;
|
|
5597
|
-
const lineNum = content
|
|
5677
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5598
5678
|
findings.push({
|
|
5599
5679
|
rule: "VC119",
|
|
5600
5680
|
title: hardcodedOAuthSecret.title,
|
|
@@ -5626,7 +5706,7 @@ var missingOAuthState = {
|
|
|
5626
5706
|
if (isCommentLine(content, m.index)) continue;
|
|
5627
5707
|
const surrounding = content.substring(m.index, Math.min(content.length, m.index + 500));
|
|
5628
5708
|
if (/state\s*[=:]/i.test(surrounding)) continue;
|
|
5629
|
-
const lineNum = content
|
|
5709
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5630
5710
|
findings.push({
|
|
5631
5711
|
rule: "VC120",
|
|
5632
5712
|
title: missingOAuthState.title,
|
|
@@ -5654,7 +5734,7 @@ var unpinnedGitHubAction = {
|
|
|
5654
5734
|
let m;
|
|
5655
5735
|
while ((m = usesPattern.exec(content)) !== null) {
|
|
5656
5736
|
if (isCommentLine(content, m.index)) continue;
|
|
5657
|
-
const lineNum = content
|
|
5737
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5658
5738
|
findings.push({
|
|
5659
5739
|
rule: "VC121",
|
|
5660
5740
|
title: unpinnedGitHubAction.title,
|
|
@@ -5688,7 +5768,7 @@ var deprecatedTLS = {
|
|
|
5688
5768
|
let m;
|
|
5689
5769
|
while ((m = pat.exec(content)) !== null) {
|
|
5690
5770
|
if (isCommentLine(content, m.index)) continue;
|
|
5691
|
-
const lineNum = content
|
|
5771
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5692
5772
|
findings.push({
|
|
5693
5773
|
rule: "VC122",
|
|
5694
5774
|
title: deprecatedTLS.title,
|
|
@@ -5724,7 +5804,7 @@ var weakRSAKeySize = {
|
|
|
5724
5804
|
let m;
|
|
5725
5805
|
while ((m = pat.exec(content)) !== null) {
|
|
5726
5806
|
if (isCommentLine(content, m.index)) continue;
|
|
5727
|
-
const lineNum = content
|
|
5807
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5728
5808
|
findings.push({
|
|
5729
5809
|
rule: "VC123",
|
|
5730
5810
|
title: weakRSAKeySize.title,
|
|
@@ -5760,7 +5840,7 @@ var ecbModeEncryption = {
|
|
|
5760
5840
|
let m;
|
|
5761
5841
|
while ((m = pat.exec(content)) !== null) {
|
|
5762
5842
|
if (isCommentLine(content, m.index)) continue;
|
|
5763
|
-
const lineNum = content
|
|
5843
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5764
5844
|
findings.push({
|
|
5765
5845
|
rule: "VC124",
|
|
5766
5846
|
title: ecbModeEncryption.title,
|
|
@@ -5791,7 +5871,7 @@ var insecurePasswordReset = {
|
|
|
5791
5871
|
let m;
|
|
5792
5872
|
while ((m = weakTokens.exec(content)) !== null) {
|
|
5793
5873
|
if (isCommentLine(content, m.index)) continue;
|
|
5794
|
-
const lineNum = content
|
|
5874
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5795
5875
|
findings.push({
|
|
5796
5876
|
rule: "VC125",
|
|
5797
5877
|
title: insecurePasswordReset.title,
|
|
@@ -5808,7 +5888,7 @@ var insecurePasswordReset = {
|
|
|
5808
5888
|
if (isCommentLine(content, m.index)) continue;
|
|
5809
5889
|
const surrounding = content.substring(Math.max(0, m.index - 500), m.index);
|
|
5810
5890
|
if (!/(?:reset|forgot).*password/i.test(surrounding)) continue;
|
|
5811
|
-
const lineNum = content
|
|
5891
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5812
5892
|
findings.push({
|
|
5813
5893
|
rule: "VC125",
|
|
5814
5894
|
title: insecurePasswordReset.title,
|
|
@@ -5869,7 +5949,7 @@ var insecureHTTPMethods = {
|
|
|
5869
5949
|
if (isCommentLine(content, m.index)) continue;
|
|
5870
5950
|
const handlerBlock = content.substring(m.index, Math.min(content.length, m.index + 500));
|
|
5871
5951
|
if (/auth|authenticate|authorize|requireAuth|isAuthenticated|protect|guard|middleware|session|jwt|verify|clerk|getAuth/i.test(handlerBlock)) continue;
|
|
5872
|
-
const lineNum = content
|
|
5952
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5873
5953
|
findings.push({
|
|
5874
5954
|
rule: "VC127",
|
|
5875
5955
|
title: insecureHTTPMethods.title,
|
|
@@ -5903,7 +5983,7 @@ var httpRequestSmuggling = {
|
|
|
5903
5983
|
let m;
|
|
5904
5984
|
while ((m = pat.exec(content)) !== null) {
|
|
5905
5985
|
if (isCommentLine(content, m.index)) continue;
|
|
5906
|
-
const lineNum = content
|
|
5986
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5907
5987
|
findings.push({
|
|
5908
5988
|
rule: "VC128",
|
|
5909
5989
|
title: httpRequestSmuggling.title,
|
|
@@ -5937,7 +6017,7 @@ var unencryptedPII = {
|
|
|
5937
6017
|
let m;
|
|
5938
6018
|
while ((m = pat.exec(content)) !== null) {
|
|
5939
6019
|
if (isCommentLine(content, m.index)) continue;
|
|
5940
|
-
const lineNum = content
|
|
6020
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5941
6021
|
findings.push({
|
|
5942
6022
|
rule: "VC129",
|
|
5943
6023
|
title: unencryptedPII.title,
|
|
@@ -5969,7 +6049,7 @@ var missingAuthRateLimit = {
|
|
|
5969
6049
|
if (isCommentLine(content, m.index)) continue;
|
|
5970
6050
|
const surrounding = content.substring(Math.max(0, m.index - 300), Math.min(content.length, m.index + 300));
|
|
5971
6051
|
if (/rateLimit|rateLimiter|throttle|slowDown|express-rate-limit|rate_limit|RateLimiter|limiter/i.test(surrounding)) continue;
|
|
5972
|
-
const lineNum = content
|
|
6052
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5973
6053
|
findings.push({
|
|
5974
6054
|
rule: "VC130",
|
|
5975
6055
|
title: missingAuthRateLimit.title,
|
|
@@ -6007,7 +6087,7 @@ var vulnerableDependencies = {
|
|
|
6007
6087
|
for (const [pat, message] of vulnerablePackages) {
|
|
6008
6088
|
let m;
|
|
6009
6089
|
while ((m = pat.exec(content)) !== null) {
|
|
6010
|
-
const lineNum = content
|
|
6090
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6011
6091
|
findings.push({
|
|
6012
6092
|
rule: "VC131",
|
|
6013
6093
|
title: vulnerableDependencies.title,
|
|
@@ -6039,7 +6119,7 @@ function secretRuleCheck(content, filePath, pattern, ruleId, title, severity, fi
|
|
|
6039
6119
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
6040
6120
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
6041
6121
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
6042
|
-
const lineNum = content
|
|
6122
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6043
6123
|
findings.push({
|
|
6044
6124
|
rule: ruleId,
|
|
6045
6125
|
title,
|
|
@@ -6140,7 +6220,7 @@ var hardcodedGCPServiceAccount = {
|
|
|
6140
6220
|
const findings = [];
|
|
6141
6221
|
const m = content.match(/"type"\s*:\s*"service_account"/);
|
|
6142
6222
|
if (m && m.index !== void 0) {
|
|
6143
|
-
const lineNum = content
|
|
6223
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6144
6224
|
findings.push({
|
|
6145
6225
|
rule: "VC136",
|
|
6146
6226
|
title: this.title,
|
|
@@ -6246,7 +6326,7 @@ var hardcodedDatadogKey = {
|
|
|
6246
6326
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
6247
6327
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
6248
6328
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
6249
|
-
const lineNum = content
|
|
6329
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6250
6330
|
findings.push({
|
|
6251
6331
|
rule: "VC141",
|
|
6252
6332
|
title: this.title,
|
|
@@ -6280,7 +6360,7 @@ var hardcodedVercelToken = {
|
|
|
6280
6360
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
6281
6361
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
6282
6362
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
6283
|
-
const lineNum = content
|
|
6363
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6284
6364
|
findings.push({
|
|
6285
6365
|
rule: "VC142",
|
|
6286
6366
|
title: this.title,
|
|
@@ -6314,7 +6394,7 @@ var hardcodedSupabaseServiceRole = {
|
|
|
6314
6394
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
6315
6395
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
6316
6396
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
6317
|
-
const lineNum = content
|
|
6397
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6318
6398
|
findings.push({
|
|
6319
6399
|
rule: "VC143",
|
|
6320
6400
|
title: this.title,
|
|
@@ -6378,7 +6458,7 @@ function contextSecretRuleCheck(content, filePath, pattern, ruleId, title, sever
|
|
|
6378
6458
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
6379
6459
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
6380
6460
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
6381
|
-
const lineNum = content
|
|
6461
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6382
6462
|
findings.push({
|
|
6383
6463
|
rule: ruleId,
|
|
6384
6464
|
title,
|
|
@@ -6858,7 +6938,7 @@ var ghaPullRequestTargetCheckout = {
|
|
|
6858
6938
|
const checkoutPattern = /uses\s*:\s*actions\/checkout@[^\n]*[\s\S]{0,400}?ref\s*:\s*\$\{\{\s*github\.event\.pull_request\.head\.(?:ref|sha)\s*\}\}/g;
|
|
6859
6939
|
let m;
|
|
6860
6940
|
while ((m = checkoutPattern.exec(content)) !== null) {
|
|
6861
|
-
const lineNum = content
|
|
6941
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6862
6942
|
findings.push({
|
|
6863
6943
|
rule: "VC184",
|
|
6864
6944
|
title: ghaPullRequestTargetCheckout.title,
|
|
@@ -6942,7 +7022,7 @@ var ghaThirdPartyActionWithSecrets = {
|
|
|
6942
7022
|
const pattern = /uses\s*:\s*(?!actions\/|github\/|aws-actions\/|azure\/|google-github-actions\/|hashicorp\/)([\w\-]+\/[\w\-./]+)@[^\n]*\n[\s\S]{0,800}?with\s*:[\s\S]{0,800}?\$\{\{\s*secrets\./g;
|
|
6943
7023
|
let m;
|
|
6944
7024
|
while ((m = pattern.exec(content)) !== null) {
|
|
6945
|
-
const lineNum = content
|
|
7025
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6946
7026
|
findings.push({
|
|
6947
7027
|
rule: "VC187",
|
|
6948
7028
|
title: ghaThirdPartyActionWithSecrets.title,
|
|
@@ -7107,7 +7187,7 @@ var pyRequestsVerifyFalse = {
|
|
|
7107
7187
|
let m;
|
|
7108
7188
|
while ((m = pattern.exec(content)) !== null) {
|
|
7109
7189
|
if (isCommentLine(content, m.index)) continue;
|
|
7110
|
-
const lineNum = content
|
|
7190
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7111
7191
|
findings.push({
|
|
7112
7192
|
rule: "VC191",
|
|
7113
7193
|
title: pyRequestsVerifyFalse.title,
|
|
@@ -7138,7 +7218,7 @@ var pyJinja2AutoescapeOff = {
|
|
|
7138
7218
|
let m;
|
|
7139
7219
|
while ((m = pattern.exec(content)) !== null) {
|
|
7140
7220
|
if (isCommentLine(content, m.index)) continue;
|
|
7141
|
-
const lineNum = content
|
|
7221
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7142
7222
|
findings.push({
|
|
7143
7223
|
rule: "VC192",
|
|
7144
7224
|
title: pyJinja2AutoescapeOff.title,
|
|
@@ -7167,7 +7247,7 @@ var pyTempfileMktemp = {
|
|
|
7167
7247
|
let m;
|
|
7168
7248
|
while ((m = pattern.exec(content)) !== null) {
|
|
7169
7249
|
if (isCommentLine(content, m.index)) continue;
|
|
7170
|
-
const lineNum = content
|
|
7250
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7171
7251
|
findings.push({
|
|
7172
7252
|
rule: "VC193",
|
|
7173
7253
|
title: pyTempfileMktemp.title,
|
|
@@ -7209,7 +7289,7 @@ var pyDjangoMarkSafe = {
|
|
|
7209
7289
|
let m;
|
|
7210
7290
|
while ((m = pattern.exec(content)) !== null) {
|
|
7211
7291
|
if (isCommentLine(content, m.index)) continue;
|
|
7212
|
-
const lineNum = content
|
|
7292
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7213
7293
|
if (seenLines.has(lineNum)) continue;
|
|
7214
7294
|
seenLines.add(lineNum);
|
|
7215
7295
|
findings.push({
|
|
@@ -7246,7 +7326,7 @@ var pyParamikoAutoAdd = {
|
|
|
7246
7326
|
let m;
|
|
7247
7327
|
while ((m = pattern.exec(content)) !== null) {
|
|
7248
7328
|
if (isCommentLine(content, m.index)) continue;
|
|
7249
|
-
const lineNum = content
|
|
7329
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7250
7330
|
if (seenLines.has(lineNum)) continue;
|
|
7251
7331
|
seenLines.add(lineNum);
|
|
7252
7332
|
findings.push({
|
|
@@ -7279,7 +7359,7 @@ var pyDjangoAllowedHostsWildcard = {
|
|
|
7279
7359
|
let m;
|
|
7280
7360
|
while ((m = pattern.exec(content)) !== null) {
|
|
7281
7361
|
if (isCommentLine(content, m.index)) continue;
|
|
7282
|
-
const lineNum = content
|
|
7362
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7283
7363
|
findings.push({
|
|
7284
7364
|
rule: "VC196",
|
|
7285
7365
|
title: pyDjangoAllowedHostsWildcard.title,
|
|
@@ -7317,7 +7397,7 @@ var pyJWTDecodeWeakConfig = {
|
|
|
7317
7397
|
if (isCommentLine(content, m.index)) continue;
|
|
7318
7398
|
const args = m[1];
|
|
7319
7399
|
if (/\balgorithms\s*=/.test(args)) continue;
|
|
7320
|
-
const lineNum = content
|
|
7400
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7321
7401
|
if (seenLines.has(lineNum)) continue;
|
|
7322
7402
|
seenLines.add(lineNum);
|
|
7323
7403
|
findings.push({
|
|
@@ -7368,7 +7448,7 @@ var llmPromptInjection = {
|
|
|
7368
7448
|
let m;
|
|
7369
7449
|
while ((m = pattern.exec(content)) !== null) {
|
|
7370
7450
|
if (isCommentLine(content, m.index)) continue;
|
|
7371
|
-
const lineNum = content
|
|
7451
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7372
7452
|
if (seenLines.has(lineNum)) continue;
|
|
7373
7453
|
seenLines.add(lineNum);
|
|
7374
7454
|
findings.push({
|
|
@@ -7407,7 +7487,7 @@ var llmSystemPromptInjection = {
|
|
|
7407
7487
|
let m;
|
|
7408
7488
|
while ((m = pattern.exec(content)) !== null) {
|
|
7409
7489
|
if (isCommentLine(content, m.index)) continue;
|
|
7410
|
-
const lineNum = content
|
|
7490
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7411
7491
|
findings.push({
|
|
7412
7492
|
rule: "VC199",
|
|
7413
7493
|
title: llmSystemPromptInjection.title,
|
|
@@ -7449,7 +7529,7 @@ var llmOutputAsHTML = {
|
|
|
7449
7529
|
let m;
|
|
7450
7530
|
while ((m = pattern.exec(content)) !== null) {
|
|
7451
7531
|
if (isCommentLine(content, m.index)) continue;
|
|
7452
|
-
const lineNum = content
|
|
7532
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7453
7533
|
if (seenLines.has(lineNum)) continue;
|
|
7454
7534
|
seenLines.add(lineNum);
|
|
7455
7535
|
findings.push({
|
|
@@ -7486,7 +7566,7 @@ var vectorStoreQueryNoUserFilter = {
|
|
|
7486
7566
|
if (/\b(?:user[_-]?id|userId|tenant[_-]?id|tenantId|org[_-]?id|orgId|owner[_-]?id|ownerId|account[_-]?id|customer[_-]?id|workspace[_-]?id)\b/i.test(args)) continue;
|
|
7487
7567
|
if (/\bnamespace\s*[:=]\s*[`"']?[^,)`"']*(?:user|tenant|org|owner|account|customer|workspace)/i.test(args)) continue;
|
|
7488
7568
|
if (/\bfilter\s*[:=][\s\S]*?(?:\buser|\btenant|\borg|\bowner|\baccount|\bcustomer|\bworkspace)/i.test(args)) continue;
|
|
7489
|
-
const lineNum = content
|
|
7569
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7490
7570
|
findings.push({
|
|
7491
7571
|
rule: "VC201",
|
|
7492
7572
|
title: vectorStoreQueryNoUserFilter.title,
|
|
@@ -7523,7 +7603,7 @@ var vectorStoreUpsertNoMetadata = {
|
|
|
7523
7603
|
if (/\bnamespace\s*[:=]\s*[`"']?[^,)`"']*(?:user|tenant|org)/i.test(args)) {
|
|
7524
7604
|
continue;
|
|
7525
7605
|
}
|
|
7526
|
-
const lineNum = content
|
|
7606
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7527
7607
|
findings.push({
|
|
7528
7608
|
rule: "VC202",
|
|
7529
7609
|
title: vectorStoreUpsertNoMetadata.title,
|
|
@@ -7577,7 +7657,7 @@ var llmCallNoMaxTokens = {
|
|
|
7577
7657
|
if (depth !== 0) continue;
|
|
7578
7658
|
const args = content.substring(openIdx + 1, i).replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "").replace(/#[^\n]*/g, "");
|
|
7579
7659
|
if (TOKEN_KW_RE.test(args)) continue;
|
|
7580
|
-
const lineNum = content
|
|
7660
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7581
7661
|
findings.push({
|
|
7582
7662
|
rule: "VC203",
|
|
7583
7663
|
title: llmCallNoMaxTokens.title,
|
|
@@ -7617,7 +7697,7 @@ var graphqlNoDepthLimit = {
|
|
|
7617
7697
|
let m;
|
|
7618
7698
|
while ((m = anchorRe.exec(content)) !== null) {
|
|
7619
7699
|
if (isCommentLine(content, m.index)) continue;
|
|
7620
|
-
const lineNum = content
|
|
7700
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7621
7701
|
findings.push({
|
|
7622
7702
|
rule: "VC204",
|
|
7623
7703
|
title: graphqlNoDepthLimit.title,
|
|
@@ -7651,7 +7731,7 @@ var graphqlNoComplexityLimit = {
|
|
|
7651
7731
|
let m;
|
|
7652
7732
|
while ((m = anchorRe.exec(content)) !== null) {
|
|
7653
7733
|
if (isCommentLine(content, m.index)) continue;
|
|
7654
|
-
const lineNum = content
|
|
7734
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7655
7735
|
findings.push({
|
|
7656
7736
|
rule: "VC205",
|
|
7657
7737
|
title: graphqlNoComplexityLimit.title,
|
|
@@ -7681,7 +7761,7 @@ var graphqlCSRFDisabled = {
|
|
|
7681
7761
|
let m;
|
|
7682
7762
|
while ((m = pattern.exec(content)) !== null) {
|
|
7683
7763
|
if (isCommentLine(content, m.index)) continue;
|
|
7684
|
-
const lineNum = content
|
|
7764
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7685
7765
|
findings.push({
|
|
7686
7766
|
rule: "VC206",
|
|
7687
7767
|
title: graphqlCSRFDisabled.title,
|
|
@@ -7777,7 +7857,7 @@ var webhookMissingIdempotency = {
|
|
|
7777
7857
|
if (/idempoten|event\.id|evt\.id|delivery_?id|alreadyProcessed|processedEvents|ON\s+CONFLICT|INSERT\s+OR\s+IGNORE|\bseen\b|\bprocessed\b/i.test(content)) return [];
|
|
7778
7858
|
const m = content.match(/constructEvent|new\s+Webhook\s*\(|verifyHeader/i);
|
|
7779
7859
|
if (!m || m.index === void 0) return [];
|
|
7780
|
-
const line = content
|
|
7860
|
+
const line = lineNumberAt(content, m.index);
|
|
7781
7861
|
return filterSilenced([{
|
|
7782
7862
|
rule: "VC209",
|
|
7783
7863
|
title: webhookMissingIdempotency.title,
|
|
@@ -7804,7 +7884,7 @@ var middlewareMatcherExcludesApi = {
|
|
|
7804
7884
|
if (!/\(\?!\s*[^)]*\bapi\b/.test(content)) return [];
|
|
7805
7885
|
const m = content.match(/matcher\s*:/);
|
|
7806
7886
|
if (!m || m.index === void 0) return [];
|
|
7807
|
-
const line = content
|
|
7887
|
+
const line = lineNumberAt(content, m.index);
|
|
7808
7888
|
return filterSilenced([{
|
|
7809
7889
|
rule: "VC210",
|
|
7810
7890
|
title: middlewareMatcherExcludesApi.title,
|
|
@@ -7817,6 +7897,113 @@ var middlewareMatcherExcludesApi = {
|
|
|
7817
7897
|
}], content, "VC210");
|
|
7818
7898
|
}
|
|
7819
7899
|
};
|
|
7900
|
+
function passesLuhn(digits) {
|
|
7901
|
+
let sum = 0;
|
|
7902
|
+
let alt = false;
|
|
7903
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
7904
|
+
let d = digits.charCodeAt(i) - 48;
|
|
7905
|
+
if (d < 0 || d > 9) return false;
|
|
7906
|
+
if (alt) {
|
|
7907
|
+
d *= 2;
|
|
7908
|
+
if (d > 9) d -= 9;
|
|
7909
|
+
}
|
|
7910
|
+
sum += d;
|
|
7911
|
+
alt = !alt;
|
|
7912
|
+
}
|
|
7913
|
+
return sum % 10 === 0;
|
|
7914
|
+
}
|
|
7915
|
+
function looksLikeIssuerCard(d) {
|
|
7916
|
+
if (/^4/.test(d) && (d.length === 13 || d.length === 16 || d.length === 19)) return true;
|
|
7917
|
+
if (/^5[1-5]/.test(d) && d.length === 16) return true;
|
|
7918
|
+
if (/^2(2[2-9]|[3-6]\d|7[01]|720)/.test(d) && d.length === 16) return true;
|
|
7919
|
+
if (/^3[47]/.test(d) && d.length === 15) return true;
|
|
7920
|
+
if (/^(6011|65|64[4-9])/.test(d) && d.length === 16) return true;
|
|
7921
|
+
return false;
|
|
7922
|
+
}
|
|
7923
|
+
var TEST_CARD_NUMBERS = /* @__PURE__ */ new Set([
|
|
7924
|
+
"4242424242424242",
|
|
7925
|
+
"4111111111111111",
|
|
7926
|
+
"4012888888881881",
|
|
7927
|
+
"4000056655665556",
|
|
7928
|
+
"4000000000000002",
|
|
7929
|
+
"4000000000009995",
|
|
7930
|
+
"5555555555554444",
|
|
7931
|
+
"5200828282828210",
|
|
7932
|
+
"5105105105105100",
|
|
7933
|
+
"2223003122003222",
|
|
7934
|
+
"378282246310005",
|
|
7935
|
+
"371449635398431",
|
|
7936
|
+
"6011111111111117",
|
|
7937
|
+
"6011000990139424",
|
|
7938
|
+
"3056930009020004",
|
|
7939
|
+
"38520000023237"
|
|
7940
|
+
]);
|
|
7941
|
+
var hardcodedCreditCard = {
|
|
7942
|
+
id: "VC211",
|
|
7943
|
+
title: "Hardcoded credit card number",
|
|
7944
|
+
severity: "high",
|
|
7945
|
+
category: "Information Leakage",
|
|
7946
|
+
description: "A literal credit card number (Luhn-valid, with a real issuer prefix, and not a known network test card) appears in source. Cardholder data does not belong in code \u2014 it lands in git history, logs, and backups, and is a PCI-DSS violation. Use your processor's published test cards for tests, and tokenize real cards.",
|
|
7947
|
+
check(content, filePath) {
|
|
7948
|
+
if (isTestFile(filePath)) return [];
|
|
7949
|
+
const findings = [];
|
|
7950
|
+
const pattern = /(?<![\d.])\d[\d -]{11,21}\d(?![\d.])/g;
|
|
7951
|
+
let m;
|
|
7952
|
+
while ((m = pattern.exec(content)) !== null) {
|
|
7953
|
+
const digits = m[0].replace(/[ -]/g, "");
|
|
7954
|
+
if (digits.length < 13 || digits.length > 19) continue;
|
|
7955
|
+
if (TEST_CARD_NUMBERS.has(digits)) continue;
|
|
7956
|
+
if (!looksLikeIssuerCard(digits)) continue;
|
|
7957
|
+
if (!passesLuhn(digits)) continue;
|
|
7958
|
+
if (isCommentLine(content, m.index)) continue;
|
|
7959
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7960
|
+
findings.push({
|
|
7961
|
+
rule: "VC211",
|
|
7962
|
+
title: hardcodedCreditCard.title,
|
|
7963
|
+
severity: "high",
|
|
7964
|
+
category: "Information Leakage",
|
|
7965
|
+
file: filePath,
|
|
7966
|
+
line: lineNum,
|
|
7967
|
+
snippet: getSnippet(content, lineNum),
|
|
7968
|
+
fix: "Never store a real card number in source. Tokenize through your payment processor (Stripe, Braintree) and keep only the returned token. For tests, use the processor's published test cards (e.g. Stripe's 4242 4242 4242 4242)."
|
|
7969
|
+
});
|
|
7970
|
+
}
|
|
7971
|
+
return findings;
|
|
7972
|
+
}
|
|
7973
|
+
};
|
|
7974
|
+
var hardcodedSSN = {
|
|
7975
|
+
id: "VC212",
|
|
7976
|
+
title: "Hardcoded US Social Security Number",
|
|
7977
|
+
severity: "high",
|
|
7978
|
+
category: "Information Leakage",
|
|
7979
|
+
description: "A value in SSN format (NNN-NN-NNNN) is assigned to an SSN-named field in source. Social Security Numbers are regulated PII; a literal one leaks into git history, logs, and backups. Store SSNs encrypted at rest and load test data from generated fakes, never a code literal.",
|
|
7980
|
+
check(content, filePath) {
|
|
7981
|
+
if (isTestFile(filePath)) return [];
|
|
7982
|
+
const findings = [];
|
|
7983
|
+
const pattern = /(?:ssn|social[_-]?sec(?:urity)?(?:[_-]?(?:no|num|number))?|taxpayer[_-]?id)\b["']?\s*[:=]\s*["']?(\d{3}-\d{2}-\d{4})/gi;
|
|
7984
|
+
let m;
|
|
7985
|
+
while ((m = pattern.exec(content)) !== null) {
|
|
7986
|
+
const ssn = m[1];
|
|
7987
|
+
const [area, group, serial] = ssn.split("-");
|
|
7988
|
+
if (area === "000" || area === "666" || Number(area) >= 900) continue;
|
|
7989
|
+
if (group === "00" || serial === "0000") continue;
|
|
7990
|
+
if (ssn === "123-45-6789") continue;
|
|
7991
|
+
if (isCommentLine(content, m.index)) continue;
|
|
7992
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7993
|
+
findings.push({
|
|
7994
|
+
rule: "VC212",
|
|
7995
|
+
title: hardcodedSSN.title,
|
|
7996
|
+
severity: "high",
|
|
7997
|
+
category: "Information Leakage",
|
|
7998
|
+
file: filePath,
|
|
7999
|
+
line: lineNum,
|
|
8000
|
+
snippet: getSnippet(content, lineNum),
|
|
8001
|
+
fix: "Don't hardcode SSNs. Generate fake values for tests (a faker/factory), and store real SSNs with application-level encryption or in a secrets vault \u2014 never as a code literal."
|
|
8002
|
+
});
|
|
8003
|
+
}
|
|
8004
|
+
return findings;
|
|
8005
|
+
}
|
|
8006
|
+
};
|
|
7820
8007
|
var secretInURLParam = {
|
|
7821
8008
|
id: "VC146",
|
|
7822
8009
|
title: "Secret Passed in URL Query Parameter",
|
|
@@ -7842,7 +8029,7 @@ var secretInURLParam = {
|
|
|
7842
8029
|
if (isCommentLine(content, m.index)) continue;
|
|
7843
8030
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7844
8031
|
if (isInlineSilenced(content, m.index, "VC146")) continue;
|
|
7845
|
-
const lineNum = content
|
|
8032
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7846
8033
|
findings.push({
|
|
7847
8034
|
rule: "VC146",
|
|
7848
8035
|
title: this.title,
|
|
@@ -7873,7 +8060,7 @@ var secretLoggedToConsole = {
|
|
|
7873
8060
|
while ((m = pattern.exec(content)) !== null) {
|
|
7874
8061
|
if (isCommentLine(content, m.index)) continue;
|
|
7875
8062
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7876
|
-
const lineNum = content
|
|
8063
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7877
8064
|
findings.push({
|
|
7878
8065
|
rule: "VC147",
|
|
7879
8066
|
title: this.title,
|
|
@@ -7903,7 +8090,7 @@ var secretInErrorResponse = {
|
|
|
7903
8090
|
while ((m = pattern.exec(content)) !== null) {
|
|
7904
8091
|
if (isCommentLine(content, m.index)) continue;
|
|
7905
8092
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7906
|
-
const lineNum = content
|
|
8093
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7907
8094
|
findings.push({
|
|
7908
8095
|
rule: "VC148",
|
|
7909
8096
|
title: this.title,
|
|
@@ -7942,7 +8129,7 @@ var secretInBundleConfig = {
|
|
|
7942
8129
|
while ((m = re.exec(content)) !== null) {
|
|
7943
8130
|
if (isCommentLine(content, m.index)) continue;
|
|
7944
8131
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7945
|
-
const lineNum = content
|
|
8132
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7946
8133
|
findings.push({
|
|
7947
8134
|
rule: "VC149",
|
|
7948
8135
|
title: this.title,
|
|
@@ -7980,7 +8167,7 @@ var secretInHTMLAttribute = {
|
|
|
7980
8167
|
while ((m = re.exec(content)) !== null) {
|
|
7981
8168
|
if (isCommentLine(content, m.index)) continue;
|
|
7982
8169
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7983
|
-
const lineNum = content
|
|
8170
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7984
8171
|
findings.push({
|
|
7985
8172
|
rule: "VC150",
|
|
7986
8173
|
title: this.title,
|
|
@@ -8007,9 +8194,9 @@ var secretInCLIArgument = {
|
|
|
8007
8194
|
if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb)$/)) return [];
|
|
8008
8195
|
const findings = [];
|
|
8009
8196
|
const patterns = [
|
|
8010
|
-
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]
|
|
8011
|
-
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]
|
|
8012
|
-
/(?:subprocess|os\.system|os\.popen)\s*\([^)]
|
|
8197
|
+
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]{0,500}\$\{[^}]{0,200}(?:api[_-]?key|secret|token|password|credentials)/gi,
|
|
8198
|
+
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]{0,500}["']\s*\+\s*(?:api[_-]?key|secret|token|password|credentials)/gi,
|
|
8199
|
+
/(?:subprocess|os\.system|os\.popen)\s*\([^)]{0,500}\{[^}]{0,200}(?:api[_-]?key|secret|token|password|credentials)/gi
|
|
8013
8200
|
];
|
|
8014
8201
|
for (const p of patterns) {
|
|
8015
8202
|
let m;
|
|
@@ -8017,7 +8204,7 @@ var secretInCLIArgument = {
|
|
|
8017
8204
|
while ((m = re.exec(content)) !== null) {
|
|
8018
8205
|
if (isCommentLine(content, m.index)) continue;
|
|
8019
8206
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
8020
|
-
const lineNum = content
|
|
8207
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
8021
8208
|
findings.push({
|
|
8022
8209
|
rule: "VC151",
|
|
8023
8210
|
title: this.title,
|
|
@@ -8063,7 +8250,7 @@ var webhookSignatureVerification = {
|
|
|
8063
8250
|
if (svc.verify.test(content)) continue;
|
|
8064
8251
|
const m = content.match(/export\s+(?:async\s+)?function\s+POST/);
|
|
8065
8252
|
if (!m || m.index === void 0) continue;
|
|
8066
|
-
const lineNum = content
|
|
8253
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
8067
8254
|
findings.push({
|
|
8068
8255
|
rule: "VC152",
|
|
8069
8256
|
title: `${svc.name} Webhook Missing Signature Verification`,
|
|
@@ -8130,7 +8317,7 @@ var missingRequestValidation = {
|
|
|
8130
8317
|
if (/if\s*\(\s*!(?:body|parsed|data|payload)\.|throw.*(?:missing|invalid|required)/i.test(content)) return [];
|
|
8131
8318
|
const m = content.match(/export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH)/);
|
|
8132
8319
|
if (!m || m.index === void 0) return [];
|
|
8133
|
-
const lineNum = content
|
|
8320
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
8134
8321
|
return [{
|
|
8135
8322
|
rule: "VC154",
|
|
8136
8323
|
title: this.title,
|
|
@@ -8157,7 +8344,7 @@ var missingAIRateLimit = {
|
|
|
8157
8344
|
if (/rateLimit|rateLimiter|throttle|checkRateLimit|limiter|slowDown|express-rate-limit/i.test(content)) return [];
|
|
8158
8345
|
const m = content.match(/export\s+(?:async\s+)?function\s+(?:POST|GET)/i) || content.match(/\.(post|get)\s*\(/i);
|
|
8159
8346
|
if (!m || m.index === void 0) return [];
|
|
8160
|
-
const lineNum = content
|
|
8347
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
8161
8348
|
return [{
|
|
8162
8349
|
rule: "VC155",
|
|
8163
8350
|
title: this.title,
|
|
@@ -8186,7 +8373,7 @@ var missingPagination = {
|
|
|
8186
8373
|
if (/findUnique|findFirst|findById|\.findOne|WHERE.*id\s*=/i.test(content)) return [];
|
|
8187
8374
|
const m = content.match(/export\s+(?:async\s+)?function\s+GET/);
|
|
8188
8375
|
if (!m || m.index === void 0) return [];
|
|
8189
|
-
const lineNum = content
|
|
8376
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
8190
8377
|
return [{
|
|
8191
8378
|
rule: "VC156",
|
|
8192
8379
|
title: this.title,
|
|
@@ -8247,7 +8434,7 @@ var insecureDirectObjectReference = {
|
|
|
8247
8434
|
if (/requireUser|requireUserForApi|getServerSession|auth\(\)/i.test(content)) return [];
|
|
8248
8435
|
const m = content.match(/params\.id|params\.(?:slug|uuid)|req\.params\./);
|
|
8249
8436
|
if (!m || m.index === void 0) return [];
|
|
8250
|
-
const lineNum = content
|
|
8437
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
8251
8438
|
return [{
|
|
8252
8439
|
rule: "VC158",
|
|
8253
8440
|
title: this.title,
|
|
@@ -8545,7 +8732,10 @@ var allCustomRules = [
|
|
|
8545
8732
|
// VC204–VC206: GraphQL server hardening
|
|
8546
8733
|
graphqlNoDepthLimit,
|
|
8547
8734
|
graphqlNoComplexityLimit,
|
|
8548
|
-
graphqlCSRFDisabled
|
|
8735
|
+
graphqlCSRFDisabled,
|
|
8736
|
+
// VC211–VC212: hardcoded sensitive personal data (PII)
|
|
8737
|
+
hardcodedCreditCard,
|
|
8738
|
+
hardcodedSSN
|
|
8549
8739
|
];
|
|
8550
8740
|
function runCustomRules(content, filePath, disabledRules = [], tier = "free", extraRules = []) {
|
|
8551
8741
|
const findings = [];
|
|
@@ -8553,6 +8743,17 @@ function runCustomRules(content, filePath, disabledRules = [], tier = "free", ex
|
|
|
8553
8743
|
return findings;
|
|
8554
8744
|
}
|
|
8555
8745
|
if (/pro-rules-bundle|\.bundle\./i.test(filePath)) return findings;
|
|
8746
|
+
let maxLineLen = 0;
|
|
8747
|
+
{
|
|
8748
|
+
let lineStart = 0;
|
|
8749
|
+
for (let i = 0; i <= content.length; i++) {
|
|
8750
|
+
if (i === content.length || content.charCodeAt(i) === 10) {
|
|
8751
|
+
if (i - lineStart > maxLineLen) maxLineLen = i - lineStart;
|
|
8752
|
+
lineStart = i + 1;
|
|
8753
|
+
}
|
|
8754
|
+
}
|
|
8755
|
+
}
|
|
8756
|
+
if (maxLineLen > 5e4) return findings;
|
|
8556
8757
|
if (/onboarding|demo-data|example-vulnerable|code-sample/i.test(filePath)) return findings;
|
|
8557
8758
|
const ruleset = tier === "pro" && extraRules.length > 0 ? [...freeRules, ...extraRules] : freeRules;
|
|
8558
8759
|
for (const rule of ruleset) {
|
|
@@ -8963,6 +9164,7 @@ function scanEntropy(files) {
|
|
|
8963
9164
|
hardcodedAnthropicKey,
|
|
8964
9165
|
hardcodedCloudflareToken,
|
|
8965
9166
|
hardcodedCohereKey,
|
|
9167
|
+
hardcodedCreditCard,
|
|
8966
9168
|
hardcodedDatadogKey,
|
|
8967
9169
|
hardcodedDiscordToken,
|
|
8968
9170
|
hardcodedEncryptionKey,
|
|
@@ -8992,6 +9194,7 @@ function scanEntropy(files) {
|
|
|
8992
9194
|
hardcodedRailwayToken,
|
|
8993
9195
|
hardcodedReplicateKey,
|
|
8994
9196
|
hardcodedResendKey,
|
|
9197
|
+
hardcodedSSN,
|
|
8995
9198
|
hardcodedSecrets,
|
|
8996
9199
|
hardcodedSendGridKey,
|
|
8997
9200
|
hardcodedSentryAuthToken,
|