xploitscan-shared-rules 1.12.0 → 1.13.1
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 +258 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.js +258 -105
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -264,15 +264,50 @@ __export(index_exports, {
|
|
|
264
264
|
module.exports = __toCommonJS(index_exports);
|
|
265
265
|
|
|
266
266
|
// src/snippet.ts
|
|
267
|
-
|
|
267
|
+
var cacheContent = null;
|
|
268
|
+
var cacheLines = [];
|
|
269
|
+
var cacheOffsets = [];
|
|
270
|
+
function lineIndex(content) {
|
|
271
|
+
if (content === cacheContent) return { lines: cacheLines, offsets: cacheOffsets };
|
|
268
272
|
const lines = content.split("\n");
|
|
273
|
+
const offsets = new Array(lines.length);
|
|
274
|
+
let off = 0;
|
|
275
|
+
for (let i = 0; i < lines.length; i++) {
|
|
276
|
+
offsets[i] = off;
|
|
277
|
+
off += lines[i].length + 1;
|
|
278
|
+
}
|
|
279
|
+
cacheContent = content;
|
|
280
|
+
cacheLines = lines;
|
|
281
|
+
cacheOffsets = offsets;
|
|
282
|
+
return { lines, offsets };
|
|
283
|
+
}
|
|
284
|
+
function lineNumberAt(content, index) {
|
|
285
|
+
const { offsets } = lineIndex(content);
|
|
286
|
+
let lo = 0;
|
|
287
|
+
let hi = offsets.length - 1;
|
|
288
|
+
let ans = 0;
|
|
289
|
+
while (lo <= hi) {
|
|
290
|
+
const mid = lo + hi >> 1;
|
|
291
|
+
if (offsets[mid] <= index) {
|
|
292
|
+
ans = mid;
|
|
293
|
+
lo = mid + 1;
|
|
294
|
+
} else {
|
|
295
|
+
hi = mid - 1;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return ans + 1;
|
|
299
|
+
}
|
|
300
|
+
function getSnippet(content, line, contextLines = 2) {
|
|
301
|
+
const { lines } = lineIndex(content);
|
|
269
302
|
const start = Math.max(0, line - 1 - contextLines);
|
|
270
303
|
const end = Math.min(lines.length, line + contextLines);
|
|
271
|
-
|
|
272
|
-
|
|
304
|
+
const out = [];
|
|
305
|
+
for (let i = start; i < end; i++) {
|
|
306
|
+
const lineNum = i + 1;
|
|
273
307
|
const marker = lineNum === line ? ">" : " ";
|
|
274
|
-
|
|
275
|
-
}
|
|
308
|
+
out.push(`${marker} ${lineNum.toString().padStart(4)} | ${lines[i]}`);
|
|
309
|
+
}
|
|
310
|
+
return out.join("\n");
|
|
276
311
|
}
|
|
277
312
|
|
|
278
313
|
// src/rule-impacts.ts
|
|
@@ -1080,7 +1115,7 @@ var SERVER_SIDE_PATH_RE = new RegExp(
|
|
|
1080
1115
|
].join("|"),
|
|
1081
1116
|
"i"
|
|
1082
1117
|
);
|
|
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/;
|
|
1118
|
+
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
1119
|
function isServerSideFile(filePath, content) {
|
|
1085
1120
|
if (isTestFile(filePath)) return false;
|
|
1086
1121
|
if (SERVER_SIDE_PATH_RE.test(filePath)) return true;
|
|
@@ -1127,13 +1162,23 @@ function filterSilenced(matches, content, ruleId) {
|
|
|
1127
1162
|
}
|
|
1128
1163
|
function findMatches(content, pattern, rule, filePath, fixTemplate) {
|
|
1129
1164
|
const matches = [];
|
|
1130
|
-
const lines = content.split("\n");
|
|
1131
1165
|
let m;
|
|
1132
1166
|
const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
1167
|
+
let scanned = 0;
|
|
1168
|
+
let lineNum = 1;
|
|
1169
|
+
let lastMatchIndex = -1;
|
|
1170
|
+
const MAX_MATCHES = 500;
|
|
1133
1171
|
while ((m = re.exec(content)) !== null) {
|
|
1172
|
+
if (m.index === lastMatchIndex && m[0].length === 0) {
|
|
1173
|
+
re.lastIndex++;
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
lastMatchIndex = m.index;
|
|
1134
1177
|
if (isCommentLine(content, m.index)) continue;
|
|
1135
1178
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
1136
|
-
|
|
1179
|
+
for (; scanned < m.index; scanned++) {
|
|
1180
|
+
if (content.charCodeAt(scanned) === 10) lineNum++;
|
|
1181
|
+
}
|
|
1137
1182
|
matches.push({
|
|
1138
1183
|
rule: rule.id,
|
|
1139
1184
|
title: rule.title,
|
|
@@ -1144,6 +1189,7 @@ function findMatches(content, pattern, rule, filePath, fixTemplate) {
|
|
|
1144
1189
|
snippet: getSnippet(content, lineNum),
|
|
1145
1190
|
fix: fixTemplate?.(m)
|
|
1146
1191
|
});
|
|
1192
|
+
if (matches.length >= MAX_MATCHES) break;
|
|
1147
1193
|
}
|
|
1148
1194
|
return matches;
|
|
1149
1195
|
}
|
|
@@ -1159,10 +1205,26 @@ function astMatch(content, filePath, line, rule, fix) {
|
|
|
1159
1205
|
fix
|
|
1160
1206
|
};
|
|
1161
1207
|
}
|
|
1208
|
+
var parseCacheContent = null;
|
|
1209
|
+
var parseCachePath = null;
|
|
1210
|
+
var parseCacheResult = null;
|
|
1211
|
+
var parseCacheValid = false;
|
|
1162
1212
|
function tryParse(content, filePath) {
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1213
|
+
if (parseCacheValid && content === parseCacheContent && filePath === parseCachePath) {
|
|
1214
|
+
return parseCacheResult;
|
|
1215
|
+
}
|
|
1216
|
+
let result = null;
|
|
1217
|
+
try {
|
|
1218
|
+
const parsed = parseFile(content, filePath);
|
|
1219
|
+
result = parsed ? { parsed, taint: buildTaintMap(parsed) } : null;
|
|
1220
|
+
} catch {
|
|
1221
|
+
result = null;
|
|
1222
|
+
}
|
|
1223
|
+
parseCacheResult = result;
|
|
1224
|
+
parseCacheContent = content;
|
|
1225
|
+
parseCachePath = filePath;
|
|
1226
|
+
parseCacheValid = true;
|
|
1227
|
+
return result;
|
|
1166
1228
|
}
|
|
1167
1229
|
var hardcodedSecrets = {
|
|
1168
1230
|
id: "VC001",
|
|
@@ -1527,9 +1589,10 @@ var xssVulnerability = {
|
|
|
1527
1589
|
category: "Injection",
|
|
1528
1590
|
description: "Rendering user input without sanitization allows attackers to inject malicious scripts.",
|
|
1529
1591
|
check(content, filePath) {
|
|
1592
|
+
const hasSanitizerBypass = /bypassSecurityTrust(?:Html|Script|Style|Url|ResourceUrl)\s*\(/.test(content);
|
|
1530
1593
|
if (/(?:sanitize|purify|escape|xss)/i.test(filePath)) return [];
|
|
1531
|
-
if (/DOMPurify\.sanitize|sanitizeHtml|xss\(|escapeHtml/i.test(content)) return [];
|
|
1532
|
-
if (/(?:import|require)\s*\(
|
|
1594
|
+
if (!hasSanitizerBypass && /DOMPurify\.sanitize|sanitizeHtml|xss\(|escapeHtml/i.test(content)) return [];
|
|
1595
|
+
if (!hasSanitizerBypass && /(?:import|require)\s*\(?[^\n]{0,200}(?:DOMPurify|dompurify|sanitize|sanitizer)/i.test(content)) return [];
|
|
1533
1596
|
const patterns = [
|
|
1534
1597
|
// React dangerouslySetInnerHTML
|
|
1535
1598
|
/dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:/g,
|
|
@@ -1540,7 +1603,12 @@ var xssVulnerability = {
|
|
|
1540
1603
|
// v-html in Vue
|
|
1541
1604
|
/v-html\s*=/g,
|
|
1542
1605
|
// {@html} in Svelte
|
|
1543
|
-
/\{@html\s/g
|
|
1606
|
+
/\{@html\s/g,
|
|
1607
|
+
// Angular DomSanitizer escape hatches — bypassSecurityTrustHtml /
|
|
1608
|
+
// bypassSecurityTrustScript / ...Url / ...ResourceUrl / ...Style. These
|
|
1609
|
+
// mark a value as trusted and skip Angular's built-in sanitization;
|
|
1610
|
+
// passing user input through one is the canonical Angular DOM-XSS sink.
|
|
1611
|
+
/bypassSecurityTrust(?:Html|Script|Style|Url|ResourceUrl)\s*\(/g
|
|
1544
1612
|
];
|
|
1545
1613
|
const allLines = content.split("\n");
|
|
1546
1614
|
const matches = [];
|
|
@@ -1553,9 +1621,9 @@ var xssVulnerability = {
|
|
|
1553
1621
|
() => "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."
|
|
1554
1622
|
);
|
|
1555
1623
|
for (const m of raw) {
|
|
1556
|
-
const lineText = allLines[m.line - 1] || "";
|
|
1624
|
+
const lineText = (allLines[m.line - 1] || "").slice(0, 4e3);
|
|
1557
1625
|
if (/\.innerHTML\s*=\s*['"]/.test(lineText) && !/\$\{/.test(lineText)) continue;
|
|
1558
|
-
if (/\.innerHTML\s*=\s*['"][^'"]
|
|
1626
|
+
if (/\.innerHTML\s*=\s*['"][^'"]{0,2000}['"]\s*$/.test(lineText)) continue;
|
|
1559
1627
|
if (/dangerouslySetInnerHTML/.test(lineText)) {
|
|
1560
1628
|
const windowText = allLines.slice(m.line - 1, m.line + 2).join("\n");
|
|
1561
1629
|
if (/JSON\.stringify/i.test(windowText)) continue;
|
|
@@ -1806,7 +1874,7 @@ var evalUsage = {
|
|
|
1806
1874
|
/new\s+Function\s*\(\s*(?!["'`])/g
|
|
1807
1875
|
];
|
|
1808
1876
|
const hasEvalInString = /["'`]eval(?:-source-map|["'`])/i.test(content);
|
|
1809
|
-
if (hasEvalInString && !/\beval\s*\([^)]
|
|
1877
|
+
if (hasEvalInString && !/\beval\s*\([^)]{0,500}(?:req\.|body\.|input|params|user|data)/i.test(content)) return [];
|
|
1810
1878
|
const matches = [];
|
|
1811
1879
|
for (const p of patterns) {
|
|
1812
1880
|
const rawMatches = findMatches(
|
|
@@ -1847,7 +1915,7 @@ var unvalidatedRedirect = {
|
|
|
1847
1915
|
const isPureLiteral = /^["'`][^"'`]*["'`]$/.test(value);
|
|
1848
1916
|
if (!hasInterpolation && isPureLiteral) continue;
|
|
1849
1917
|
if (isInlineSilenced(content, m.index, "VC016")) continue;
|
|
1850
|
-
const line = content
|
|
1918
|
+
const line = lineNumberAt(content, m.index);
|
|
1851
1919
|
matches.push({
|
|
1852
1920
|
rule: "VC016",
|
|
1853
1921
|
title: unvalidatedRedirect.title,
|
|
@@ -2070,11 +2138,11 @@ var prototypePollution = {
|
|
|
2070
2138
|
];
|
|
2071
2139
|
const hasValidation = /schema|validate|sanitize|whitelist|allowedKeys|pick\(|Object\.freeze|zod|yup|joi|ajv/i.test(content);
|
|
2072
2140
|
if (!hasValidation) {
|
|
2073
|
-
const hasUnsafeMerge = /Object\.assign\s*\([^)]
|
|
2141
|
+
const hasUnsafeMerge = /Object\.assign\s*\([^)]{0,500}JSON\.parse|\.\.\.JSON\.parse|\{[^\n]{0,300}\.\.\.(?:stored|saved|cached|parsed|data)/i.test(content);
|
|
2074
2142
|
if (hasUnsafeMerge) {
|
|
2075
2143
|
matches.push(...findMatches(
|
|
2076
2144
|
content,
|
|
2077
|
-
/Object\.assign\s*\([^)]
|
|
2145
|
+
/Object\.assign\s*\([^)]{0,500}JSON\.parse|\.\.\.JSON\.parse/g,
|
|
2078
2146
|
prototypePollution,
|
|
2079
2147
|
filePath,
|
|
2080
2148
|
() => "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."
|
|
@@ -3620,7 +3688,7 @@ var dangerousInnerHTML = {
|
|
|
3620
3688
|
if (/^[A-Z][A-Z0-9_]+$/.test(value)) continue;
|
|
3621
3689
|
if (/^["'`][^$]*["'`]$/.test(value)) continue;
|
|
3622
3690
|
if (/JSON\.stringify/i.test(value)) continue;
|
|
3623
|
-
const lineNum = content
|
|
3691
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
3624
3692
|
findings.push({
|
|
3625
3693
|
rule: "VC063",
|
|
3626
3694
|
title: dangerousInnerHTML.title,
|
|
@@ -4283,17 +4351,37 @@ var ssti = {
|
|
|
4283
4351
|
(call, line) => {
|
|
4284
4352
|
const first = call.arguments[0];
|
|
4285
4353
|
if (!first || first.type === "SpreadElement") return;
|
|
4286
|
-
if (
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4354
|
+
if (taint.isTainted(first)) {
|
|
4355
|
+
if (matches.some((m) => m.line === line)) return;
|
|
4356
|
+
matches.push(
|
|
4357
|
+
astMatch(
|
|
4358
|
+
content,
|
|
4359
|
+
filePath,
|
|
4360
|
+
line,
|
|
4361
|
+
ssti,
|
|
4362
|
+
"Compile templates from a trusted, static source (a file path or a constant string). Pass user data only as context values."
|
|
4363
|
+
)
|
|
4364
|
+
);
|
|
4365
|
+
return;
|
|
4366
|
+
}
|
|
4367
|
+
const opts = call.arguments[1];
|
|
4368
|
+
if (opts && opts.type === "ObjectExpression") {
|
|
4369
|
+
const hasTaintedSpread = opts.properties.some(
|
|
4370
|
+
(p) => p.type === "SpreadElement" && taint.isTainted(p.argument)
|
|
4371
|
+
);
|
|
4372
|
+
if (hasTaintedSpread) {
|
|
4373
|
+
if (matches.some((m) => m.line === line)) return;
|
|
4374
|
+
matches.push(
|
|
4375
|
+
astMatch(
|
|
4376
|
+
content,
|
|
4377
|
+
filePath,
|
|
4378
|
+
line,
|
|
4379
|
+
ssti,
|
|
4380
|
+
"Don't spread req.body/req.query into template locals \u2014 an attacker can inject reserved view options (e.g. `layout`) to load arbitrary templates. Pass only the specific values the view needs: render('view', { name: req.body.name })."
|
|
4381
|
+
)
|
|
4382
|
+
);
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4297
4385
|
}
|
|
4298
4386
|
);
|
|
4299
4387
|
return filterSilenced(matches, content, "VC082");
|
|
@@ -4342,7 +4430,7 @@ var missingSRI = {
|
|
|
4342
4430
|
const re = new RegExp(scriptPattern.source, scriptPattern.flags);
|
|
4343
4431
|
while ((m = re.exec(content)) !== null) {
|
|
4344
4432
|
if (!m[0].includes("integrity")) {
|
|
4345
|
-
const lineNum = content
|
|
4433
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
4346
4434
|
matches.push({
|
|
4347
4435
|
rule: "VC084",
|
|
4348
4436
|
title: missingSRI.title,
|
|
@@ -4556,16 +4644,61 @@ var unprotectedDownload = {
|
|
|
4556
4644
|
if (!/(?:download|sendFile|send_file)/i.test(content)) return [];
|
|
4557
4645
|
if (!isServerSideFile(filePath, content)) return [];
|
|
4558
4646
|
const matches = [];
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4647
|
+
const hasContainmentCheck = /\.startsWith\s*\(/.test(content) || /realpath/i.test(content) || /includes\s*\(\s*["']\.\./.test(content);
|
|
4648
|
+
if (/(?:sendFile|download|send_file)\s*\([^)]*(?:req\.|params\.|query\.|body\.)/i.test(content) && !hasContainmentCheck) {
|
|
4649
|
+
matches.push(...findMatches(
|
|
4650
|
+
content,
|
|
4651
|
+
/(?:sendFile|download|send_file)\s*\(/gi,
|
|
4652
|
+
unprotectedDownload,
|
|
4653
|
+
filePath,
|
|
4654
|
+
() => "Validate file paths: const safePath = path.resolve(DOWNLOAD_DIR, filename); if (!safePath.startsWith(DOWNLOAD_DIR + path.sep)) throw new Error('Invalid path');"
|
|
4655
|
+
));
|
|
4656
|
+
}
|
|
4657
|
+
if (!hasContainmentCheck && /\b(?:sendFile|download)\s*\(/.test(content)) {
|
|
4658
|
+
const ctx = tryParse(content, filePath);
|
|
4659
|
+
if (ctx) {
|
|
4660
|
+
const { parsed, taint } = ctx;
|
|
4661
|
+
const readsUserInput = /\b(?:req|request)\s*\.\s*(?:params|query|body|headers|cookies)\b/.test(content) || /\bparams\s*\.\s*\w/.test(content) || /\bquery\s*\.\s*\w/.test(content) || /\breq\.body\b/.test(content);
|
|
4662
|
+
const isUnsafePathBuild = (node) => {
|
|
4663
|
+
if (node.type !== "CallExpression") return false;
|
|
4664
|
+
const callee = node.callee;
|
|
4665
|
+
const isPathBuild = callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "path" && callee.property.type === "Identifier" && (callee.property.name === "resolve" || callee.property.name === "join");
|
|
4666
|
+
if (!isPathBuild) return false;
|
|
4667
|
+
const rootedAtDirname = node.arguments.some(
|
|
4668
|
+
(a) => a.type === "Identifier" && (a.name === "__dirname" || a.name === "__filename")
|
|
4669
|
+
);
|
|
4670
|
+
if (rootedAtDirname) return false;
|
|
4671
|
+
return node.arguments.some(
|
|
4672
|
+
(a) => a.type !== "SpreadElement" && a.type !== "StringLiteral" && a.type !== "TemplateLiteral"
|
|
4673
|
+
);
|
|
4674
|
+
};
|
|
4675
|
+
const argIsTainted = (node) => {
|
|
4676
|
+
if (taint.isTainted(node)) return true;
|
|
4677
|
+
if (readsUserInput && isUnsafePathBuild(node)) return true;
|
|
4678
|
+
if (node.type === "CallExpression") {
|
|
4679
|
+
return node.arguments.some((a) => a.type !== "SpreadElement" && argIsTainted(a));
|
|
4680
|
+
}
|
|
4681
|
+
return false;
|
|
4682
|
+
};
|
|
4683
|
+
visitCalls(
|
|
4684
|
+
parsed,
|
|
4685
|
+
(callee) => callee.type === "MemberExpression" && callee.property.type === "Identifier" && (callee.property.name === "sendFile" || callee.property.name === "download"),
|
|
4686
|
+
(call, line) => {
|
|
4687
|
+
const first = call.arguments[0];
|
|
4688
|
+
if (!first || first.type === "SpreadElement") return;
|
|
4689
|
+
if (!argIsTainted(first)) return;
|
|
4690
|
+
if (matches.some((m) => m.line === line)) return;
|
|
4691
|
+
matches.push(
|
|
4692
|
+
astMatch(
|
|
4693
|
+
content,
|
|
4694
|
+
filePath,
|
|
4695
|
+
line,
|
|
4696
|
+
unprotectedDownload,
|
|
4697
|
+
"Validate file paths: const safePath = path.resolve(DOWNLOAD_DIR, filename); if (!safePath.startsWith(DOWNLOAD_DIR + path.sep)) throw new Error('Invalid path');"
|
|
4698
|
+
)
|
|
4699
|
+
);
|
|
4700
|
+
}
|
|
4701
|
+
);
|
|
4569
4702
|
}
|
|
4570
4703
|
}
|
|
4571
4704
|
return matches;
|
|
@@ -5168,7 +5301,7 @@ var s3BucketNoEncryption = {
|
|
|
5168
5301
|
const blockEnd = Math.min(m.index + 2e3, content.length);
|
|
5169
5302
|
const blockContent = content.substring(m.index, blockEnd);
|
|
5170
5303
|
if (!/server_side_encryption/.test(blockContent)) {
|
|
5171
|
-
const lineNum = content
|
|
5304
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5172
5305
|
matches.push({
|
|
5173
5306
|
rule: "VC107",
|
|
5174
5307
|
title: s3BucketNoEncryption.title,
|
|
@@ -5199,7 +5332,7 @@ var securityGroupAllInbound = {
|
|
|
5199
5332
|
while ((m = ingressPattern.exec(content)) !== null) {
|
|
5200
5333
|
if (isCommentLine(content, m.index)) continue;
|
|
5201
5334
|
if (/from_port\s*=\s*0/.test(m[0]) || !/from_port/.test(m[0])) {
|
|
5202
|
-
const lineNum = content
|
|
5335
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5203
5336
|
matches.push({
|
|
5204
5337
|
rule: "VC108",
|
|
5205
5338
|
title: securityGroupAllInbound.title,
|
|
@@ -5286,7 +5419,7 @@ var lambdaWithoutVPC = {
|
|
|
5286
5419
|
const blockEnd = Math.min(m.index + 2e3, content.length);
|
|
5287
5420
|
const blockContent = content.substring(m.index, blockEnd);
|
|
5288
5421
|
if (!/vpc_config\s*\{/.test(blockContent)) {
|
|
5289
|
-
const lineNum = content
|
|
5422
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5290
5423
|
matches.push({
|
|
5291
5424
|
rule: "VC111",
|
|
5292
5425
|
title: lambdaWithoutVPC.title,
|
|
@@ -5316,7 +5449,7 @@ var dockerLatestTag = {
|
|
|
5316
5449
|
while ((m = fromPattern.exec(content)) !== null) {
|
|
5317
5450
|
const image = m[1];
|
|
5318
5451
|
if (image.endsWith(":latest") || !image.includes(":") && !image.startsWith("$")) {
|
|
5319
|
-
const lineNum = content
|
|
5452
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5320
5453
|
matches.push({
|
|
5321
5454
|
rule: "VC112",
|
|
5322
5455
|
title: dockerLatestTag.title,
|
|
@@ -5444,17 +5577,26 @@ var pathTraversal = {
|
|
|
5444
5577
|
const findings = [];
|
|
5445
5578
|
const patterns = [
|
|
5446
5579
|
/(?:readFile|readFileSync|createReadStream|writeFile|writeFileSync|appendFile|unlink|unlinkSync|access|stat|statSync|existsSync)\s*\(\s*(?:req\.|request\.|ctx\.|params\.|query\.)/gi,
|
|
5447
|
-
/(?:path\.join|path\.resolve)\s*\([^)]
|
|
5448
|
-
/(?:res\.sendFile|res\.download)\s*\([^)]
|
|
5580
|
+
/(?:path\.join|path\.resolve)\s*\([^)]{0,500}(?:req\.|request\.|ctx\.|params\.|query\.)[^)]{0,500}\)/gi,
|
|
5581
|
+
/(?:res\.sendFile|res\.download)\s*\([^)]{0,500}(?:req\.|request\.)/gi,
|
|
5449
5582
|
/open\s*\(\s*(?:request\.|params\[|args\.)/gi
|
|
5450
5583
|
];
|
|
5584
|
+
const MAX_MATCHES = 500;
|
|
5451
5585
|
for (const pat of patterns) {
|
|
5586
|
+
if (findings.length >= MAX_MATCHES) break;
|
|
5452
5587
|
let m;
|
|
5588
|
+
let lastIdx = -1;
|
|
5453
5589
|
while ((m = pat.exec(content)) !== null) {
|
|
5590
|
+
if (findings.length >= MAX_MATCHES) break;
|
|
5591
|
+
if (m.index === lastIdx && m[0].length === 0) {
|
|
5592
|
+
pat.lastIndex++;
|
|
5593
|
+
continue;
|
|
5594
|
+
}
|
|
5595
|
+
lastIdx = m.index;
|
|
5454
5596
|
if (isCommentLine(content, m.index)) continue;
|
|
5455
5597
|
const surrounding = content.substring(Math.max(0, m.index - 200), m.index + 200);
|
|
5456
|
-
if (/path\.normalize|sanitize|\.replace\(\s*['"]
|
|
5457
|
-
const lineNum = content
|
|
5598
|
+
if (/path\.normalize|sanitize|\.replace\(\s*['"]\.\.|\.startsWith\s*\(|realpath/i.test(surrounding)) continue;
|
|
5599
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5458
5600
|
findings.push({
|
|
5459
5601
|
rule: "VC117",
|
|
5460
5602
|
title: pathTraversal.title,
|
|
@@ -5485,7 +5627,7 @@ var piiInLogs = {
|
|
|
5485
5627
|
while ((m = logPattern.exec(content)) !== null) {
|
|
5486
5628
|
if (isCommentLine(content, m.index)) continue;
|
|
5487
5629
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
5488
|
-
const lineNum = content
|
|
5630
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5489
5631
|
findings.push({
|
|
5490
5632
|
rule: "VC118",
|
|
5491
5633
|
title: piiInLogs.title,
|
|
@@ -5523,7 +5665,7 @@ var hardcodedOAuthSecret = {
|
|
|
5523
5665
|
while ((m = pat.exec(content)) !== null) {
|
|
5524
5666
|
if (isCommentLine(content, m.index)) continue;
|
|
5525
5667
|
if (/process\.env|os\.environ|ENV\[|getenv|\$\{|<your|CHANGE_ME|xxx|placeholder/i.test(m[0])) continue;
|
|
5526
|
-
const lineNum = content
|
|
5668
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5527
5669
|
findings.push({
|
|
5528
5670
|
rule: "VC119",
|
|
5529
5671
|
title: hardcodedOAuthSecret.title,
|
|
@@ -5555,7 +5697,7 @@ var missingOAuthState = {
|
|
|
5555
5697
|
if (isCommentLine(content, m.index)) continue;
|
|
5556
5698
|
const surrounding = content.substring(m.index, Math.min(content.length, m.index + 500));
|
|
5557
5699
|
if (/state\s*[=:]/i.test(surrounding)) continue;
|
|
5558
|
-
const lineNum = content
|
|
5700
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5559
5701
|
findings.push({
|
|
5560
5702
|
rule: "VC120",
|
|
5561
5703
|
title: missingOAuthState.title,
|
|
@@ -5583,7 +5725,7 @@ var unpinnedGitHubAction = {
|
|
|
5583
5725
|
let m;
|
|
5584
5726
|
while ((m = usesPattern.exec(content)) !== null) {
|
|
5585
5727
|
if (isCommentLine(content, m.index)) continue;
|
|
5586
|
-
const lineNum = content
|
|
5728
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5587
5729
|
findings.push({
|
|
5588
5730
|
rule: "VC121",
|
|
5589
5731
|
title: unpinnedGitHubAction.title,
|
|
@@ -5617,7 +5759,7 @@ var deprecatedTLS = {
|
|
|
5617
5759
|
let m;
|
|
5618
5760
|
while ((m = pat.exec(content)) !== null) {
|
|
5619
5761
|
if (isCommentLine(content, m.index)) continue;
|
|
5620
|
-
const lineNum = content
|
|
5762
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5621
5763
|
findings.push({
|
|
5622
5764
|
rule: "VC122",
|
|
5623
5765
|
title: deprecatedTLS.title,
|
|
@@ -5653,7 +5795,7 @@ var weakRSAKeySize = {
|
|
|
5653
5795
|
let m;
|
|
5654
5796
|
while ((m = pat.exec(content)) !== null) {
|
|
5655
5797
|
if (isCommentLine(content, m.index)) continue;
|
|
5656
|
-
const lineNum = content
|
|
5798
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5657
5799
|
findings.push({
|
|
5658
5800
|
rule: "VC123",
|
|
5659
5801
|
title: weakRSAKeySize.title,
|
|
@@ -5689,7 +5831,7 @@ var ecbModeEncryption = {
|
|
|
5689
5831
|
let m;
|
|
5690
5832
|
while ((m = pat.exec(content)) !== null) {
|
|
5691
5833
|
if (isCommentLine(content, m.index)) continue;
|
|
5692
|
-
const lineNum = content
|
|
5834
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5693
5835
|
findings.push({
|
|
5694
5836
|
rule: "VC124",
|
|
5695
5837
|
title: ecbModeEncryption.title,
|
|
@@ -5720,7 +5862,7 @@ var insecurePasswordReset = {
|
|
|
5720
5862
|
let m;
|
|
5721
5863
|
while ((m = weakTokens.exec(content)) !== null) {
|
|
5722
5864
|
if (isCommentLine(content, m.index)) continue;
|
|
5723
|
-
const lineNum = content
|
|
5865
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5724
5866
|
findings.push({
|
|
5725
5867
|
rule: "VC125",
|
|
5726
5868
|
title: insecurePasswordReset.title,
|
|
@@ -5737,7 +5879,7 @@ var insecurePasswordReset = {
|
|
|
5737
5879
|
if (isCommentLine(content, m.index)) continue;
|
|
5738
5880
|
const surrounding = content.substring(Math.max(0, m.index - 500), m.index);
|
|
5739
5881
|
if (!/(?:reset|forgot).*password/i.test(surrounding)) continue;
|
|
5740
|
-
const lineNum = content
|
|
5882
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5741
5883
|
findings.push({
|
|
5742
5884
|
rule: "VC125",
|
|
5743
5885
|
title: insecurePasswordReset.title,
|
|
@@ -5798,7 +5940,7 @@ var insecureHTTPMethods = {
|
|
|
5798
5940
|
if (isCommentLine(content, m.index)) continue;
|
|
5799
5941
|
const handlerBlock = content.substring(m.index, Math.min(content.length, m.index + 500));
|
|
5800
5942
|
if (/auth|authenticate|authorize|requireAuth|isAuthenticated|protect|guard|middleware|session|jwt|verify|clerk|getAuth/i.test(handlerBlock)) continue;
|
|
5801
|
-
const lineNum = content
|
|
5943
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5802
5944
|
findings.push({
|
|
5803
5945
|
rule: "VC127",
|
|
5804
5946
|
title: insecureHTTPMethods.title,
|
|
@@ -5832,7 +5974,7 @@ var httpRequestSmuggling = {
|
|
|
5832
5974
|
let m;
|
|
5833
5975
|
while ((m = pat.exec(content)) !== null) {
|
|
5834
5976
|
if (isCommentLine(content, m.index)) continue;
|
|
5835
|
-
const lineNum = content
|
|
5977
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5836
5978
|
findings.push({
|
|
5837
5979
|
rule: "VC128",
|
|
5838
5980
|
title: httpRequestSmuggling.title,
|
|
@@ -5866,7 +6008,7 @@ var unencryptedPII = {
|
|
|
5866
6008
|
let m;
|
|
5867
6009
|
while ((m = pat.exec(content)) !== null) {
|
|
5868
6010
|
if (isCommentLine(content, m.index)) continue;
|
|
5869
|
-
const lineNum = content
|
|
6011
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5870
6012
|
findings.push({
|
|
5871
6013
|
rule: "VC129",
|
|
5872
6014
|
title: unencryptedPII.title,
|
|
@@ -5898,7 +6040,7 @@ var missingAuthRateLimit = {
|
|
|
5898
6040
|
if (isCommentLine(content, m.index)) continue;
|
|
5899
6041
|
const surrounding = content.substring(Math.max(0, m.index - 300), Math.min(content.length, m.index + 300));
|
|
5900
6042
|
if (/rateLimit|rateLimiter|throttle|slowDown|express-rate-limit|rate_limit|RateLimiter|limiter/i.test(surrounding)) continue;
|
|
5901
|
-
const lineNum = content
|
|
6043
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5902
6044
|
findings.push({
|
|
5903
6045
|
rule: "VC130",
|
|
5904
6046
|
title: missingAuthRateLimit.title,
|
|
@@ -5936,7 +6078,7 @@ var vulnerableDependencies = {
|
|
|
5936
6078
|
for (const [pat, message] of vulnerablePackages) {
|
|
5937
6079
|
let m;
|
|
5938
6080
|
while ((m = pat.exec(content)) !== null) {
|
|
5939
|
-
const lineNum = content
|
|
6081
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5940
6082
|
findings.push({
|
|
5941
6083
|
rule: "VC131",
|
|
5942
6084
|
title: vulnerableDependencies.title,
|
|
@@ -5968,7 +6110,7 @@ function secretRuleCheck(content, filePath, pattern, ruleId, title, severity, fi
|
|
|
5968
6110
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
5969
6111
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
5970
6112
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
5971
|
-
const lineNum = content
|
|
6113
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5972
6114
|
findings.push({
|
|
5973
6115
|
rule: ruleId,
|
|
5974
6116
|
title,
|
|
@@ -6069,7 +6211,7 @@ var hardcodedGCPServiceAccount = {
|
|
|
6069
6211
|
const findings = [];
|
|
6070
6212
|
const m = content.match(/"type"\s*:\s*"service_account"/);
|
|
6071
6213
|
if (m && m.index !== void 0) {
|
|
6072
|
-
const lineNum = content
|
|
6214
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6073
6215
|
findings.push({
|
|
6074
6216
|
rule: "VC136",
|
|
6075
6217
|
title: this.title,
|
|
@@ -6175,7 +6317,7 @@ var hardcodedDatadogKey = {
|
|
|
6175
6317
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
6176
6318
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
6177
6319
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
6178
|
-
const lineNum = content
|
|
6320
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6179
6321
|
findings.push({
|
|
6180
6322
|
rule: "VC141",
|
|
6181
6323
|
title: this.title,
|
|
@@ -6209,7 +6351,7 @@ var hardcodedVercelToken = {
|
|
|
6209
6351
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
6210
6352
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
6211
6353
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
6212
|
-
const lineNum = content
|
|
6354
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6213
6355
|
findings.push({
|
|
6214
6356
|
rule: "VC142",
|
|
6215
6357
|
title: this.title,
|
|
@@ -6243,7 +6385,7 @@ var hardcodedSupabaseServiceRole = {
|
|
|
6243
6385
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
6244
6386
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
6245
6387
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
6246
|
-
const lineNum = content
|
|
6388
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6247
6389
|
findings.push({
|
|
6248
6390
|
rule: "VC143",
|
|
6249
6391
|
title: this.title,
|
|
@@ -6307,7 +6449,7 @@ function contextSecretRuleCheck(content, filePath, pattern, ruleId, title, sever
|
|
|
6307
6449
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
6308
6450
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
6309
6451
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
6310
|
-
const lineNum = content
|
|
6452
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6311
6453
|
findings.push({
|
|
6312
6454
|
rule: ruleId,
|
|
6313
6455
|
title,
|
|
@@ -6787,7 +6929,7 @@ var ghaPullRequestTargetCheckout = {
|
|
|
6787
6929
|
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;
|
|
6788
6930
|
let m;
|
|
6789
6931
|
while ((m = checkoutPattern.exec(content)) !== null) {
|
|
6790
|
-
const lineNum = content
|
|
6932
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6791
6933
|
findings.push({
|
|
6792
6934
|
rule: "VC184",
|
|
6793
6935
|
title: ghaPullRequestTargetCheckout.title,
|
|
@@ -6871,7 +7013,7 @@ var ghaThirdPartyActionWithSecrets = {
|
|
|
6871
7013
|
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;
|
|
6872
7014
|
let m;
|
|
6873
7015
|
while ((m = pattern.exec(content)) !== null) {
|
|
6874
|
-
const lineNum = content
|
|
7016
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6875
7017
|
findings.push({
|
|
6876
7018
|
rule: "VC187",
|
|
6877
7019
|
title: ghaThirdPartyActionWithSecrets.title,
|
|
@@ -7036,7 +7178,7 @@ var pyRequestsVerifyFalse = {
|
|
|
7036
7178
|
let m;
|
|
7037
7179
|
while ((m = pattern.exec(content)) !== null) {
|
|
7038
7180
|
if (isCommentLine(content, m.index)) continue;
|
|
7039
|
-
const lineNum = content
|
|
7181
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7040
7182
|
findings.push({
|
|
7041
7183
|
rule: "VC191",
|
|
7042
7184
|
title: pyRequestsVerifyFalse.title,
|
|
@@ -7067,7 +7209,7 @@ var pyJinja2AutoescapeOff = {
|
|
|
7067
7209
|
let m;
|
|
7068
7210
|
while ((m = pattern.exec(content)) !== null) {
|
|
7069
7211
|
if (isCommentLine(content, m.index)) continue;
|
|
7070
|
-
const lineNum = content
|
|
7212
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7071
7213
|
findings.push({
|
|
7072
7214
|
rule: "VC192",
|
|
7073
7215
|
title: pyJinja2AutoescapeOff.title,
|
|
@@ -7096,7 +7238,7 @@ var pyTempfileMktemp = {
|
|
|
7096
7238
|
let m;
|
|
7097
7239
|
while ((m = pattern.exec(content)) !== null) {
|
|
7098
7240
|
if (isCommentLine(content, m.index)) continue;
|
|
7099
|
-
const lineNum = content
|
|
7241
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7100
7242
|
findings.push({
|
|
7101
7243
|
rule: "VC193",
|
|
7102
7244
|
title: pyTempfileMktemp.title,
|
|
@@ -7138,7 +7280,7 @@ var pyDjangoMarkSafe = {
|
|
|
7138
7280
|
let m;
|
|
7139
7281
|
while ((m = pattern.exec(content)) !== null) {
|
|
7140
7282
|
if (isCommentLine(content, m.index)) continue;
|
|
7141
|
-
const lineNum = content
|
|
7283
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7142
7284
|
if (seenLines.has(lineNum)) continue;
|
|
7143
7285
|
seenLines.add(lineNum);
|
|
7144
7286
|
findings.push({
|
|
@@ -7175,7 +7317,7 @@ var pyParamikoAutoAdd = {
|
|
|
7175
7317
|
let m;
|
|
7176
7318
|
while ((m = pattern.exec(content)) !== null) {
|
|
7177
7319
|
if (isCommentLine(content, m.index)) continue;
|
|
7178
|
-
const lineNum = content
|
|
7320
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7179
7321
|
if (seenLines.has(lineNum)) continue;
|
|
7180
7322
|
seenLines.add(lineNum);
|
|
7181
7323
|
findings.push({
|
|
@@ -7208,7 +7350,7 @@ var pyDjangoAllowedHostsWildcard = {
|
|
|
7208
7350
|
let m;
|
|
7209
7351
|
while ((m = pattern.exec(content)) !== null) {
|
|
7210
7352
|
if (isCommentLine(content, m.index)) continue;
|
|
7211
|
-
const lineNum = content
|
|
7353
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7212
7354
|
findings.push({
|
|
7213
7355
|
rule: "VC196",
|
|
7214
7356
|
title: pyDjangoAllowedHostsWildcard.title,
|
|
@@ -7246,7 +7388,7 @@ var pyJWTDecodeWeakConfig = {
|
|
|
7246
7388
|
if (isCommentLine(content, m.index)) continue;
|
|
7247
7389
|
const args = m[1];
|
|
7248
7390
|
if (/\balgorithms\s*=/.test(args)) continue;
|
|
7249
|
-
const lineNum = content
|
|
7391
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7250
7392
|
if (seenLines.has(lineNum)) continue;
|
|
7251
7393
|
seenLines.add(lineNum);
|
|
7252
7394
|
findings.push({
|
|
@@ -7297,7 +7439,7 @@ var llmPromptInjection = {
|
|
|
7297
7439
|
let m;
|
|
7298
7440
|
while ((m = pattern.exec(content)) !== null) {
|
|
7299
7441
|
if (isCommentLine(content, m.index)) continue;
|
|
7300
|
-
const lineNum = content
|
|
7442
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7301
7443
|
if (seenLines.has(lineNum)) continue;
|
|
7302
7444
|
seenLines.add(lineNum);
|
|
7303
7445
|
findings.push({
|
|
@@ -7336,7 +7478,7 @@ var llmSystemPromptInjection = {
|
|
|
7336
7478
|
let m;
|
|
7337
7479
|
while ((m = pattern.exec(content)) !== null) {
|
|
7338
7480
|
if (isCommentLine(content, m.index)) continue;
|
|
7339
|
-
const lineNum = content
|
|
7481
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7340
7482
|
findings.push({
|
|
7341
7483
|
rule: "VC199",
|
|
7342
7484
|
title: llmSystemPromptInjection.title,
|
|
@@ -7378,7 +7520,7 @@ var llmOutputAsHTML = {
|
|
|
7378
7520
|
let m;
|
|
7379
7521
|
while ((m = pattern.exec(content)) !== null) {
|
|
7380
7522
|
if (isCommentLine(content, m.index)) continue;
|
|
7381
|
-
const lineNum = content
|
|
7523
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7382
7524
|
if (seenLines.has(lineNum)) continue;
|
|
7383
7525
|
seenLines.add(lineNum);
|
|
7384
7526
|
findings.push({
|
|
@@ -7415,7 +7557,7 @@ var vectorStoreQueryNoUserFilter = {
|
|
|
7415
7557
|
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;
|
|
7416
7558
|
if (/\bnamespace\s*[:=]\s*[`"']?[^,)`"']*(?:user|tenant|org|owner|account|customer|workspace)/i.test(args)) continue;
|
|
7417
7559
|
if (/\bfilter\s*[:=][\s\S]*?(?:\buser|\btenant|\borg|\bowner|\baccount|\bcustomer|\bworkspace)/i.test(args)) continue;
|
|
7418
|
-
const lineNum = content
|
|
7560
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7419
7561
|
findings.push({
|
|
7420
7562
|
rule: "VC201",
|
|
7421
7563
|
title: vectorStoreQueryNoUserFilter.title,
|
|
@@ -7452,7 +7594,7 @@ var vectorStoreUpsertNoMetadata = {
|
|
|
7452
7594
|
if (/\bnamespace\s*[:=]\s*[`"']?[^,)`"']*(?:user|tenant|org)/i.test(args)) {
|
|
7453
7595
|
continue;
|
|
7454
7596
|
}
|
|
7455
|
-
const lineNum = content
|
|
7597
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7456
7598
|
findings.push({
|
|
7457
7599
|
rule: "VC202",
|
|
7458
7600
|
title: vectorStoreUpsertNoMetadata.title,
|
|
@@ -7506,7 +7648,7 @@ var llmCallNoMaxTokens = {
|
|
|
7506
7648
|
if (depth !== 0) continue;
|
|
7507
7649
|
const args = content.substring(openIdx + 1, i).replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "").replace(/#[^\n]*/g, "");
|
|
7508
7650
|
if (TOKEN_KW_RE.test(args)) continue;
|
|
7509
|
-
const lineNum = content
|
|
7651
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7510
7652
|
findings.push({
|
|
7511
7653
|
rule: "VC203",
|
|
7512
7654
|
title: llmCallNoMaxTokens.title,
|
|
@@ -7546,7 +7688,7 @@ var graphqlNoDepthLimit = {
|
|
|
7546
7688
|
let m;
|
|
7547
7689
|
while ((m = anchorRe.exec(content)) !== null) {
|
|
7548
7690
|
if (isCommentLine(content, m.index)) continue;
|
|
7549
|
-
const lineNum = content
|
|
7691
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7550
7692
|
findings.push({
|
|
7551
7693
|
rule: "VC204",
|
|
7552
7694
|
title: graphqlNoDepthLimit.title,
|
|
@@ -7580,7 +7722,7 @@ var graphqlNoComplexityLimit = {
|
|
|
7580
7722
|
let m;
|
|
7581
7723
|
while ((m = anchorRe.exec(content)) !== null) {
|
|
7582
7724
|
if (isCommentLine(content, m.index)) continue;
|
|
7583
|
-
const lineNum = content
|
|
7725
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7584
7726
|
findings.push({
|
|
7585
7727
|
rule: "VC205",
|
|
7586
7728
|
title: graphqlNoComplexityLimit.title,
|
|
@@ -7610,7 +7752,7 @@ var graphqlCSRFDisabled = {
|
|
|
7610
7752
|
let m;
|
|
7611
7753
|
while ((m = pattern.exec(content)) !== null) {
|
|
7612
7754
|
if (isCommentLine(content, m.index)) continue;
|
|
7613
|
-
const lineNum = content
|
|
7755
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7614
7756
|
findings.push({
|
|
7615
7757
|
rule: "VC206",
|
|
7616
7758
|
title: graphqlCSRFDisabled.title,
|
|
@@ -7706,7 +7848,7 @@ var webhookMissingIdempotency = {
|
|
|
7706
7848
|
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 [];
|
|
7707
7849
|
const m = content.match(/constructEvent|new\s+Webhook\s*\(|verifyHeader/i);
|
|
7708
7850
|
if (!m || m.index === void 0) return [];
|
|
7709
|
-
const line = content
|
|
7851
|
+
const line = lineNumberAt(content, m.index);
|
|
7710
7852
|
return filterSilenced([{
|
|
7711
7853
|
rule: "VC209",
|
|
7712
7854
|
title: webhookMissingIdempotency.title,
|
|
@@ -7733,7 +7875,7 @@ var middlewareMatcherExcludesApi = {
|
|
|
7733
7875
|
if (!/\(\?!\s*[^)]*\bapi\b/.test(content)) return [];
|
|
7734
7876
|
const m = content.match(/matcher\s*:/);
|
|
7735
7877
|
if (!m || m.index === void 0) return [];
|
|
7736
|
-
const line = content
|
|
7878
|
+
const line = lineNumberAt(content, m.index);
|
|
7737
7879
|
return filterSilenced([{
|
|
7738
7880
|
rule: "VC210",
|
|
7739
7881
|
title: middlewareMatcherExcludesApi.title,
|
|
@@ -7771,7 +7913,7 @@ var secretInURLParam = {
|
|
|
7771
7913
|
if (isCommentLine(content, m.index)) continue;
|
|
7772
7914
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7773
7915
|
if (isInlineSilenced(content, m.index, "VC146")) continue;
|
|
7774
|
-
const lineNum = content
|
|
7916
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7775
7917
|
findings.push({
|
|
7776
7918
|
rule: "VC146",
|
|
7777
7919
|
title: this.title,
|
|
@@ -7802,7 +7944,7 @@ var secretLoggedToConsole = {
|
|
|
7802
7944
|
while ((m = pattern.exec(content)) !== null) {
|
|
7803
7945
|
if (isCommentLine(content, m.index)) continue;
|
|
7804
7946
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7805
|
-
const lineNum = content
|
|
7947
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7806
7948
|
findings.push({
|
|
7807
7949
|
rule: "VC147",
|
|
7808
7950
|
title: this.title,
|
|
@@ -7832,7 +7974,7 @@ var secretInErrorResponse = {
|
|
|
7832
7974
|
while ((m = pattern.exec(content)) !== null) {
|
|
7833
7975
|
if (isCommentLine(content, m.index)) continue;
|
|
7834
7976
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7835
|
-
const lineNum = content
|
|
7977
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7836
7978
|
findings.push({
|
|
7837
7979
|
rule: "VC148",
|
|
7838
7980
|
title: this.title,
|
|
@@ -7871,7 +8013,7 @@ var secretInBundleConfig = {
|
|
|
7871
8013
|
while ((m = re.exec(content)) !== null) {
|
|
7872
8014
|
if (isCommentLine(content, m.index)) continue;
|
|
7873
8015
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7874
|
-
const lineNum = content
|
|
8016
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7875
8017
|
findings.push({
|
|
7876
8018
|
rule: "VC149",
|
|
7877
8019
|
title: this.title,
|
|
@@ -7909,7 +8051,7 @@ var secretInHTMLAttribute = {
|
|
|
7909
8051
|
while ((m = re.exec(content)) !== null) {
|
|
7910
8052
|
if (isCommentLine(content, m.index)) continue;
|
|
7911
8053
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7912
|
-
const lineNum = content
|
|
8054
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7913
8055
|
findings.push({
|
|
7914
8056
|
rule: "VC150",
|
|
7915
8057
|
title: this.title,
|
|
@@ -7936,9 +8078,9 @@ var secretInCLIArgument = {
|
|
|
7936
8078
|
if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb)$/)) return [];
|
|
7937
8079
|
const findings = [];
|
|
7938
8080
|
const patterns = [
|
|
7939
|
-
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]
|
|
7940
|
-
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]
|
|
7941
|
-
/(?:subprocess|os\.system|os\.popen)\s*\([^)]
|
|
8081
|
+
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]{0,500}\$\{[^}]{0,200}(?:api[_-]?key|secret|token|password|credentials)/gi,
|
|
8082
|
+
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]{0,500}["']\s*\+\s*(?:api[_-]?key|secret|token|password|credentials)/gi,
|
|
8083
|
+
/(?:subprocess|os\.system|os\.popen)\s*\([^)]{0,500}\{[^}]{0,200}(?:api[_-]?key|secret|token|password|credentials)/gi
|
|
7942
8084
|
];
|
|
7943
8085
|
for (const p of patterns) {
|
|
7944
8086
|
let m;
|
|
@@ -7946,7 +8088,7 @@ var secretInCLIArgument = {
|
|
|
7946
8088
|
while ((m = re.exec(content)) !== null) {
|
|
7947
8089
|
if (isCommentLine(content, m.index)) continue;
|
|
7948
8090
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7949
|
-
const lineNum = content
|
|
8091
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7950
8092
|
findings.push({
|
|
7951
8093
|
rule: "VC151",
|
|
7952
8094
|
title: this.title,
|
|
@@ -7992,7 +8134,7 @@ var webhookSignatureVerification = {
|
|
|
7992
8134
|
if (svc.verify.test(content)) continue;
|
|
7993
8135
|
const m = content.match(/export\s+(?:async\s+)?function\s+POST/);
|
|
7994
8136
|
if (!m || m.index === void 0) continue;
|
|
7995
|
-
const lineNum = content
|
|
8137
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7996
8138
|
findings.push({
|
|
7997
8139
|
rule: "VC152",
|
|
7998
8140
|
title: `${svc.name} Webhook Missing Signature Verification`,
|
|
@@ -8059,7 +8201,7 @@ var missingRequestValidation = {
|
|
|
8059
8201
|
if (/if\s*\(\s*!(?:body|parsed|data|payload)\.|throw.*(?:missing|invalid|required)/i.test(content)) return [];
|
|
8060
8202
|
const m = content.match(/export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH)/);
|
|
8061
8203
|
if (!m || m.index === void 0) return [];
|
|
8062
|
-
const lineNum = content
|
|
8204
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
8063
8205
|
return [{
|
|
8064
8206
|
rule: "VC154",
|
|
8065
8207
|
title: this.title,
|
|
@@ -8086,7 +8228,7 @@ var missingAIRateLimit = {
|
|
|
8086
8228
|
if (/rateLimit|rateLimiter|throttle|checkRateLimit|limiter|slowDown|express-rate-limit/i.test(content)) return [];
|
|
8087
8229
|
const m = content.match(/export\s+(?:async\s+)?function\s+(?:POST|GET)/i) || content.match(/\.(post|get)\s*\(/i);
|
|
8088
8230
|
if (!m || m.index === void 0) return [];
|
|
8089
|
-
const lineNum = content
|
|
8231
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
8090
8232
|
return [{
|
|
8091
8233
|
rule: "VC155",
|
|
8092
8234
|
title: this.title,
|
|
@@ -8115,7 +8257,7 @@ var missingPagination = {
|
|
|
8115
8257
|
if (/findUnique|findFirst|findById|\.findOne|WHERE.*id\s*=/i.test(content)) return [];
|
|
8116
8258
|
const m = content.match(/export\s+(?:async\s+)?function\s+GET/);
|
|
8117
8259
|
if (!m || m.index === void 0) return [];
|
|
8118
|
-
const lineNum = content
|
|
8260
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
8119
8261
|
return [{
|
|
8120
8262
|
rule: "VC156",
|
|
8121
8263
|
title: this.title,
|
|
@@ -8176,7 +8318,7 @@ var insecureDirectObjectReference = {
|
|
|
8176
8318
|
if (/requireUser|requireUserForApi|getServerSession|auth\(\)/i.test(content)) return [];
|
|
8177
8319
|
const m = content.match(/params\.id|params\.(?:slug|uuid)|req\.params\./);
|
|
8178
8320
|
if (!m || m.index === void 0) return [];
|
|
8179
|
-
const lineNum = content
|
|
8321
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
8180
8322
|
return [{
|
|
8181
8323
|
rule: "VC158",
|
|
8182
8324
|
title: this.title,
|
|
@@ -8482,6 +8624,17 @@ function runCustomRules(content, filePath, disabledRules = [], tier = "free", ex
|
|
|
8482
8624
|
return findings;
|
|
8483
8625
|
}
|
|
8484
8626
|
if (/pro-rules-bundle|\.bundle\./i.test(filePath)) return findings;
|
|
8627
|
+
let maxLineLen = 0;
|
|
8628
|
+
{
|
|
8629
|
+
let lineStart = 0;
|
|
8630
|
+
for (let i = 0; i <= content.length; i++) {
|
|
8631
|
+
if (i === content.length || content.charCodeAt(i) === 10) {
|
|
8632
|
+
if (i - lineStart > maxLineLen) maxLineLen = i - lineStart;
|
|
8633
|
+
lineStart = i + 1;
|
|
8634
|
+
}
|
|
8635
|
+
}
|
|
8636
|
+
}
|
|
8637
|
+
if (maxLineLen > 5e4) return findings;
|
|
8485
8638
|
if (/onboarding|demo-data|example-vulnerable|code-sample/i.test(filePath)) return findings;
|
|
8486
8639
|
const ruleset = tier === "pro" && extraRules.length > 0 ? [...freeRules, ...extraRules] : freeRules;
|
|
8487
8640
|
for (const rule of ruleset) {
|