xploitscan-shared-rules 1.13.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 CHANGED
@@ -264,15 +264,50 @@ __export(index_exports, {
264
264
  module.exports = __toCommonJS(index_exports);
265
265
 
266
266
  // src/snippet.ts
267
- function getSnippet(content, line, contextLines = 2) {
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
- return lines.slice(start, end).map((l, i) => {
272
- const lineNum = start + i + 1;
304
+ const out = [];
305
+ for (let i = start; i < end; i++) {
306
+ const lineNum = i + 1;
273
307
  const marker = lineNum === line ? ">" : " ";
274
- return `${marker} ${lineNum.toString().padStart(4)} | ${l}`;
275
- }).join("\n");
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|(?:import|require)\b[^;\n]*['"]express['"]|\bNextFunction\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
- const lineNum = content.substring(0, m.index).split("\n").length;
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
- const parsed = parseFile(content, filePath);
1164
- if (!parsed) return null;
1165
- return { parsed, taint: buildTaintMap(parsed) };
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",
@@ -1530,7 +1592,7 @@ var xssVulnerability = {
1530
1592
  const hasSanitizerBypass = /bypassSecurityTrust(?:Html|Script|Style|Url|ResourceUrl)\s*\(/.test(content);
1531
1593
  if (/(?:sanitize|purify|escape|xss)/i.test(filePath)) return [];
1532
1594
  if (!hasSanitizerBypass && /DOMPurify\.sanitize|sanitizeHtml|xss\(|escapeHtml/i.test(content)) return [];
1533
- if (!hasSanitizerBypass && /(?:import|require)\s*\(?.*(?:DOMPurify|dompurify|sanitize|sanitizer)/i.test(content)) return [];
1595
+ if (!hasSanitizerBypass && /(?:import|require)\s*\(?[^\n]{0,200}(?:DOMPurify|dompurify|sanitize|sanitizer)/i.test(content)) return [];
1534
1596
  const patterns = [
1535
1597
  // React dangerouslySetInnerHTML
1536
1598
  /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:/g,
@@ -1559,9 +1621,9 @@ var xssVulnerability = {
1559
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."
1560
1622
  );
1561
1623
  for (const m of raw) {
1562
- const lineText = allLines[m.line - 1] || "";
1624
+ const lineText = (allLines[m.line - 1] || "").slice(0, 4e3);
1563
1625
  if (/\.innerHTML\s*=\s*['"]/.test(lineText) && !/\$\{/.test(lineText)) continue;
1564
- if (/\.innerHTML\s*=\s*['"][^'"]*['"]\s*$/.test(lineText)) continue;
1626
+ if (/\.innerHTML\s*=\s*['"][^'"]{0,2000}['"]\s*$/.test(lineText)) continue;
1565
1627
  if (/dangerouslySetInnerHTML/.test(lineText)) {
1566
1628
  const windowText = allLines.slice(m.line - 1, m.line + 2).join("\n");
1567
1629
  if (/JSON\.stringify/i.test(windowText)) continue;
@@ -1812,7 +1874,7 @@ var evalUsage = {
1812
1874
  /new\s+Function\s*\(\s*(?!["'`])/g
1813
1875
  ];
1814
1876
  const hasEvalInString = /["'`]eval(?:-source-map|["'`])/i.test(content);
1815
- if (hasEvalInString && !/\beval\s*\([^)]*(?:req\.|body\.|input|params|user|data)/i.test(content)) return [];
1877
+ if (hasEvalInString && !/\beval\s*\([^)]{0,500}(?:req\.|body\.|input|params|user|data)/i.test(content)) return [];
1816
1878
  const matches = [];
1817
1879
  for (const p of patterns) {
1818
1880
  const rawMatches = findMatches(
@@ -1853,7 +1915,7 @@ var unvalidatedRedirect = {
1853
1915
  const isPureLiteral = /^["'`][^"'`]*["'`]$/.test(value);
1854
1916
  if (!hasInterpolation && isPureLiteral) continue;
1855
1917
  if (isInlineSilenced(content, m.index, "VC016")) continue;
1856
- const line = content.substring(0, m.index).split("\n").length;
1918
+ const line = lineNumberAt(content, m.index);
1857
1919
  matches.push({
1858
1920
  rule: "VC016",
1859
1921
  title: unvalidatedRedirect.title,
@@ -2076,11 +2138,11 @@ var prototypePollution = {
2076
2138
  ];
2077
2139
  const hasValidation = /schema|validate|sanitize|whitelist|allowedKeys|pick\(|Object\.freeze|zod|yup|joi|ajv/i.test(content);
2078
2140
  if (!hasValidation) {
2079
- const hasUnsafeMerge = /Object\.assign\s*\([^)]*JSON\.parse|\.\.\.JSON\.parse|\{.*\.\.\.(?:stored|saved|cached|parsed|data)/i.test(content);
2141
+ const hasUnsafeMerge = /Object\.assign\s*\([^)]{0,500}JSON\.parse|\.\.\.JSON\.parse|\{[^\n]{0,300}\.\.\.(?:stored|saved|cached|parsed|data)/i.test(content);
2080
2142
  if (hasUnsafeMerge) {
2081
2143
  matches.push(...findMatches(
2082
2144
  content,
2083
- /Object\.assign\s*\([^)]*JSON\.parse|\.\.\.JSON\.parse/g,
2145
+ /Object\.assign\s*\([^)]{0,500}JSON\.parse|\.\.\.JSON\.parse/g,
2084
2146
  prototypePollution,
2085
2147
  filePath,
2086
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."
@@ -3626,7 +3688,7 @@ var dangerousInnerHTML = {
3626
3688
  if (/^[A-Z][A-Z0-9_]+$/.test(value)) continue;
3627
3689
  if (/^["'`][^$]*["'`]$/.test(value)) continue;
3628
3690
  if (/JSON\.stringify/i.test(value)) continue;
3629
- const lineNum = content.substring(0, m.index).split("\n").length;
3691
+ const lineNum = lineNumberAt(content, m.index);
3630
3692
  findings.push({
3631
3693
  rule: "VC063",
3632
3694
  title: dangerousInnerHTML.title,
@@ -4368,7 +4430,7 @@ var missingSRI = {
4368
4430
  const re = new RegExp(scriptPattern.source, scriptPattern.flags);
4369
4431
  while ((m = re.exec(content)) !== null) {
4370
4432
  if (!m[0].includes("integrity")) {
4371
- const lineNum = content.substring(0, m.index).split("\n").length;
4433
+ const lineNum = lineNumberAt(content, m.index);
4372
4434
  matches.push({
4373
4435
  rule: "VC084",
4374
4436
  title: missingSRI.title,
@@ -5239,7 +5301,7 @@ var s3BucketNoEncryption = {
5239
5301
  const blockEnd = Math.min(m.index + 2e3, content.length);
5240
5302
  const blockContent = content.substring(m.index, blockEnd);
5241
5303
  if (!/server_side_encryption/.test(blockContent)) {
5242
- const lineNum = content.substring(0, m.index).split("\n").length;
5304
+ const lineNum = lineNumberAt(content, m.index);
5243
5305
  matches.push({
5244
5306
  rule: "VC107",
5245
5307
  title: s3BucketNoEncryption.title,
@@ -5270,7 +5332,7 @@ var securityGroupAllInbound = {
5270
5332
  while ((m = ingressPattern.exec(content)) !== null) {
5271
5333
  if (isCommentLine(content, m.index)) continue;
5272
5334
  if (/from_port\s*=\s*0/.test(m[0]) || !/from_port/.test(m[0])) {
5273
- const lineNum = content.substring(0, m.index).split("\n").length;
5335
+ const lineNum = lineNumberAt(content, m.index);
5274
5336
  matches.push({
5275
5337
  rule: "VC108",
5276
5338
  title: securityGroupAllInbound.title,
@@ -5357,7 +5419,7 @@ var lambdaWithoutVPC = {
5357
5419
  const blockEnd = Math.min(m.index + 2e3, content.length);
5358
5420
  const blockContent = content.substring(m.index, blockEnd);
5359
5421
  if (!/vpc_config\s*\{/.test(blockContent)) {
5360
- const lineNum = content.substring(0, m.index).split("\n").length;
5422
+ const lineNum = lineNumberAt(content, m.index);
5361
5423
  matches.push({
5362
5424
  rule: "VC111",
5363
5425
  title: lambdaWithoutVPC.title,
@@ -5387,7 +5449,7 @@ var dockerLatestTag = {
5387
5449
  while ((m = fromPattern.exec(content)) !== null) {
5388
5450
  const image = m[1];
5389
5451
  if (image.endsWith(":latest") || !image.includes(":") && !image.startsWith("$")) {
5390
- const lineNum = content.substring(0, m.index).split("\n").length;
5452
+ const lineNum = lineNumberAt(content, m.index);
5391
5453
  matches.push({
5392
5454
  rule: "VC112",
5393
5455
  title: dockerLatestTag.title,
@@ -5515,17 +5577,26 @@ var pathTraversal = {
5515
5577
  const findings = [];
5516
5578
  const patterns = [
5517
5579
  /(?:readFile|readFileSync|createReadStream|writeFile|writeFileSync|appendFile|unlink|unlinkSync|access|stat|statSync|existsSync)\s*\(\s*(?:req\.|request\.|ctx\.|params\.|query\.)/gi,
5518
- /(?:path\.join|path\.resolve)\s*\([^)]*(?:req\.|request\.|ctx\.|params\.|query\.)[^)]*\)/gi,
5519
- /(?:res\.sendFile|res\.download)\s*\([^)]*(?:req\.|request\.)/gi,
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,
5520
5582
  /open\s*\(\s*(?:request\.|params\[|args\.)/gi
5521
5583
  ];
5584
+ const MAX_MATCHES = 500;
5522
5585
  for (const pat of patterns) {
5586
+ if (findings.length >= MAX_MATCHES) break;
5523
5587
  let m;
5588
+ let lastIdx = -1;
5524
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;
5525
5596
  if (isCommentLine(content, m.index)) continue;
5526
5597
  const surrounding = content.substring(Math.max(0, m.index - 200), m.index + 200);
5527
5598
  if (/path\.normalize|sanitize|\.replace\(\s*['"]\.\.|\.startsWith\s*\(|realpath/i.test(surrounding)) continue;
5528
- const lineNum = content.substring(0, m.index).split("\n").length;
5599
+ const lineNum = lineNumberAt(content, m.index);
5529
5600
  findings.push({
5530
5601
  rule: "VC117",
5531
5602
  title: pathTraversal.title,
@@ -5556,7 +5627,7 @@ var piiInLogs = {
5556
5627
  while ((m = logPattern.exec(content)) !== null) {
5557
5628
  if (isCommentLine(content, m.index)) continue;
5558
5629
  if (isInsideFixMessage(content, m.index)) continue;
5559
- const lineNum = content.substring(0, m.index).split("\n").length;
5630
+ const lineNum = lineNumberAt(content, m.index);
5560
5631
  findings.push({
5561
5632
  rule: "VC118",
5562
5633
  title: piiInLogs.title,
@@ -5594,7 +5665,7 @@ var hardcodedOAuthSecret = {
5594
5665
  while ((m = pat.exec(content)) !== null) {
5595
5666
  if (isCommentLine(content, m.index)) continue;
5596
5667
  if (/process\.env|os\.environ|ENV\[|getenv|\$\{|<your|CHANGE_ME|xxx|placeholder/i.test(m[0])) continue;
5597
- const lineNum = content.substring(0, m.index).split("\n").length;
5668
+ const lineNum = lineNumberAt(content, m.index);
5598
5669
  findings.push({
5599
5670
  rule: "VC119",
5600
5671
  title: hardcodedOAuthSecret.title,
@@ -5626,7 +5697,7 @@ var missingOAuthState = {
5626
5697
  if (isCommentLine(content, m.index)) continue;
5627
5698
  const surrounding = content.substring(m.index, Math.min(content.length, m.index + 500));
5628
5699
  if (/state\s*[=:]/i.test(surrounding)) continue;
5629
- const lineNum = content.substring(0, m.index).split("\n").length;
5700
+ const lineNum = lineNumberAt(content, m.index);
5630
5701
  findings.push({
5631
5702
  rule: "VC120",
5632
5703
  title: missingOAuthState.title,
@@ -5654,7 +5725,7 @@ var unpinnedGitHubAction = {
5654
5725
  let m;
5655
5726
  while ((m = usesPattern.exec(content)) !== null) {
5656
5727
  if (isCommentLine(content, m.index)) continue;
5657
- const lineNum = content.substring(0, m.index).split("\n").length;
5728
+ const lineNum = lineNumberAt(content, m.index);
5658
5729
  findings.push({
5659
5730
  rule: "VC121",
5660
5731
  title: unpinnedGitHubAction.title,
@@ -5688,7 +5759,7 @@ var deprecatedTLS = {
5688
5759
  let m;
5689
5760
  while ((m = pat.exec(content)) !== null) {
5690
5761
  if (isCommentLine(content, m.index)) continue;
5691
- const lineNum = content.substring(0, m.index).split("\n").length;
5762
+ const lineNum = lineNumberAt(content, m.index);
5692
5763
  findings.push({
5693
5764
  rule: "VC122",
5694
5765
  title: deprecatedTLS.title,
@@ -5724,7 +5795,7 @@ var weakRSAKeySize = {
5724
5795
  let m;
5725
5796
  while ((m = pat.exec(content)) !== null) {
5726
5797
  if (isCommentLine(content, m.index)) continue;
5727
- const lineNum = content.substring(0, m.index).split("\n").length;
5798
+ const lineNum = lineNumberAt(content, m.index);
5728
5799
  findings.push({
5729
5800
  rule: "VC123",
5730
5801
  title: weakRSAKeySize.title,
@@ -5760,7 +5831,7 @@ var ecbModeEncryption = {
5760
5831
  let m;
5761
5832
  while ((m = pat.exec(content)) !== null) {
5762
5833
  if (isCommentLine(content, m.index)) continue;
5763
- const lineNum = content.substring(0, m.index).split("\n").length;
5834
+ const lineNum = lineNumberAt(content, m.index);
5764
5835
  findings.push({
5765
5836
  rule: "VC124",
5766
5837
  title: ecbModeEncryption.title,
@@ -5791,7 +5862,7 @@ var insecurePasswordReset = {
5791
5862
  let m;
5792
5863
  while ((m = weakTokens.exec(content)) !== null) {
5793
5864
  if (isCommentLine(content, m.index)) continue;
5794
- const lineNum = content.substring(0, m.index).split("\n").length;
5865
+ const lineNum = lineNumberAt(content, m.index);
5795
5866
  findings.push({
5796
5867
  rule: "VC125",
5797
5868
  title: insecurePasswordReset.title,
@@ -5808,7 +5879,7 @@ var insecurePasswordReset = {
5808
5879
  if (isCommentLine(content, m.index)) continue;
5809
5880
  const surrounding = content.substring(Math.max(0, m.index - 500), m.index);
5810
5881
  if (!/(?:reset|forgot).*password/i.test(surrounding)) continue;
5811
- const lineNum = content.substring(0, m.index).split("\n").length;
5882
+ const lineNum = lineNumberAt(content, m.index);
5812
5883
  findings.push({
5813
5884
  rule: "VC125",
5814
5885
  title: insecurePasswordReset.title,
@@ -5869,7 +5940,7 @@ var insecureHTTPMethods = {
5869
5940
  if (isCommentLine(content, m.index)) continue;
5870
5941
  const handlerBlock = content.substring(m.index, Math.min(content.length, m.index + 500));
5871
5942
  if (/auth|authenticate|authorize|requireAuth|isAuthenticated|protect|guard|middleware|session|jwt|verify|clerk|getAuth/i.test(handlerBlock)) continue;
5872
- const lineNum = content.substring(0, m.index).split("\n").length;
5943
+ const lineNum = lineNumberAt(content, m.index);
5873
5944
  findings.push({
5874
5945
  rule: "VC127",
5875
5946
  title: insecureHTTPMethods.title,
@@ -5903,7 +5974,7 @@ var httpRequestSmuggling = {
5903
5974
  let m;
5904
5975
  while ((m = pat.exec(content)) !== null) {
5905
5976
  if (isCommentLine(content, m.index)) continue;
5906
- const lineNum = content.substring(0, m.index).split("\n").length;
5977
+ const lineNum = lineNumberAt(content, m.index);
5907
5978
  findings.push({
5908
5979
  rule: "VC128",
5909
5980
  title: httpRequestSmuggling.title,
@@ -5937,7 +6008,7 @@ var unencryptedPII = {
5937
6008
  let m;
5938
6009
  while ((m = pat.exec(content)) !== null) {
5939
6010
  if (isCommentLine(content, m.index)) continue;
5940
- const lineNum = content.substring(0, m.index).split("\n").length;
6011
+ const lineNum = lineNumberAt(content, m.index);
5941
6012
  findings.push({
5942
6013
  rule: "VC129",
5943
6014
  title: unencryptedPII.title,
@@ -5969,7 +6040,7 @@ var missingAuthRateLimit = {
5969
6040
  if (isCommentLine(content, m.index)) continue;
5970
6041
  const surrounding = content.substring(Math.max(0, m.index - 300), Math.min(content.length, m.index + 300));
5971
6042
  if (/rateLimit|rateLimiter|throttle|slowDown|express-rate-limit|rate_limit|RateLimiter|limiter/i.test(surrounding)) continue;
5972
- const lineNum = content.substring(0, m.index).split("\n").length;
6043
+ const lineNum = lineNumberAt(content, m.index);
5973
6044
  findings.push({
5974
6045
  rule: "VC130",
5975
6046
  title: missingAuthRateLimit.title,
@@ -6007,7 +6078,7 @@ var vulnerableDependencies = {
6007
6078
  for (const [pat, message] of vulnerablePackages) {
6008
6079
  let m;
6009
6080
  while ((m = pat.exec(content)) !== null) {
6010
- const lineNum = content.substring(0, m.index).split("\n").length;
6081
+ const lineNum = lineNumberAt(content, m.index);
6011
6082
  findings.push({
6012
6083
  rule: "VC131",
6013
6084
  title: vulnerableDependencies.title,
@@ -6039,7 +6110,7 @@ function secretRuleCheck(content, filePath, pattern, ruleId, title, severity, fi
6039
6110
  const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
6040
6111
  const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
6041
6112
  if (PLACEHOLDER_RE.test(lineText)) continue;
6042
- const lineNum = content.substring(0, m.index).split("\n").length;
6113
+ const lineNum = lineNumberAt(content, m.index);
6043
6114
  findings.push({
6044
6115
  rule: ruleId,
6045
6116
  title,
@@ -6140,7 +6211,7 @@ var hardcodedGCPServiceAccount = {
6140
6211
  const findings = [];
6141
6212
  const m = content.match(/"type"\s*:\s*"service_account"/);
6142
6213
  if (m && m.index !== void 0) {
6143
- const lineNum = content.substring(0, m.index).split("\n").length;
6214
+ const lineNum = lineNumberAt(content, m.index);
6144
6215
  findings.push({
6145
6216
  rule: "VC136",
6146
6217
  title: this.title,
@@ -6246,7 +6317,7 @@ var hardcodedDatadogKey = {
6246
6317
  const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
6247
6318
  const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
6248
6319
  if (PLACEHOLDER_RE.test(lineText)) continue;
6249
- const lineNum = content.substring(0, m.index).split("\n").length;
6320
+ const lineNum = lineNumberAt(content, m.index);
6250
6321
  findings.push({
6251
6322
  rule: "VC141",
6252
6323
  title: this.title,
@@ -6280,7 +6351,7 @@ var hardcodedVercelToken = {
6280
6351
  const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
6281
6352
  const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
6282
6353
  if (PLACEHOLDER_RE.test(lineText)) continue;
6283
- const lineNum = content.substring(0, m.index).split("\n").length;
6354
+ const lineNum = lineNumberAt(content, m.index);
6284
6355
  findings.push({
6285
6356
  rule: "VC142",
6286
6357
  title: this.title,
@@ -6314,7 +6385,7 @@ var hardcodedSupabaseServiceRole = {
6314
6385
  const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
6315
6386
  const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
6316
6387
  if (PLACEHOLDER_RE.test(lineText)) continue;
6317
- const lineNum = content.substring(0, m.index).split("\n").length;
6388
+ const lineNum = lineNumberAt(content, m.index);
6318
6389
  findings.push({
6319
6390
  rule: "VC143",
6320
6391
  title: this.title,
@@ -6378,7 +6449,7 @@ function contextSecretRuleCheck(content, filePath, pattern, ruleId, title, sever
6378
6449
  const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
6379
6450
  const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
6380
6451
  if (PLACEHOLDER_RE.test(lineText)) continue;
6381
- const lineNum = content.substring(0, m.index).split("\n").length;
6452
+ const lineNum = lineNumberAt(content, m.index);
6382
6453
  findings.push({
6383
6454
  rule: ruleId,
6384
6455
  title,
@@ -6858,7 +6929,7 @@ var ghaPullRequestTargetCheckout = {
6858
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;
6859
6930
  let m;
6860
6931
  while ((m = checkoutPattern.exec(content)) !== null) {
6861
- const lineNum = content.substring(0, m.index).split("\n").length;
6932
+ const lineNum = lineNumberAt(content, m.index);
6862
6933
  findings.push({
6863
6934
  rule: "VC184",
6864
6935
  title: ghaPullRequestTargetCheckout.title,
@@ -6942,7 +7013,7 @@ var ghaThirdPartyActionWithSecrets = {
6942
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;
6943
7014
  let m;
6944
7015
  while ((m = pattern.exec(content)) !== null) {
6945
- const lineNum = content.substring(0, m.index).split("\n").length;
7016
+ const lineNum = lineNumberAt(content, m.index);
6946
7017
  findings.push({
6947
7018
  rule: "VC187",
6948
7019
  title: ghaThirdPartyActionWithSecrets.title,
@@ -7107,7 +7178,7 @@ var pyRequestsVerifyFalse = {
7107
7178
  let m;
7108
7179
  while ((m = pattern.exec(content)) !== null) {
7109
7180
  if (isCommentLine(content, m.index)) continue;
7110
- const lineNum = content.substring(0, m.index).split("\n").length;
7181
+ const lineNum = lineNumberAt(content, m.index);
7111
7182
  findings.push({
7112
7183
  rule: "VC191",
7113
7184
  title: pyRequestsVerifyFalse.title,
@@ -7138,7 +7209,7 @@ var pyJinja2AutoescapeOff = {
7138
7209
  let m;
7139
7210
  while ((m = pattern.exec(content)) !== null) {
7140
7211
  if (isCommentLine(content, m.index)) continue;
7141
- const lineNum = content.substring(0, m.index).split("\n").length;
7212
+ const lineNum = lineNumberAt(content, m.index);
7142
7213
  findings.push({
7143
7214
  rule: "VC192",
7144
7215
  title: pyJinja2AutoescapeOff.title,
@@ -7167,7 +7238,7 @@ var pyTempfileMktemp = {
7167
7238
  let m;
7168
7239
  while ((m = pattern.exec(content)) !== null) {
7169
7240
  if (isCommentLine(content, m.index)) continue;
7170
- const lineNum = content.substring(0, m.index).split("\n").length;
7241
+ const lineNum = lineNumberAt(content, m.index);
7171
7242
  findings.push({
7172
7243
  rule: "VC193",
7173
7244
  title: pyTempfileMktemp.title,
@@ -7209,7 +7280,7 @@ var pyDjangoMarkSafe = {
7209
7280
  let m;
7210
7281
  while ((m = pattern.exec(content)) !== null) {
7211
7282
  if (isCommentLine(content, m.index)) continue;
7212
- const lineNum = content.substring(0, m.index).split("\n").length;
7283
+ const lineNum = lineNumberAt(content, m.index);
7213
7284
  if (seenLines.has(lineNum)) continue;
7214
7285
  seenLines.add(lineNum);
7215
7286
  findings.push({
@@ -7246,7 +7317,7 @@ var pyParamikoAutoAdd = {
7246
7317
  let m;
7247
7318
  while ((m = pattern.exec(content)) !== null) {
7248
7319
  if (isCommentLine(content, m.index)) continue;
7249
- const lineNum = content.substring(0, m.index).split("\n").length;
7320
+ const lineNum = lineNumberAt(content, m.index);
7250
7321
  if (seenLines.has(lineNum)) continue;
7251
7322
  seenLines.add(lineNum);
7252
7323
  findings.push({
@@ -7279,7 +7350,7 @@ var pyDjangoAllowedHostsWildcard = {
7279
7350
  let m;
7280
7351
  while ((m = pattern.exec(content)) !== null) {
7281
7352
  if (isCommentLine(content, m.index)) continue;
7282
- const lineNum = content.substring(0, m.index).split("\n").length;
7353
+ const lineNum = lineNumberAt(content, m.index);
7283
7354
  findings.push({
7284
7355
  rule: "VC196",
7285
7356
  title: pyDjangoAllowedHostsWildcard.title,
@@ -7317,7 +7388,7 @@ var pyJWTDecodeWeakConfig = {
7317
7388
  if (isCommentLine(content, m.index)) continue;
7318
7389
  const args = m[1];
7319
7390
  if (/\balgorithms\s*=/.test(args)) continue;
7320
- const lineNum = content.substring(0, m.index).split("\n").length;
7391
+ const lineNum = lineNumberAt(content, m.index);
7321
7392
  if (seenLines.has(lineNum)) continue;
7322
7393
  seenLines.add(lineNum);
7323
7394
  findings.push({
@@ -7368,7 +7439,7 @@ var llmPromptInjection = {
7368
7439
  let m;
7369
7440
  while ((m = pattern.exec(content)) !== null) {
7370
7441
  if (isCommentLine(content, m.index)) continue;
7371
- const lineNum = content.substring(0, m.index).split("\n").length;
7442
+ const lineNum = lineNumberAt(content, m.index);
7372
7443
  if (seenLines.has(lineNum)) continue;
7373
7444
  seenLines.add(lineNum);
7374
7445
  findings.push({
@@ -7407,7 +7478,7 @@ var llmSystemPromptInjection = {
7407
7478
  let m;
7408
7479
  while ((m = pattern.exec(content)) !== null) {
7409
7480
  if (isCommentLine(content, m.index)) continue;
7410
- const lineNum = content.substring(0, m.index).split("\n").length;
7481
+ const lineNum = lineNumberAt(content, m.index);
7411
7482
  findings.push({
7412
7483
  rule: "VC199",
7413
7484
  title: llmSystemPromptInjection.title,
@@ -7449,7 +7520,7 @@ var llmOutputAsHTML = {
7449
7520
  let m;
7450
7521
  while ((m = pattern.exec(content)) !== null) {
7451
7522
  if (isCommentLine(content, m.index)) continue;
7452
- const lineNum = content.substring(0, m.index).split("\n").length;
7523
+ const lineNum = lineNumberAt(content, m.index);
7453
7524
  if (seenLines.has(lineNum)) continue;
7454
7525
  seenLines.add(lineNum);
7455
7526
  findings.push({
@@ -7486,7 +7557,7 @@ var vectorStoreQueryNoUserFilter = {
7486
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;
7487
7558
  if (/\bnamespace\s*[:=]\s*[`"']?[^,)`"']*(?:user|tenant|org|owner|account|customer|workspace)/i.test(args)) continue;
7488
7559
  if (/\bfilter\s*[:=][\s\S]*?(?:\buser|\btenant|\borg|\bowner|\baccount|\bcustomer|\bworkspace)/i.test(args)) continue;
7489
- const lineNum = content.substring(0, m.index).split("\n").length;
7560
+ const lineNum = lineNumberAt(content, m.index);
7490
7561
  findings.push({
7491
7562
  rule: "VC201",
7492
7563
  title: vectorStoreQueryNoUserFilter.title,
@@ -7523,7 +7594,7 @@ var vectorStoreUpsertNoMetadata = {
7523
7594
  if (/\bnamespace\s*[:=]\s*[`"']?[^,)`"']*(?:user|tenant|org)/i.test(args)) {
7524
7595
  continue;
7525
7596
  }
7526
- const lineNum = content.substring(0, m.index).split("\n").length;
7597
+ const lineNum = lineNumberAt(content, m.index);
7527
7598
  findings.push({
7528
7599
  rule: "VC202",
7529
7600
  title: vectorStoreUpsertNoMetadata.title,
@@ -7577,7 +7648,7 @@ var llmCallNoMaxTokens = {
7577
7648
  if (depth !== 0) continue;
7578
7649
  const args = content.substring(openIdx + 1, i).replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "").replace(/#[^\n]*/g, "");
7579
7650
  if (TOKEN_KW_RE.test(args)) continue;
7580
- const lineNum = content.substring(0, m.index).split("\n").length;
7651
+ const lineNum = lineNumberAt(content, m.index);
7581
7652
  findings.push({
7582
7653
  rule: "VC203",
7583
7654
  title: llmCallNoMaxTokens.title,
@@ -7617,7 +7688,7 @@ var graphqlNoDepthLimit = {
7617
7688
  let m;
7618
7689
  while ((m = anchorRe.exec(content)) !== null) {
7619
7690
  if (isCommentLine(content, m.index)) continue;
7620
- const lineNum = content.substring(0, m.index).split("\n").length;
7691
+ const lineNum = lineNumberAt(content, m.index);
7621
7692
  findings.push({
7622
7693
  rule: "VC204",
7623
7694
  title: graphqlNoDepthLimit.title,
@@ -7651,7 +7722,7 @@ var graphqlNoComplexityLimit = {
7651
7722
  let m;
7652
7723
  while ((m = anchorRe.exec(content)) !== null) {
7653
7724
  if (isCommentLine(content, m.index)) continue;
7654
- const lineNum = content.substring(0, m.index).split("\n").length;
7725
+ const lineNum = lineNumberAt(content, m.index);
7655
7726
  findings.push({
7656
7727
  rule: "VC205",
7657
7728
  title: graphqlNoComplexityLimit.title,
@@ -7681,7 +7752,7 @@ var graphqlCSRFDisabled = {
7681
7752
  let m;
7682
7753
  while ((m = pattern.exec(content)) !== null) {
7683
7754
  if (isCommentLine(content, m.index)) continue;
7684
- const lineNum = content.substring(0, m.index).split("\n").length;
7755
+ const lineNum = lineNumberAt(content, m.index);
7685
7756
  findings.push({
7686
7757
  rule: "VC206",
7687
7758
  title: graphqlCSRFDisabled.title,
@@ -7777,7 +7848,7 @@ var webhookMissingIdempotency = {
7777
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 [];
7778
7849
  const m = content.match(/constructEvent|new\s+Webhook\s*\(|verifyHeader/i);
7779
7850
  if (!m || m.index === void 0) return [];
7780
- const line = content.substring(0, m.index).split("\n").length;
7851
+ const line = lineNumberAt(content, m.index);
7781
7852
  return filterSilenced([{
7782
7853
  rule: "VC209",
7783
7854
  title: webhookMissingIdempotency.title,
@@ -7804,7 +7875,7 @@ var middlewareMatcherExcludesApi = {
7804
7875
  if (!/\(\?!\s*[^)]*\bapi\b/.test(content)) return [];
7805
7876
  const m = content.match(/matcher\s*:/);
7806
7877
  if (!m || m.index === void 0) return [];
7807
- const line = content.substring(0, m.index).split("\n").length;
7878
+ const line = lineNumberAt(content, m.index);
7808
7879
  return filterSilenced([{
7809
7880
  rule: "VC210",
7810
7881
  title: middlewareMatcherExcludesApi.title,
@@ -7842,7 +7913,7 @@ var secretInURLParam = {
7842
7913
  if (isCommentLine(content, m.index)) continue;
7843
7914
  if (isInsideFixMessage(content, m.index)) continue;
7844
7915
  if (isInlineSilenced(content, m.index, "VC146")) continue;
7845
- const lineNum = content.substring(0, m.index).split("\n").length;
7916
+ const lineNum = lineNumberAt(content, m.index);
7846
7917
  findings.push({
7847
7918
  rule: "VC146",
7848
7919
  title: this.title,
@@ -7873,7 +7944,7 @@ var secretLoggedToConsole = {
7873
7944
  while ((m = pattern.exec(content)) !== null) {
7874
7945
  if (isCommentLine(content, m.index)) continue;
7875
7946
  if (isInsideFixMessage(content, m.index)) continue;
7876
- const lineNum = content.substring(0, m.index).split("\n").length;
7947
+ const lineNum = lineNumberAt(content, m.index);
7877
7948
  findings.push({
7878
7949
  rule: "VC147",
7879
7950
  title: this.title,
@@ -7903,7 +7974,7 @@ var secretInErrorResponse = {
7903
7974
  while ((m = pattern.exec(content)) !== null) {
7904
7975
  if (isCommentLine(content, m.index)) continue;
7905
7976
  if (isInsideFixMessage(content, m.index)) continue;
7906
- const lineNum = content.substring(0, m.index).split("\n").length;
7977
+ const lineNum = lineNumberAt(content, m.index);
7907
7978
  findings.push({
7908
7979
  rule: "VC148",
7909
7980
  title: this.title,
@@ -7942,7 +8013,7 @@ var secretInBundleConfig = {
7942
8013
  while ((m = re.exec(content)) !== null) {
7943
8014
  if (isCommentLine(content, m.index)) continue;
7944
8015
  if (isInsideFixMessage(content, m.index)) continue;
7945
- const lineNum = content.substring(0, m.index).split("\n").length;
8016
+ const lineNum = lineNumberAt(content, m.index);
7946
8017
  findings.push({
7947
8018
  rule: "VC149",
7948
8019
  title: this.title,
@@ -7980,7 +8051,7 @@ var secretInHTMLAttribute = {
7980
8051
  while ((m = re.exec(content)) !== null) {
7981
8052
  if (isCommentLine(content, m.index)) continue;
7982
8053
  if (isInsideFixMessage(content, m.index)) continue;
7983
- const lineNum = content.substring(0, m.index).split("\n").length;
8054
+ const lineNum = lineNumberAt(content, m.index);
7984
8055
  findings.push({
7985
8056
  rule: "VC150",
7986
8057
  title: this.title,
@@ -8007,9 +8078,9 @@ var secretInCLIArgument = {
8007
8078
  if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb)$/)) return [];
8008
8079
  const findings = [];
8009
8080
  const patterns = [
8010
- /(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]*\$\{[^}]*(?:api[_-]?key|secret|token|password|credentials)/gi,
8011
- /(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]*["']\s*\+\s*(?:api[_-]?key|secret|token|password|credentials)/gi,
8012
- /(?:subprocess|os\.system|os\.popen)\s*\([^)]*\{[^}]*(?:api[_-]?key|secret|token|password|credentials)/gi
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
8013
8084
  ];
8014
8085
  for (const p of patterns) {
8015
8086
  let m;
@@ -8017,7 +8088,7 @@ var secretInCLIArgument = {
8017
8088
  while ((m = re.exec(content)) !== null) {
8018
8089
  if (isCommentLine(content, m.index)) continue;
8019
8090
  if (isInsideFixMessage(content, m.index)) continue;
8020
- const lineNum = content.substring(0, m.index).split("\n").length;
8091
+ const lineNum = lineNumberAt(content, m.index);
8021
8092
  findings.push({
8022
8093
  rule: "VC151",
8023
8094
  title: this.title,
@@ -8063,7 +8134,7 @@ var webhookSignatureVerification = {
8063
8134
  if (svc.verify.test(content)) continue;
8064
8135
  const m = content.match(/export\s+(?:async\s+)?function\s+POST/);
8065
8136
  if (!m || m.index === void 0) continue;
8066
- const lineNum = content.substring(0, m.index).split("\n").length;
8137
+ const lineNum = lineNumberAt(content, m.index);
8067
8138
  findings.push({
8068
8139
  rule: "VC152",
8069
8140
  title: `${svc.name} Webhook Missing Signature Verification`,
@@ -8130,7 +8201,7 @@ var missingRequestValidation = {
8130
8201
  if (/if\s*\(\s*!(?:body|parsed|data|payload)\.|throw.*(?:missing|invalid|required)/i.test(content)) return [];
8131
8202
  const m = content.match(/export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH)/);
8132
8203
  if (!m || m.index === void 0) return [];
8133
- const lineNum = content.substring(0, m.index).split("\n").length;
8204
+ const lineNum = lineNumberAt(content, m.index);
8134
8205
  return [{
8135
8206
  rule: "VC154",
8136
8207
  title: this.title,
@@ -8157,7 +8228,7 @@ var missingAIRateLimit = {
8157
8228
  if (/rateLimit|rateLimiter|throttle|checkRateLimit|limiter|slowDown|express-rate-limit/i.test(content)) return [];
8158
8229
  const m = content.match(/export\s+(?:async\s+)?function\s+(?:POST|GET)/i) || content.match(/\.(post|get)\s*\(/i);
8159
8230
  if (!m || m.index === void 0) return [];
8160
- const lineNum = content.substring(0, m.index).split("\n").length;
8231
+ const lineNum = lineNumberAt(content, m.index);
8161
8232
  return [{
8162
8233
  rule: "VC155",
8163
8234
  title: this.title,
@@ -8186,7 +8257,7 @@ var missingPagination = {
8186
8257
  if (/findUnique|findFirst|findById|\.findOne|WHERE.*id\s*=/i.test(content)) return [];
8187
8258
  const m = content.match(/export\s+(?:async\s+)?function\s+GET/);
8188
8259
  if (!m || m.index === void 0) return [];
8189
- const lineNum = content.substring(0, m.index).split("\n").length;
8260
+ const lineNum = lineNumberAt(content, m.index);
8190
8261
  return [{
8191
8262
  rule: "VC156",
8192
8263
  title: this.title,
@@ -8247,7 +8318,7 @@ var insecureDirectObjectReference = {
8247
8318
  if (/requireUser|requireUserForApi|getServerSession|auth\(\)/i.test(content)) return [];
8248
8319
  const m = content.match(/params\.id|params\.(?:slug|uuid)|req\.params\./);
8249
8320
  if (!m || m.index === void 0) return [];
8250
- const lineNum = content.substring(0, m.index).split("\n").length;
8321
+ const lineNum = lineNumberAt(content, m.index);
8251
8322
  return [{
8252
8323
  rule: "VC158",
8253
8324
  title: this.title,
@@ -8553,6 +8624,17 @@ function runCustomRules(content, filePath, disabledRules = [], tier = "free", ex
8553
8624
  return findings;
8554
8625
  }
8555
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;
8556
8638
  if (/onboarding|demo-data|example-vulnerable|code-sample/i.test(filePath)) return findings;
8557
8639
  const ruleset = tier === "pro" && extraRules.length > 0 ? [...freeRules, ...extraRules] : freeRules;
8558
8640
  for (const rule of ruleset) {