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.js
CHANGED
|
@@ -1,13 +1,48 @@
|
|
|
1
1
|
// src/snippet.ts
|
|
2
|
-
|
|
2
|
+
var cacheContent = null;
|
|
3
|
+
var cacheLines = [];
|
|
4
|
+
var cacheOffsets = [];
|
|
5
|
+
function lineIndex(content) {
|
|
6
|
+
if (content === cacheContent) return { lines: cacheLines, offsets: cacheOffsets };
|
|
3
7
|
const lines = content.split("\n");
|
|
8
|
+
const offsets = new Array(lines.length);
|
|
9
|
+
let off = 0;
|
|
10
|
+
for (let i = 0; i < lines.length; i++) {
|
|
11
|
+
offsets[i] = off;
|
|
12
|
+
off += lines[i].length + 1;
|
|
13
|
+
}
|
|
14
|
+
cacheContent = content;
|
|
15
|
+
cacheLines = lines;
|
|
16
|
+
cacheOffsets = offsets;
|
|
17
|
+
return { lines, offsets };
|
|
18
|
+
}
|
|
19
|
+
function lineNumberAt(content, index) {
|
|
20
|
+
const { offsets } = lineIndex(content);
|
|
21
|
+
let lo = 0;
|
|
22
|
+
let hi = offsets.length - 1;
|
|
23
|
+
let ans = 0;
|
|
24
|
+
while (lo <= hi) {
|
|
25
|
+
const mid = lo + hi >> 1;
|
|
26
|
+
if (offsets[mid] <= index) {
|
|
27
|
+
ans = mid;
|
|
28
|
+
lo = mid + 1;
|
|
29
|
+
} else {
|
|
30
|
+
hi = mid - 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return ans + 1;
|
|
34
|
+
}
|
|
35
|
+
function getSnippet(content, line, contextLines = 2) {
|
|
36
|
+
const { lines } = lineIndex(content);
|
|
4
37
|
const start = Math.max(0, line - 1 - contextLines);
|
|
5
38
|
const end = Math.min(lines.length, line + contextLines);
|
|
6
|
-
|
|
7
|
-
|
|
39
|
+
const out = [];
|
|
40
|
+
for (let i = start; i < end; i++) {
|
|
41
|
+
const lineNum = i + 1;
|
|
8
42
|
const marker = lineNum === line ? ">" : " ";
|
|
9
|
-
|
|
10
|
-
}
|
|
43
|
+
out.push(`${marker} ${lineNum.toString().padStart(4)} | ${lines[i]}`);
|
|
44
|
+
}
|
|
45
|
+
return out.join("\n");
|
|
11
46
|
}
|
|
12
47
|
|
|
13
48
|
// src/rule-impacts.ts
|
|
@@ -815,7 +850,7 @@ var SERVER_SIDE_PATH_RE = new RegExp(
|
|
|
815
850
|
].join("|"),
|
|
816
851
|
"i"
|
|
817
852
|
);
|
|
818
|
-
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/;
|
|
853
|
+
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/;
|
|
819
854
|
function isServerSideFile(filePath, content) {
|
|
820
855
|
if (isTestFile(filePath)) return false;
|
|
821
856
|
if (SERVER_SIDE_PATH_RE.test(filePath)) return true;
|
|
@@ -862,13 +897,23 @@ function filterSilenced(matches, content, ruleId) {
|
|
|
862
897
|
}
|
|
863
898
|
function findMatches(content, pattern, rule, filePath, fixTemplate) {
|
|
864
899
|
const matches = [];
|
|
865
|
-
const lines = content.split("\n");
|
|
866
900
|
let m;
|
|
867
901
|
const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
902
|
+
let scanned = 0;
|
|
903
|
+
let lineNum = 1;
|
|
904
|
+
let lastMatchIndex = -1;
|
|
905
|
+
const MAX_MATCHES = 500;
|
|
868
906
|
while ((m = re.exec(content)) !== null) {
|
|
907
|
+
if (m.index === lastMatchIndex && m[0].length === 0) {
|
|
908
|
+
re.lastIndex++;
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
lastMatchIndex = m.index;
|
|
869
912
|
if (isCommentLine(content, m.index)) continue;
|
|
870
913
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
871
|
-
|
|
914
|
+
for (; scanned < m.index; scanned++) {
|
|
915
|
+
if (content.charCodeAt(scanned) === 10) lineNum++;
|
|
916
|
+
}
|
|
872
917
|
matches.push({
|
|
873
918
|
rule: rule.id,
|
|
874
919
|
title: rule.title,
|
|
@@ -879,6 +924,7 @@ function findMatches(content, pattern, rule, filePath, fixTemplate) {
|
|
|
879
924
|
snippet: getSnippet(content, lineNum),
|
|
880
925
|
fix: fixTemplate?.(m)
|
|
881
926
|
});
|
|
927
|
+
if (matches.length >= MAX_MATCHES) break;
|
|
882
928
|
}
|
|
883
929
|
return matches;
|
|
884
930
|
}
|
|
@@ -894,10 +940,26 @@ function astMatch(content, filePath, line, rule, fix) {
|
|
|
894
940
|
fix
|
|
895
941
|
};
|
|
896
942
|
}
|
|
943
|
+
var parseCacheContent = null;
|
|
944
|
+
var parseCachePath = null;
|
|
945
|
+
var parseCacheResult = null;
|
|
946
|
+
var parseCacheValid = false;
|
|
897
947
|
function tryParse(content, filePath) {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
948
|
+
if (parseCacheValid && content === parseCacheContent && filePath === parseCachePath) {
|
|
949
|
+
return parseCacheResult;
|
|
950
|
+
}
|
|
951
|
+
let result = null;
|
|
952
|
+
try {
|
|
953
|
+
const parsed = parseFile(content, filePath);
|
|
954
|
+
result = parsed ? { parsed, taint: buildTaintMap(parsed) } : null;
|
|
955
|
+
} catch {
|
|
956
|
+
result = null;
|
|
957
|
+
}
|
|
958
|
+
parseCacheResult = result;
|
|
959
|
+
parseCacheContent = content;
|
|
960
|
+
parseCachePath = filePath;
|
|
961
|
+
parseCacheValid = true;
|
|
962
|
+
return result;
|
|
901
963
|
}
|
|
902
964
|
var hardcodedSecrets = {
|
|
903
965
|
id: "VC001",
|
|
@@ -1262,9 +1324,10 @@ var xssVulnerability = {
|
|
|
1262
1324
|
category: "Injection",
|
|
1263
1325
|
description: "Rendering user input without sanitization allows attackers to inject malicious scripts.",
|
|
1264
1326
|
check(content, filePath) {
|
|
1327
|
+
const hasSanitizerBypass = /bypassSecurityTrust(?:Html|Script|Style|Url|ResourceUrl)\s*\(/.test(content);
|
|
1265
1328
|
if (/(?:sanitize|purify|escape|xss)/i.test(filePath)) return [];
|
|
1266
|
-
if (/DOMPurify\.sanitize|sanitizeHtml|xss\(|escapeHtml/i.test(content)) return [];
|
|
1267
|
-
if (/(?:import|require)\s*\(
|
|
1329
|
+
if (!hasSanitizerBypass && /DOMPurify\.sanitize|sanitizeHtml|xss\(|escapeHtml/i.test(content)) return [];
|
|
1330
|
+
if (!hasSanitizerBypass && /(?:import|require)\s*\(?[^\n]{0,200}(?:DOMPurify|dompurify|sanitize|sanitizer)/i.test(content)) return [];
|
|
1268
1331
|
const patterns = [
|
|
1269
1332
|
// React dangerouslySetInnerHTML
|
|
1270
1333
|
/dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:/g,
|
|
@@ -1275,7 +1338,12 @@ var xssVulnerability = {
|
|
|
1275
1338
|
// v-html in Vue
|
|
1276
1339
|
/v-html\s*=/g,
|
|
1277
1340
|
// {@html} in Svelte
|
|
1278
|
-
/\{@html\s/g
|
|
1341
|
+
/\{@html\s/g,
|
|
1342
|
+
// Angular DomSanitizer escape hatches — bypassSecurityTrustHtml /
|
|
1343
|
+
// bypassSecurityTrustScript / ...Url / ...ResourceUrl / ...Style. These
|
|
1344
|
+
// mark a value as trusted and skip Angular's built-in sanitization;
|
|
1345
|
+
// passing user input through one is the canonical Angular DOM-XSS sink.
|
|
1346
|
+
/bypassSecurityTrust(?:Html|Script|Style|Url|ResourceUrl)\s*\(/g
|
|
1279
1347
|
];
|
|
1280
1348
|
const allLines = content.split("\n");
|
|
1281
1349
|
const matches = [];
|
|
@@ -1288,9 +1356,9 @@ var xssVulnerability = {
|
|
|
1288
1356
|
() => "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."
|
|
1289
1357
|
);
|
|
1290
1358
|
for (const m of raw) {
|
|
1291
|
-
const lineText = allLines[m.line - 1] || "";
|
|
1359
|
+
const lineText = (allLines[m.line - 1] || "").slice(0, 4e3);
|
|
1292
1360
|
if (/\.innerHTML\s*=\s*['"]/.test(lineText) && !/\$\{/.test(lineText)) continue;
|
|
1293
|
-
if (/\.innerHTML\s*=\s*['"][^'"]
|
|
1361
|
+
if (/\.innerHTML\s*=\s*['"][^'"]{0,2000}['"]\s*$/.test(lineText)) continue;
|
|
1294
1362
|
if (/dangerouslySetInnerHTML/.test(lineText)) {
|
|
1295
1363
|
const windowText = allLines.slice(m.line - 1, m.line + 2).join("\n");
|
|
1296
1364
|
if (/JSON\.stringify/i.test(windowText)) continue;
|
|
@@ -1541,7 +1609,7 @@ var evalUsage = {
|
|
|
1541
1609
|
/new\s+Function\s*\(\s*(?!["'`])/g
|
|
1542
1610
|
];
|
|
1543
1611
|
const hasEvalInString = /["'`]eval(?:-source-map|["'`])/i.test(content);
|
|
1544
|
-
if (hasEvalInString && !/\beval\s*\([^)]
|
|
1612
|
+
if (hasEvalInString && !/\beval\s*\([^)]{0,500}(?:req\.|body\.|input|params|user|data)/i.test(content)) return [];
|
|
1545
1613
|
const matches = [];
|
|
1546
1614
|
for (const p of patterns) {
|
|
1547
1615
|
const rawMatches = findMatches(
|
|
@@ -1582,7 +1650,7 @@ var unvalidatedRedirect = {
|
|
|
1582
1650
|
const isPureLiteral = /^["'`][^"'`]*["'`]$/.test(value);
|
|
1583
1651
|
if (!hasInterpolation && isPureLiteral) continue;
|
|
1584
1652
|
if (isInlineSilenced(content, m.index, "VC016")) continue;
|
|
1585
|
-
const line = content
|
|
1653
|
+
const line = lineNumberAt(content, m.index);
|
|
1586
1654
|
matches.push({
|
|
1587
1655
|
rule: "VC016",
|
|
1588
1656
|
title: unvalidatedRedirect.title,
|
|
@@ -1805,11 +1873,11 @@ var prototypePollution = {
|
|
|
1805
1873
|
];
|
|
1806
1874
|
const hasValidation = /schema|validate|sanitize|whitelist|allowedKeys|pick\(|Object\.freeze|zod|yup|joi|ajv/i.test(content);
|
|
1807
1875
|
if (!hasValidation) {
|
|
1808
|
-
const hasUnsafeMerge = /Object\.assign\s*\([^)]
|
|
1876
|
+
const hasUnsafeMerge = /Object\.assign\s*\([^)]{0,500}JSON\.parse|\.\.\.JSON\.parse|\{[^\n]{0,300}\.\.\.(?:stored|saved|cached|parsed|data)/i.test(content);
|
|
1809
1877
|
if (hasUnsafeMerge) {
|
|
1810
1878
|
matches.push(...findMatches(
|
|
1811
1879
|
content,
|
|
1812
|
-
/Object\.assign\s*\([^)]
|
|
1880
|
+
/Object\.assign\s*\([^)]{0,500}JSON\.parse|\.\.\.JSON\.parse/g,
|
|
1813
1881
|
prototypePollution,
|
|
1814
1882
|
filePath,
|
|
1815
1883
|
() => "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."
|
|
@@ -3355,7 +3423,7 @@ var dangerousInnerHTML = {
|
|
|
3355
3423
|
if (/^[A-Z][A-Z0-9_]+$/.test(value)) continue;
|
|
3356
3424
|
if (/^["'`][^$]*["'`]$/.test(value)) continue;
|
|
3357
3425
|
if (/JSON\.stringify/i.test(value)) continue;
|
|
3358
|
-
const lineNum = content
|
|
3426
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
3359
3427
|
findings.push({
|
|
3360
3428
|
rule: "VC063",
|
|
3361
3429
|
title: dangerousInnerHTML.title,
|
|
@@ -4018,17 +4086,37 @@ var ssti = {
|
|
|
4018
4086
|
(call, line) => {
|
|
4019
4087
|
const first = call.arguments[0];
|
|
4020
4088
|
if (!first || first.type === "SpreadElement") return;
|
|
4021
|
-
if (
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4089
|
+
if (taint.isTainted(first)) {
|
|
4090
|
+
if (matches.some((m) => m.line === line)) return;
|
|
4091
|
+
matches.push(
|
|
4092
|
+
astMatch(
|
|
4093
|
+
content,
|
|
4094
|
+
filePath,
|
|
4095
|
+
line,
|
|
4096
|
+
ssti,
|
|
4097
|
+
"Compile templates from a trusted, static source (a file path or a constant string). Pass user data only as context values."
|
|
4098
|
+
)
|
|
4099
|
+
);
|
|
4100
|
+
return;
|
|
4101
|
+
}
|
|
4102
|
+
const opts = call.arguments[1];
|
|
4103
|
+
if (opts && opts.type === "ObjectExpression") {
|
|
4104
|
+
const hasTaintedSpread = opts.properties.some(
|
|
4105
|
+
(p) => p.type === "SpreadElement" && taint.isTainted(p.argument)
|
|
4106
|
+
);
|
|
4107
|
+
if (hasTaintedSpread) {
|
|
4108
|
+
if (matches.some((m) => m.line === line)) return;
|
|
4109
|
+
matches.push(
|
|
4110
|
+
astMatch(
|
|
4111
|
+
content,
|
|
4112
|
+
filePath,
|
|
4113
|
+
line,
|
|
4114
|
+
ssti,
|
|
4115
|
+
"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 })."
|
|
4116
|
+
)
|
|
4117
|
+
);
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4032
4120
|
}
|
|
4033
4121
|
);
|
|
4034
4122
|
return filterSilenced(matches, content, "VC082");
|
|
@@ -4077,7 +4165,7 @@ var missingSRI = {
|
|
|
4077
4165
|
const re = new RegExp(scriptPattern.source, scriptPattern.flags);
|
|
4078
4166
|
while ((m = re.exec(content)) !== null) {
|
|
4079
4167
|
if (!m[0].includes("integrity")) {
|
|
4080
|
-
const lineNum = content
|
|
4168
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
4081
4169
|
matches.push({
|
|
4082
4170
|
rule: "VC084",
|
|
4083
4171
|
title: missingSRI.title,
|
|
@@ -4291,16 +4379,61 @@ var unprotectedDownload = {
|
|
|
4291
4379
|
if (!/(?:download|sendFile|send_file)/i.test(content)) return [];
|
|
4292
4380
|
if (!isServerSideFile(filePath, content)) return [];
|
|
4293
4381
|
const matches = [];
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4382
|
+
const hasContainmentCheck = /\.startsWith\s*\(/.test(content) || /realpath/i.test(content) || /includes\s*\(\s*["']\.\./.test(content);
|
|
4383
|
+
if (/(?:sendFile|download|send_file)\s*\([^)]*(?:req\.|params\.|query\.|body\.)/i.test(content) && !hasContainmentCheck) {
|
|
4384
|
+
matches.push(...findMatches(
|
|
4385
|
+
content,
|
|
4386
|
+
/(?:sendFile|download|send_file)\s*\(/gi,
|
|
4387
|
+
unprotectedDownload,
|
|
4388
|
+
filePath,
|
|
4389
|
+
() => "Validate file paths: const safePath = path.resolve(DOWNLOAD_DIR, filename); if (!safePath.startsWith(DOWNLOAD_DIR + path.sep)) throw new Error('Invalid path');"
|
|
4390
|
+
));
|
|
4391
|
+
}
|
|
4392
|
+
if (!hasContainmentCheck && /\b(?:sendFile|download)\s*\(/.test(content)) {
|
|
4393
|
+
const ctx = tryParse(content, filePath);
|
|
4394
|
+
if (ctx) {
|
|
4395
|
+
const { parsed, taint } = ctx;
|
|
4396
|
+
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);
|
|
4397
|
+
const isUnsafePathBuild = (node) => {
|
|
4398
|
+
if (node.type !== "CallExpression") return false;
|
|
4399
|
+
const callee = node.callee;
|
|
4400
|
+
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");
|
|
4401
|
+
if (!isPathBuild) return false;
|
|
4402
|
+
const rootedAtDirname = node.arguments.some(
|
|
4403
|
+
(a) => a.type === "Identifier" && (a.name === "__dirname" || a.name === "__filename")
|
|
4404
|
+
);
|
|
4405
|
+
if (rootedAtDirname) return false;
|
|
4406
|
+
return node.arguments.some(
|
|
4407
|
+
(a) => a.type !== "SpreadElement" && a.type !== "StringLiteral" && a.type !== "TemplateLiteral"
|
|
4408
|
+
);
|
|
4409
|
+
};
|
|
4410
|
+
const argIsTainted = (node) => {
|
|
4411
|
+
if (taint.isTainted(node)) return true;
|
|
4412
|
+
if (readsUserInput && isUnsafePathBuild(node)) return true;
|
|
4413
|
+
if (node.type === "CallExpression") {
|
|
4414
|
+
return node.arguments.some((a) => a.type !== "SpreadElement" && argIsTainted(a));
|
|
4415
|
+
}
|
|
4416
|
+
return false;
|
|
4417
|
+
};
|
|
4418
|
+
visitCalls(
|
|
4419
|
+
parsed,
|
|
4420
|
+
(callee) => callee.type === "MemberExpression" && callee.property.type === "Identifier" && (callee.property.name === "sendFile" || callee.property.name === "download"),
|
|
4421
|
+
(call, line) => {
|
|
4422
|
+
const first = call.arguments[0];
|
|
4423
|
+
if (!first || first.type === "SpreadElement") return;
|
|
4424
|
+
if (!argIsTainted(first)) return;
|
|
4425
|
+
if (matches.some((m) => m.line === line)) return;
|
|
4426
|
+
matches.push(
|
|
4427
|
+
astMatch(
|
|
4428
|
+
content,
|
|
4429
|
+
filePath,
|
|
4430
|
+
line,
|
|
4431
|
+
unprotectedDownload,
|
|
4432
|
+
"Validate file paths: const safePath = path.resolve(DOWNLOAD_DIR, filename); if (!safePath.startsWith(DOWNLOAD_DIR + path.sep)) throw new Error('Invalid path');"
|
|
4433
|
+
)
|
|
4434
|
+
);
|
|
4435
|
+
}
|
|
4436
|
+
);
|
|
4304
4437
|
}
|
|
4305
4438
|
}
|
|
4306
4439
|
return matches;
|
|
@@ -4903,7 +5036,7 @@ var s3BucketNoEncryption = {
|
|
|
4903
5036
|
const blockEnd = Math.min(m.index + 2e3, content.length);
|
|
4904
5037
|
const blockContent = content.substring(m.index, blockEnd);
|
|
4905
5038
|
if (!/server_side_encryption/.test(blockContent)) {
|
|
4906
|
-
const lineNum = content
|
|
5039
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
4907
5040
|
matches.push({
|
|
4908
5041
|
rule: "VC107",
|
|
4909
5042
|
title: s3BucketNoEncryption.title,
|
|
@@ -4934,7 +5067,7 @@ var securityGroupAllInbound = {
|
|
|
4934
5067
|
while ((m = ingressPattern.exec(content)) !== null) {
|
|
4935
5068
|
if (isCommentLine(content, m.index)) continue;
|
|
4936
5069
|
if (/from_port\s*=\s*0/.test(m[0]) || !/from_port/.test(m[0])) {
|
|
4937
|
-
const lineNum = content
|
|
5070
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
4938
5071
|
matches.push({
|
|
4939
5072
|
rule: "VC108",
|
|
4940
5073
|
title: securityGroupAllInbound.title,
|
|
@@ -5021,7 +5154,7 @@ var lambdaWithoutVPC = {
|
|
|
5021
5154
|
const blockEnd = Math.min(m.index + 2e3, content.length);
|
|
5022
5155
|
const blockContent = content.substring(m.index, blockEnd);
|
|
5023
5156
|
if (!/vpc_config\s*\{/.test(blockContent)) {
|
|
5024
|
-
const lineNum = content
|
|
5157
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5025
5158
|
matches.push({
|
|
5026
5159
|
rule: "VC111",
|
|
5027
5160
|
title: lambdaWithoutVPC.title,
|
|
@@ -5051,7 +5184,7 @@ var dockerLatestTag = {
|
|
|
5051
5184
|
while ((m = fromPattern.exec(content)) !== null) {
|
|
5052
5185
|
const image = m[1];
|
|
5053
5186
|
if (image.endsWith(":latest") || !image.includes(":") && !image.startsWith("$")) {
|
|
5054
|
-
const lineNum = content
|
|
5187
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5055
5188
|
matches.push({
|
|
5056
5189
|
rule: "VC112",
|
|
5057
5190
|
title: dockerLatestTag.title,
|
|
@@ -5179,17 +5312,26 @@ var pathTraversal = {
|
|
|
5179
5312
|
const findings = [];
|
|
5180
5313
|
const patterns = [
|
|
5181
5314
|
/(?:readFile|readFileSync|createReadStream|writeFile|writeFileSync|appendFile|unlink|unlinkSync|access|stat|statSync|existsSync)\s*\(\s*(?:req\.|request\.|ctx\.|params\.|query\.)/gi,
|
|
5182
|
-
/(?:path\.join|path\.resolve)\s*\([^)]
|
|
5183
|
-
/(?:res\.sendFile|res\.download)\s*\([^)]
|
|
5315
|
+
/(?:path\.join|path\.resolve)\s*\([^)]{0,500}(?:req\.|request\.|ctx\.|params\.|query\.)[^)]{0,500}\)/gi,
|
|
5316
|
+
/(?:res\.sendFile|res\.download)\s*\([^)]{0,500}(?:req\.|request\.)/gi,
|
|
5184
5317
|
/open\s*\(\s*(?:request\.|params\[|args\.)/gi
|
|
5185
5318
|
];
|
|
5319
|
+
const MAX_MATCHES = 500;
|
|
5186
5320
|
for (const pat of patterns) {
|
|
5321
|
+
if (findings.length >= MAX_MATCHES) break;
|
|
5187
5322
|
let m;
|
|
5323
|
+
let lastIdx = -1;
|
|
5188
5324
|
while ((m = pat.exec(content)) !== null) {
|
|
5325
|
+
if (findings.length >= MAX_MATCHES) break;
|
|
5326
|
+
if (m.index === lastIdx && m[0].length === 0) {
|
|
5327
|
+
pat.lastIndex++;
|
|
5328
|
+
continue;
|
|
5329
|
+
}
|
|
5330
|
+
lastIdx = m.index;
|
|
5189
5331
|
if (isCommentLine(content, m.index)) continue;
|
|
5190
5332
|
const surrounding = content.substring(Math.max(0, m.index - 200), m.index + 200);
|
|
5191
|
-
if (/path\.normalize|sanitize|\.replace\(\s*['"]
|
|
5192
|
-
const lineNum = content
|
|
5333
|
+
if (/path\.normalize|sanitize|\.replace\(\s*['"]\.\.|\.startsWith\s*\(|realpath/i.test(surrounding)) continue;
|
|
5334
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5193
5335
|
findings.push({
|
|
5194
5336
|
rule: "VC117",
|
|
5195
5337
|
title: pathTraversal.title,
|
|
@@ -5220,7 +5362,7 @@ var piiInLogs = {
|
|
|
5220
5362
|
while ((m = logPattern.exec(content)) !== null) {
|
|
5221
5363
|
if (isCommentLine(content, m.index)) continue;
|
|
5222
5364
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
5223
|
-
const lineNum = content
|
|
5365
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5224
5366
|
findings.push({
|
|
5225
5367
|
rule: "VC118",
|
|
5226
5368
|
title: piiInLogs.title,
|
|
@@ -5258,7 +5400,7 @@ var hardcodedOAuthSecret = {
|
|
|
5258
5400
|
while ((m = pat.exec(content)) !== null) {
|
|
5259
5401
|
if (isCommentLine(content, m.index)) continue;
|
|
5260
5402
|
if (/process\.env|os\.environ|ENV\[|getenv|\$\{|<your|CHANGE_ME|xxx|placeholder/i.test(m[0])) continue;
|
|
5261
|
-
const lineNum = content
|
|
5403
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5262
5404
|
findings.push({
|
|
5263
5405
|
rule: "VC119",
|
|
5264
5406
|
title: hardcodedOAuthSecret.title,
|
|
@@ -5290,7 +5432,7 @@ var missingOAuthState = {
|
|
|
5290
5432
|
if (isCommentLine(content, m.index)) continue;
|
|
5291
5433
|
const surrounding = content.substring(m.index, Math.min(content.length, m.index + 500));
|
|
5292
5434
|
if (/state\s*[=:]/i.test(surrounding)) continue;
|
|
5293
|
-
const lineNum = content
|
|
5435
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5294
5436
|
findings.push({
|
|
5295
5437
|
rule: "VC120",
|
|
5296
5438
|
title: missingOAuthState.title,
|
|
@@ -5318,7 +5460,7 @@ var unpinnedGitHubAction = {
|
|
|
5318
5460
|
let m;
|
|
5319
5461
|
while ((m = usesPattern.exec(content)) !== null) {
|
|
5320
5462
|
if (isCommentLine(content, m.index)) continue;
|
|
5321
|
-
const lineNum = content
|
|
5463
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5322
5464
|
findings.push({
|
|
5323
5465
|
rule: "VC121",
|
|
5324
5466
|
title: unpinnedGitHubAction.title,
|
|
@@ -5352,7 +5494,7 @@ var deprecatedTLS = {
|
|
|
5352
5494
|
let m;
|
|
5353
5495
|
while ((m = pat.exec(content)) !== null) {
|
|
5354
5496
|
if (isCommentLine(content, m.index)) continue;
|
|
5355
|
-
const lineNum = content
|
|
5497
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5356
5498
|
findings.push({
|
|
5357
5499
|
rule: "VC122",
|
|
5358
5500
|
title: deprecatedTLS.title,
|
|
@@ -5388,7 +5530,7 @@ var weakRSAKeySize = {
|
|
|
5388
5530
|
let m;
|
|
5389
5531
|
while ((m = pat.exec(content)) !== null) {
|
|
5390
5532
|
if (isCommentLine(content, m.index)) continue;
|
|
5391
|
-
const lineNum = content
|
|
5533
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5392
5534
|
findings.push({
|
|
5393
5535
|
rule: "VC123",
|
|
5394
5536
|
title: weakRSAKeySize.title,
|
|
@@ -5424,7 +5566,7 @@ var ecbModeEncryption = {
|
|
|
5424
5566
|
let m;
|
|
5425
5567
|
while ((m = pat.exec(content)) !== null) {
|
|
5426
5568
|
if (isCommentLine(content, m.index)) continue;
|
|
5427
|
-
const lineNum = content
|
|
5569
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5428
5570
|
findings.push({
|
|
5429
5571
|
rule: "VC124",
|
|
5430
5572
|
title: ecbModeEncryption.title,
|
|
@@ -5455,7 +5597,7 @@ var insecurePasswordReset = {
|
|
|
5455
5597
|
let m;
|
|
5456
5598
|
while ((m = weakTokens.exec(content)) !== null) {
|
|
5457
5599
|
if (isCommentLine(content, m.index)) continue;
|
|
5458
|
-
const lineNum = content
|
|
5600
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5459
5601
|
findings.push({
|
|
5460
5602
|
rule: "VC125",
|
|
5461
5603
|
title: insecurePasswordReset.title,
|
|
@@ -5472,7 +5614,7 @@ var insecurePasswordReset = {
|
|
|
5472
5614
|
if (isCommentLine(content, m.index)) continue;
|
|
5473
5615
|
const surrounding = content.substring(Math.max(0, m.index - 500), m.index);
|
|
5474
5616
|
if (!/(?:reset|forgot).*password/i.test(surrounding)) continue;
|
|
5475
|
-
const lineNum = content
|
|
5617
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5476
5618
|
findings.push({
|
|
5477
5619
|
rule: "VC125",
|
|
5478
5620
|
title: insecurePasswordReset.title,
|
|
@@ -5533,7 +5675,7 @@ var insecureHTTPMethods = {
|
|
|
5533
5675
|
if (isCommentLine(content, m.index)) continue;
|
|
5534
5676
|
const handlerBlock = content.substring(m.index, Math.min(content.length, m.index + 500));
|
|
5535
5677
|
if (/auth|authenticate|authorize|requireAuth|isAuthenticated|protect|guard|middleware|session|jwt|verify|clerk|getAuth/i.test(handlerBlock)) continue;
|
|
5536
|
-
const lineNum = content
|
|
5678
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5537
5679
|
findings.push({
|
|
5538
5680
|
rule: "VC127",
|
|
5539
5681
|
title: insecureHTTPMethods.title,
|
|
@@ -5567,7 +5709,7 @@ var httpRequestSmuggling = {
|
|
|
5567
5709
|
let m;
|
|
5568
5710
|
while ((m = pat.exec(content)) !== null) {
|
|
5569
5711
|
if (isCommentLine(content, m.index)) continue;
|
|
5570
|
-
const lineNum = content
|
|
5712
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5571
5713
|
findings.push({
|
|
5572
5714
|
rule: "VC128",
|
|
5573
5715
|
title: httpRequestSmuggling.title,
|
|
@@ -5601,7 +5743,7 @@ var unencryptedPII = {
|
|
|
5601
5743
|
let m;
|
|
5602
5744
|
while ((m = pat.exec(content)) !== null) {
|
|
5603
5745
|
if (isCommentLine(content, m.index)) continue;
|
|
5604
|
-
const lineNum = content
|
|
5746
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5605
5747
|
findings.push({
|
|
5606
5748
|
rule: "VC129",
|
|
5607
5749
|
title: unencryptedPII.title,
|
|
@@ -5633,7 +5775,7 @@ var missingAuthRateLimit = {
|
|
|
5633
5775
|
if (isCommentLine(content, m.index)) continue;
|
|
5634
5776
|
const surrounding = content.substring(Math.max(0, m.index - 300), Math.min(content.length, m.index + 300));
|
|
5635
5777
|
if (/rateLimit|rateLimiter|throttle|slowDown|express-rate-limit|rate_limit|RateLimiter|limiter/i.test(surrounding)) continue;
|
|
5636
|
-
const lineNum = content
|
|
5778
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5637
5779
|
findings.push({
|
|
5638
5780
|
rule: "VC130",
|
|
5639
5781
|
title: missingAuthRateLimit.title,
|
|
@@ -5671,7 +5813,7 @@ var vulnerableDependencies = {
|
|
|
5671
5813
|
for (const [pat, message] of vulnerablePackages) {
|
|
5672
5814
|
let m;
|
|
5673
5815
|
while ((m = pat.exec(content)) !== null) {
|
|
5674
|
-
const lineNum = content
|
|
5816
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5675
5817
|
findings.push({
|
|
5676
5818
|
rule: "VC131",
|
|
5677
5819
|
title: vulnerableDependencies.title,
|
|
@@ -5703,7 +5845,7 @@ function secretRuleCheck(content, filePath, pattern, ruleId, title, severity, fi
|
|
|
5703
5845
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
5704
5846
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
5705
5847
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
5706
|
-
const lineNum = content
|
|
5848
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5707
5849
|
findings.push({
|
|
5708
5850
|
rule: ruleId,
|
|
5709
5851
|
title,
|
|
@@ -5804,7 +5946,7 @@ var hardcodedGCPServiceAccount = {
|
|
|
5804
5946
|
const findings = [];
|
|
5805
5947
|
const m = content.match(/"type"\s*:\s*"service_account"/);
|
|
5806
5948
|
if (m && m.index !== void 0) {
|
|
5807
|
-
const lineNum = content
|
|
5949
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5808
5950
|
findings.push({
|
|
5809
5951
|
rule: "VC136",
|
|
5810
5952
|
title: this.title,
|
|
@@ -5910,7 +6052,7 @@ var hardcodedDatadogKey = {
|
|
|
5910
6052
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
5911
6053
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
5912
6054
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
5913
|
-
const lineNum = content
|
|
6055
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5914
6056
|
findings.push({
|
|
5915
6057
|
rule: "VC141",
|
|
5916
6058
|
title: this.title,
|
|
@@ -5944,7 +6086,7 @@ var hardcodedVercelToken = {
|
|
|
5944
6086
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
5945
6087
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
5946
6088
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
5947
|
-
const lineNum = content
|
|
6089
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5948
6090
|
findings.push({
|
|
5949
6091
|
rule: "VC142",
|
|
5950
6092
|
title: this.title,
|
|
@@ -5978,7 +6120,7 @@ var hardcodedSupabaseServiceRole = {
|
|
|
5978
6120
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
5979
6121
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
5980
6122
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
5981
|
-
const lineNum = content
|
|
6123
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
5982
6124
|
findings.push({
|
|
5983
6125
|
rule: "VC143",
|
|
5984
6126
|
title: this.title,
|
|
@@ -6042,7 +6184,7 @@ function contextSecretRuleCheck(content, filePath, pattern, ruleId, title, sever
|
|
|
6042
6184
|
const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
|
|
6043
6185
|
const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
|
|
6044
6186
|
if (PLACEHOLDER_RE.test(lineText)) continue;
|
|
6045
|
-
const lineNum = content
|
|
6187
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6046
6188
|
findings.push({
|
|
6047
6189
|
rule: ruleId,
|
|
6048
6190
|
title,
|
|
@@ -6522,7 +6664,7 @@ var ghaPullRequestTargetCheckout = {
|
|
|
6522
6664
|
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;
|
|
6523
6665
|
let m;
|
|
6524
6666
|
while ((m = checkoutPattern.exec(content)) !== null) {
|
|
6525
|
-
const lineNum = content
|
|
6667
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6526
6668
|
findings.push({
|
|
6527
6669
|
rule: "VC184",
|
|
6528
6670
|
title: ghaPullRequestTargetCheckout.title,
|
|
@@ -6606,7 +6748,7 @@ var ghaThirdPartyActionWithSecrets = {
|
|
|
6606
6748
|
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;
|
|
6607
6749
|
let m;
|
|
6608
6750
|
while ((m = pattern.exec(content)) !== null) {
|
|
6609
|
-
const lineNum = content
|
|
6751
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6610
6752
|
findings.push({
|
|
6611
6753
|
rule: "VC187",
|
|
6612
6754
|
title: ghaThirdPartyActionWithSecrets.title,
|
|
@@ -6771,7 +6913,7 @@ var pyRequestsVerifyFalse = {
|
|
|
6771
6913
|
let m;
|
|
6772
6914
|
while ((m = pattern.exec(content)) !== null) {
|
|
6773
6915
|
if (isCommentLine(content, m.index)) continue;
|
|
6774
|
-
const lineNum = content
|
|
6916
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6775
6917
|
findings.push({
|
|
6776
6918
|
rule: "VC191",
|
|
6777
6919
|
title: pyRequestsVerifyFalse.title,
|
|
@@ -6802,7 +6944,7 @@ var pyJinja2AutoescapeOff = {
|
|
|
6802
6944
|
let m;
|
|
6803
6945
|
while ((m = pattern.exec(content)) !== null) {
|
|
6804
6946
|
if (isCommentLine(content, m.index)) continue;
|
|
6805
|
-
const lineNum = content
|
|
6947
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6806
6948
|
findings.push({
|
|
6807
6949
|
rule: "VC192",
|
|
6808
6950
|
title: pyJinja2AutoescapeOff.title,
|
|
@@ -6831,7 +6973,7 @@ var pyTempfileMktemp = {
|
|
|
6831
6973
|
let m;
|
|
6832
6974
|
while ((m = pattern.exec(content)) !== null) {
|
|
6833
6975
|
if (isCommentLine(content, m.index)) continue;
|
|
6834
|
-
const lineNum = content
|
|
6976
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6835
6977
|
findings.push({
|
|
6836
6978
|
rule: "VC193",
|
|
6837
6979
|
title: pyTempfileMktemp.title,
|
|
@@ -6873,7 +7015,7 @@ var pyDjangoMarkSafe = {
|
|
|
6873
7015
|
let m;
|
|
6874
7016
|
while ((m = pattern.exec(content)) !== null) {
|
|
6875
7017
|
if (isCommentLine(content, m.index)) continue;
|
|
6876
|
-
const lineNum = content
|
|
7018
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6877
7019
|
if (seenLines.has(lineNum)) continue;
|
|
6878
7020
|
seenLines.add(lineNum);
|
|
6879
7021
|
findings.push({
|
|
@@ -6910,7 +7052,7 @@ var pyParamikoAutoAdd = {
|
|
|
6910
7052
|
let m;
|
|
6911
7053
|
while ((m = pattern.exec(content)) !== null) {
|
|
6912
7054
|
if (isCommentLine(content, m.index)) continue;
|
|
6913
|
-
const lineNum = content
|
|
7055
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6914
7056
|
if (seenLines.has(lineNum)) continue;
|
|
6915
7057
|
seenLines.add(lineNum);
|
|
6916
7058
|
findings.push({
|
|
@@ -6943,7 +7085,7 @@ var pyDjangoAllowedHostsWildcard = {
|
|
|
6943
7085
|
let m;
|
|
6944
7086
|
while ((m = pattern.exec(content)) !== null) {
|
|
6945
7087
|
if (isCommentLine(content, m.index)) continue;
|
|
6946
|
-
const lineNum = content
|
|
7088
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6947
7089
|
findings.push({
|
|
6948
7090
|
rule: "VC196",
|
|
6949
7091
|
title: pyDjangoAllowedHostsWildcard.title,
|
|
@@ -6981,7 +7123,7 @@ var pyJWTDecodeWeakConfig = {
|
|
|
6981
7123
|
if (isCommentLine(content, m.index)) continue;
|
|
6982
7124
|
const args = m[1];
|
|
6983
7125
|
if (/\balgorithms\s*=/.test(args)) continue;
|
|
6984
|
-
const lineNum = content
|
|
7126
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
6985
7127
|
if (seenLines.has(lineNum)) continue;
|
|
6986
7128
|
seenLines.add(lineNum);
|
|
6987
7129
|
findings.push({
|
|
@@ -7032,7 +7174,7 @@ var llmPromptInjection = {
|
|
|
7032
7174
|
let m;
|
|
7033
7175
|
while ((m = pattern.exec(content)) !== null) {
|
|
7034
7176
|
if (isCommentLine(content, m.index)) continue;
|
|
7035
|
-
const lineNum = content
|
|
7177
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7036
7178
|
if (seenLines.has(lineNum)) continue;
|
|
7037
7179
|
seenLines.add(lineNum);
|
|
7038
7180
|
findings.push({
|
|
@@ -7071,7 +7213,7 @@ var llmSystemPromptInjection = {
|
|
|
7071
7213
|
let m;
|
|
7072
7214
|
while ((m = pattern.exec(content)) !== null) {
|
|
7073
7215
|
if (isCommentLine(content, m.index)) continue;
|
|
7074
|
-
const lineNum = content
|
|
7216
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7075
7217
|
findings.push({
|
|
7076
7218
|
rule: "VC199",
|
|
7077
7219
|
title: llmSystemPromptInjection.title,
|
|
@@ -7113,7 +7255,7 @@ var llmOutputAsHTML = {
|
|
|
7113
7255
|
let m;
|
|
7114
7256
|
while ((m = pattern.exec(content)) !== null) {
|
|
7115
7257
|
if (isCommentLine(content, m.index)) continue;
|
|
7116
|
-
const lineNum = content
|
|
7258
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7117
7259
|
if (seenLines.has(lineNum)) continue;
|
|
7118
7260
|
seenLines.add(lineNum);
|
|
7119
7261
|
findings.push({
|
|
@@ -7150,7 +7292,7 @@ var vectorStoreQueryNoUserFilter = {
|
|
|
7150
7292
|
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;
|
|
7151
7293
|
if (/\bnamespace\s*[:=]\s*[`"']?[^,)`"']*(?:user|tenant|org|owner|account|customer|workspace)/i.test(args)) continue;
|
|
7152
7294
|
if (/\bfilter\s*[:=][\s\S]*?(?:\buser|\btenant|\borg|\bowner|\baccount|\bcustomer|\bworkspace)/i.test(args)) continue;
|
|
7153
|
-
const lineNum = content
|
|
7295
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7154
7296
|
findings.push({
|
|
7155
7297
|
rule: "VC201",
|
|
7156
7298
|
title: vectorStoreQueryNoUserFilter.title,
|
|
@@ -7187,7 +7329,7 @@ var vectorStoreUpsertNoMetadata = {
|
|
|
7187
7329
|
if (/\bnamespace\s*[:=]\s*[`"']?[^,)`"']*(?:user|tenant|org)/i.test(args)) {
|
|
7188
7330
|
continue;
|
|
7189
7331
|
}
|
|
7190
|
-
const lineNum = content
|
|
7332
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7191
7333
|
findings.push({
|
|
7192
7334
|
rule: "VC202",
|
|
7193
7335
|
title: vectorStoreUpsertNoMetadata.title,
|
|
@@ -7241,7 +7383,7 @@ var llmCallNoMaxTokens = {
|
|
|
7241
7383
|
if (depth !== 0) continue;
|
|
7242
7384
|
const args = content.substring(openIdx + 1, i).replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "").replace(/#[^\n]*/g, "");
|
|
7243
7385
|
if (TOKEN_KW_RE.test(args)) continue;
|
|
7244
|
-
const lineNum = content
|
|
7386
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7245
7387
|
findings.push({
|
|
7246
7388
|
rule: "VC203",
|
|
7247
7389
|
title: llmCallNoMaxTokens.title,
|
|
@@ -7281,7 +7423,7 @@ var graphqlNoDepthLimit = {
|
|
|
7281
7423
|
let m;
|
|
7282
7424
|
while ((m = anchorRe.exec(content)) !== null) {
|
|
7283
7425
|
if (isCommentLine(content, m.index)) continue;
|
|
7284
|
-
const lineNum = content
|
|
7426
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7285
7427
|
findings.push({
|
|
7286
7428
|
rule: "VC204",
|
|
7287
7429
|
title: graphqlNoDepthLimit.title,
|
|
@@ -7315,7 +7457,7 @@ var graphqlNoComplexityLimit = {
|
|
|
7315
7457
|
let m;
|
|
7316
7458
|
while ((m = anchorRe.exec(content)) !== null) {
|
|
7317
7459
|
if (isCommentLine(content, m.index)) continue;
|
|
7318
|
-
const lineNum = content
|
|
7460
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7319
7461
|
findings.push({
|
|
7320
7462
|
rule: "VC205",
|
|
7321
7463
|
title: graphqlNoComplexityLimit.title,
|
|
@@ -7345,7 +7487,7 @@ var graphqlCSRFDisabled = {
|
|
|
7345
7487
|
let m;
|
|
7346
7488
|
while ((m = pattern.exec(content)) !== null) {
|
|
7347
7489
|
if (isCommentLine(content, m.index)) continue;
|
|
7348
|
-
const lineNum = content
|
|
7490
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7349
7491
|
findings.push({
|
|
7350
7492
|
rule: "VC206",
|
|
7351
7493
|
title: graphqlCSRFDisabled.title,
|
|
@@ -7441,7 +7583,7 @@ var webhookMissingIdempotency = {
|
|
|
7441
7583
|
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 [];
|
|
7442
7584
|
const m = content.match(/constructEvent|new\s+Webhook\s*\(|verifyHeader/i);
|
|
7443
7585
|
if (!m || m.index === void 0) return [];
|
|
7444
|
-
const line = content
|
|
7586
|
+
const line = lineNumberAt(content, m.index);
|
|
7445
7587
|
return filterSilenced([{
|
|
7446
7588
|
rule: "VC209",
|
|
7447
7589
|
title: webhookMissingIdempotency.title,
|
|
@@ -7468,7 +7610,7 @@ var middlewareMatcherExcludesApi = {
|
|
|
7468
7610
|
if (!/\(\?!\s*[^)]*\bapi\b/.test(content)) return [];
|
|
7469
7611
|
const m = content.match(/matcher\s*:/);
|
|
7470
7612
|
if (!m || m.index === void 0) return [];
|
|
7471
|
-
const line = content
|
|
7613
|
+
const line = lineNumberAt(content, m.index);
|
|
7472
7614
|
return filterSilenced([{
|
|
7473
7615
|
rule: "VC210",
|
|
7474
7616
|
title: middlewareMatcherExcludesApi.title,
|
|
@@ -7506,7 +7648,7 @@ var secretInURLParam = {
|
|
|
7506
7648
|
if (isCommentLine(content, m.index)) continue;
|
|
7507
7649
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7508
7650
|
if (isInlineSilenced(content, m.index, "VC146")) continue;
|
|
7509
|
-
const lineNum = content
|
|
7651
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7510
7652
|
findings.push({
|
|
7511
7653
|
rule: "VC146",
|
|
7512
7654
|
title: this.title,
|
|
@@ -7537,7 +7679,7 @@ var secretLoggedToConsole = {
|
|
|
7537
7679
|
while ((m = pattern.exec(content)) !== null) {
|
|
7538
7680
|
if (isCommentLine(content, m.index)) continue;
|
|
7539
7681
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7540
|
-
const lineNum = content
|
|
7682
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7541
7683
|
findings.push({
|
|
7542
7684
|
rule: "VC147",
|
|
7543
7685
|
title: this.title,
|
|
@@ -7567,7 +7709,7 @@ var secretInErrorResponse = {
|
|
|
7567
7709
|
while ((m = pattern.exec(content)) !== null) {
|
|
7568
7710
|
if (isCommentLine(content, m.index)) continue;
|
|
7569
7711
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7570
|
-
const lineNum = content
|
|
7712
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7571
7713
|
findings.push({
|
|
7572
7714
|
rule: "VC148",
|
|
7573
7715
|
title: this.title,
|
|
@@ -7606,7 +7748,7 @@ var secretInBundleConfig = {
|
|
|
7606
7748
|
while ((m = re.exec(content)) !== null) {
|
|
7607
7749
|
if (isCommentLine(content, m.index)) continue;
|
|
7608
7750
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7609
|
-
const lineNum = content
|
|
7751
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7610
7752
|
findings.push({
|
|
7611
7753
|
rule: "VC149",
|
|
7612
7754
|
title: this.title,
|
|
@@ -7644,7 +7786,7 @@ var secretInHTMLAttribute = {
|
|
|
7644
7786
|
while ((m = re.exec(content)) !== null) {
|
|
7645
7787
|
if (isCommentLine(content, m.index)) continue;
|
|
7646
7788
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7647
|
-
const lineNum = content
|
|
7789
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7648
7790
|
findings.push({
|
|
7649
7791
|
rule: "VC150",
|
|
7650
7792
|
title: this.title,
|
|
@@ -7671,9 +7813,9 @@ var secretInCLIArgument = {
|
|
|
7671
7813
|
if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb)$/)) return [];
|
|
7672
7814
|
const findings = [];
|
|
7673
7815
|
const patterns = [
|
|
7674
|
-
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]
|
|
7675
|
-
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]
|
|
7676
|
-
/(?:subprocess|os\.system|os\.popen)\s*\([^)]
|
|
7816
|
+
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]{0,500}\$\{[^}]{0,200}(?:api[_-]?key|secret|token|password|credentials)/gi,
|
|
7817
|
+
/(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]{0,500}["']\s*\+\s*(?:api[_-]?key|secret|token|password|credentials)/gi,
|
|
7818
|
+
/(?:subprocess|os\.system|os\.popen)\s*\([^)]{0,500}\{[^}]{0,200}(?:api[_-]?key|secret|token|password|credentials)/gi
|
|
7677
7819
|
];
|
|
7678
7820
|
for (const p of patterns) {
|
|
7679
7821
|
let m;
|
|
@@ -7681,7 +7823,7 @@ var secretInCLIArgument = {
|
|
|
7681
7823
|
while ((m = re.exec(content)) !== null) {
|
|
7682
7824
|
if (isCommentLine(content, m.index)) continue;
|
|
7683
7825
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7684
|
-
const lineNum = content
|
|
7826
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7685
7827
|
findings.push({
|
|
7686
7828
|
rule: "VC151",
|
|
7687
7829
|
title: this.title,
|
|
@@ -7727,7 +7869,7 @@ var webhookSignatureVerification = {
|
|
|
7727
7869
|
if (svc.verify.test(content)) continue;
|
|
7728
7870
|
const m = content.match(/export\s+(?:async\s+)?function\s+POST/);
|
|
7729
7871
|
if (!m || m.index === void 0) continue;
|
|
7730
|
-
const lineNum = content
|
|
7872
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7731
7873
|
findings.push({
|
|
7732
7874
|
rule: "VC152",
|
|
7733
7875
|
title: `${svc.name} Webhook Missing Signature Verification`,
|
|
@@ -7794,7 +7936,7 @@ var missingRequestValidation = {
|
|
|
7794
7936
|
if (/if\s*\(\s*!(?:body|parsed|data|payload)\.|throw.*(?:missing|invalid|required)/i.test(content)) return [];
|
|
7795
7937
|
const m = content.match(/export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH)/);
|
|
7796
7938
|
if (!m || m.index === void 0) return [];
|
|
7797
|
-
const lineNum = content
|
|
7939
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7798
7940
|
return [{
|
|
7799
7941
|
rule: "VC154",
|
|
7800
7942
|
title: this.title,
|
|
@@ -7821,7 +7963,7 @@ var missingAIRateLimit = {
|
|
|
7821
7963
|
if (/rateLimit|rateLimiter|throttle|checkRateLimit|limiter|slowDown|express-rate-limit/i.test(content)) return [];
|
|
7822
7964
|
const m = content.match(/export\s+(?:async\s+)?function\s+(?:POST|GET)/i) || content.match(/\.(post|get)\s*\(/i);
|
|
7823
7965
|
if (!m || m.index === void 0) return [];
|
|
7824
|
-
const lineNum = content
|
|
7966
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7825
7967
|
return [{
|
|
7826
7968
|
rule: "VC155",
|
|
7827
7969
|
title: this.title,
|
|
@@ -7850,7 +7992,7 @@ var missingPagination = {
|
|
|
7850
7992
|
if (/findUnique|findFirst|findById|\.findOne|WHERE.*id\s*=/i.test(content)) return [];
|
|
7851
7993
|
const m = content.match(/export\s+(?:async\s+)?function\s+GET/);
|
|
7852
7994
|
if (!m || m.index === void 0) return [];
|
|
7853
|
-
const lineNum = content
|
|
7995
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7854
7996
|
return [{
|
|
7855
7997
|
rule: "VC156",
|
|
7856
7998
|
title: this.title,
|
|
@@ -7911,7 +8053,7 @@ var insecureDirectObjectReference = {
|
|
|
7911
8053
|
if (/requireUser|requireUserForApi|getServerSession|auth\(\)/i.test(content)) return [];
|
|
7912
8054
|
const m = content.match(/params\.id|params\.(?:slug|uuid)|req\.params\./);
|
|
7913
8055
|
if (!m || m.index === void 0) return [];
|
|
7914
|
-
const lineNum = content
|
|
8056
|
+
const lineNum = lineNumberAt(content, m.index);
|
|
7915
8057
|
return [{
|
|
7916
8058
|
rule: "VC158",
|
|
7917
8059
|
title: this.title,
|
|
@@ -8217,6 +8359,17 @@ function runCustomRules(content, filePath, disabledRules = [], tier = "free", ex
|
|
|
8217
8359
|
return findings;
|
|
8218
8360
|
}
|
|
8219
8361
|
if (/pro-rules-bundle|\.bundle\./i.test(filePath)) return findings;
|
|
8362
|
+
let maxLineLen = 0;
|
|
8363
|
+
{
|
|
8364
|
+
let lineStart = 0;
|
|
8365
|
+
for (let i = 0; i <= content.length; i++) {
|
|
8366
|
+
if (i === content.length || content.charCodeAt(i) === 10) {
|
|
8367
|
+
if (i - lineStart > maxLineLen) maxLineLen = i - lineStart;
|
|
8368
|
+
lineStart = i + 1;
|
|
8369
|
+
}
|
|
8370
|
+
}
|
|
8371
|
+
}
|
|
8372
|
+
if (maxLineLen > 5e4) return findings;
|
|
8220
8373
|
if (/onboarding|demo-data|example-vulnerable|code-sample/i.test(filePath)) return findings;
|
|
8221
8374
|
const ruleset = tier === "pro" && extraRules.length > 0 ? [...freeRules, ...extraRules] : freeRules;
|
|
8222
8375
|
for (const rule of ruleset) {
|