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.js CHANGED
@@ -1,13 +1,48 @@
1
1
  // src/snippet.ts
2
- function getSnippet(content, line, contextLines = 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
- return lines.slice(start, end).map((l, i) => {
7
- const lineNum = start + i + 1;
39
+ const out = [];
40
+ for (let i = start; i < end; i++) {
41
+ const lineNum = i + 1;
8
42
  const marker = lineNum === line ? ">" : " ";
9
- return `${marker} ${lineNum.toString().padStart(4)} | ${l}`;
10
- }).join("\n");
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|(?:import|require)\b[^;\n]*['"]express['"]|\bNextFunction\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
- const lineNum = content.substring(0, m.index).split("\n").length;
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
- const parsed = parseFile(content, filePath);
899
- if (!parsed) return null;
900
- return { parsed, taint: buildTaintMap(parsed) };
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",
@@ -1265,7 +1327,7 @@ var xssVulnerability = {
1265
1327
  const hasSanitizerBypass = /bypassSecurityTrust(?:Html|Script|Style|Url|ResourceUrl)\s*\(/.test(content);
1266
1328
  if (/(?:sanitize|purify|escape|xss)/i.test(filePath)) return [];
1267
1329
  if (!hasSanitizerBypass && /DOMPurify\.sanitize|sanitizeHtml|xss\(|escapeHtml/i.test(content)) return [];
1268
- if (!hasSanitizerBypass && /(?:import|require)\s*\(?.*(?:DOMPurify|dompurify|sanitize|sanitizer)/i.test(content)) return [];
1330
+ if (!hasSanitizerBypass && /(?:import|require)\s*\(?[^\n]{0,200}(?:DOMPurify|dompurify|sanitize|sanitizer)/i.test(content)) return [];
1269
1331
  const patterns = [
1270
1332
  // React dangerouslySetInnerHTML
1271
1333
  /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:/g,
@@ -1294,9 +1356,9 @@ var xssVulnerability = {
1294
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."
1295
1357
  );
1296
1358
  for (const m of raw) {
1297
- const lineText = allLines[m.line - 1] || "";
1359
+ const lineText = (allLines[m.line - 1] || "").slice(0, 4e3);
1298
1360
  if (/\.innerHTML\s*=\s*['"]/.test(lineText) && !/\$\{/.test(lineText)) continue;
1299
- if (/\.innerHTML\s*=\s*['"][^'"]*['"]\s*$/.test(lineText)) continue;
1361
+ if (/\.innerHTML\s*=\s*['"][^'"]{0,2000}['"]\s*$/.test(lineText)) continue;
1300
1362
  if (/dangerouslySetInnerHTML/.test(lineText)) {
1301
1363
  const windowText = allLines.slice(m.line - 1, m.line + 2).join("\n");
1302
1364
  if (/JSON\.stringify/i.test(windowText)) continue;
@@ -1547,7 +1609,7 @@ var evalUsage = {
1547
1609
  /new\s+Function\s*\(\s*(?!["'`])/g
1548
1610
  ];
1549
1611
  const hasEvalInString = /["'`]eval(?:-source-map|["'`])/i.test(content);
1550
- if (hasEvalInString && !/\beval\s*\([^)]*(?:req\.|body\.|input|params|user|data)/i.test(content)) return [];
1612
+ if (hasEvalInString && !/\beval\s*\([^)]{0,500}(?:req\.|body\.|input|params|user|data)/i.test(content)) return [];
1551
1613
  const matches = [];
1552
1614
  for (const p of patterns) {
1553
1615
  const rawMatches = findMatches(
@@ -1588,7 +1650,7 @@ var unvalidatedRedirect = {
1588
1650
  const isPureLiteral = /^["'`][^"'`]*["'`]$/.test(value);
1589
1651
  if (!hasInterpolation && isPureLiteral) continue;
1590
1652
  if (isInlineSilenced(content, m.index, "VC016")) continue;
1591
- const line = content.substring(0, m.index).split("\n").length;
1653
+ const line = lineNumberAt(content, m.index);
1592
1654
  matches.push({
1593
1655
  rule: "VC016",
1594
1656
  title: unvalidatedRedirect.title,
@@ -1811,11 +1873,11 @@ var prototypePollution = {
1811
1873
  ];
1812
1874
  const hasValidation = /schema|validate|sanitize|whitelist|allowedKeys|pick\(|Object\.freeze|zod|yup|joi|ajv/i.test(content);
1813
1875
  if (!hasValidation) {
1814
- const hasUnsafeMerge = /Object\.assign\s*\([^)]*JSON\.parse|\.\.\.JSON\.parse|\{.*\.\.\.(?:stored|saved|cached|parsed|data)/i.test(content);
1876
+ const hasUnsafeMerge = /Object\.assign\s*\([^)]{0,500}JSON\.parse|\.\.\.JSON\.parse|\{[^\n]{0,300}\.\.\.(?:stored|saved|cached|parsed|data)/i.test(content);
1815
1877
  if (hasUnsafeMerge) {
1816
1878
  matches.push(...findMatches(
1817
1879
  content,
1818
- /Object\.assign\s*\([^)]*JSON\.parse|\.\.\.JSON\.parse/g,
1880
+ /Object\.assign\s*\([^)]{0,500}JSON\.parse|\.\.\.JSON\.parse/g,
1819
1881
  prototypePollution,
1820
1882
  filePath,
1821
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."
@@ -3361,7 +3423,7 @@ var dangerousInnerHTML = {
3361
3423
  if (/^[A-Z][A-Z0-9_]+$/.test(value)) continue;
3362
3424
  if (/^["'`][^$]*["'`]$/.test(value)) continue;
3363
3425
  if (/JSON\.stringify/i.test(value)) continue;
3364
- const lineNum = content.substring(0, m.index).split("\n").length;
3426
+ const lineNum = lineNumberAt(content, m.index);
3365
3427
  findings.push({
3366
3428
  rule: "VC063",
3367
3429
  title: dangerousInnerHTML.title,
@@ -4103,7 +4165,7 @@ var missingSRI = {
4103
4165
  const re = new RegExp(scriptPattern.source, scriptPattern.flags);
4104
4166
  while ((m = re.exec(content)) !== null) {
4105
4167
  if (!m[0].includes("integrity")) {
4106
- const lineNum = content.substring(0, m.index).split("\n").length;
4168
+ const lineNum = lineNumberAt(content, m.index);
4107
4169
  matches.push({
4108
4170
  rule: "VC084",
4109
4171
  title: missingSRI.title,
@@ -4974,7 +5036,7 @@ var s3BucketNoEncryption = {
4974
5036
  const blockEnd = Math.min(m.index + 2e3, content.length);
4975
5037
  const blockContent = content.substring(m.index, blockEnd);
4976
5038
  if (!/server_side_encryption/.test(blockContent)) {
4977
- const lineNum = content.substring(0, m.index).split("\n").length;
5039
+ const lineNum = lineNumberAt(content, m.index);
4978
5040
  matches.push({
4979
5041
  rule: "VC107",
4980
5042
  title: s3BucketNoEncryption.title,
@@ -5005,7 +5067,7 @@ var securityGroupAllInbound = {
5005
5067
  while ((m = ingressPattern.exec(content)) !== null) {
5006
5068
  if (isCommentLine(content, m.index)) continue;
5007
5069
  if (/from_port\s*=\s*0/.test(m[0]) || !/from_port/.test(m[0])) {
5008
- const lineNum = content.substring(0, m.index).split("\n").length;
5070
+ const lineNum = lineNumberAt(content, m.index);
5009
5071
  matches.push({
5010
5072
  rule: "VC108",
5011
5073
  title: securityGroupAllInbound.title,
@@ -5092,7 +5154,7 @@ var lambdaWithoutVPC = {
5092
5154
  const blockEnd = Math.min(m.index + 2e3, content.length);
5093
5155
  const blockContent = content.substring(m.index, blockEnd);
5094
5156
  if (!/vpc_config\s*\{/.test(blockContent)) {
5095
- const lineNum = content.substring(0, m.index).split("\n").length;
5157
+ const lineNum = lineNumberAt(content, m.index);
5096
5158
  matches.push({
5097
5159
  rule: "VC111",
5098
5160
  title: lambdaWithoutVPC.title,
@@ -5122,7 +5184,7 @@ var dockerLatestTag = {
5122
5184
  while ((m = fromPattern.exec(content)) !== null) {
5123
5185
  const image = m[1];
5124
5186
  if (image.endsWith(":latest") || !image.includes(":") && !image.startsWith("$")) {
5125
- const lineNum = content.substring(0, m.index).split("\n").length;
5187
+ const lineNum = lineNumberAt(content, m.index);
5126
5188
  matches.push({
5127
5189
  rule: "VC112",
5128
5190
  title: dockerLatestTag.title,
@@ -5250,17 +5312,26 @@ var pathTraversal = {
5250
5312
  const findings = [];
5251
5313
  const patterns = [
5252
5314
  /(?:readFile|readFileSync|createReadStream|writeFile|writeFileSync|appendFile|unlink|unlinkSync|access|stat|statSync|existsSync)\s*\(\s*(?:req\.|request\.|ctx\.|params\.|query\.)/gi,
5253
- /(?:path\.join|path\.resolve)\s*\([^)]*(?:req\.|request\.|ctx\.|params\.|query\.)[^)]*\)/gi,
5254
- /(?:res\.sendFile|res\.download)\s*\([^)]*(?:req\.|request\.)/gi,
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,
5255
5317
  /open\s*\(\s*(?:request\.|params\[|args\.)/gi
5256
5318
  ];
5319
+ const MAX_MATCHES = 500;
5257
5320
  for (const pat of patterns) {
5321
+ if (findings.length >= MAX_MATCHES) break;
5258
5322
  let m;
5323
+ let lastIdx = -1;
5259
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;
5260
5331
  if (isCommentLine(content, m.index)) continue;
5261
5332
  const surrounding = content.substring(Math.max(0, m.index - 200), m.index + 200);
5262
5333
  if (/path\.normalize|sanitize|\.replace\(\s*['"]\.\.|\.startsWith\s*\(|realpath/i.test(surrounding)) continue;
5263
- const lineNum = content.substring(0, m.index).split("\n").length;
5334
+ const lineNum = lineNumberAt(content, m.index);
5264
5335
  findings.push({
5265
5336
  rule: "VC117",
5266
5337
  title: pathTraversal.title,
@@ -5291,7 +5362,7 @@ var piiInLogs = {
5291
5362
  while ((m = logPattern.exec(content)) !== null) {
5292
5363
  if (isCommentLine(content, m.index)) continue;
5293
5364
  if (isInsideFixMessage(content, m.index)) continue;
5294
- const lineNum = content.substring(0, m.index).split("\n").length;
5365
+ const lineNum = lineNumberAt(content, m.index);
5295
5366
  findings.push({
5296
5367
  rule: "VC118",
5297
5368
  title: piiInLogs.title,
@@ -5329,7 +5400,7 @@ var hardcodedOAuthSecret = {
5329
5400
  while ((m = pat.exec(content)) !== null) {
5330
5401
  if (isCommentLine(content, m.index)) continue;
5331
5402
  if (/process\.env|os\.environ|ENV\[|getenv|\$\{|<your|CHANGE_ME|xxx|placeholder/i.test(m[0])) continue;
5332
- const lineNum = content.substring(0, m.index).split("\n").length;
5403
+ const lineNum = lineNumberAt(content, m.index);
5333
5404
  findings.push({
5334
5405
  rule: "VC119",
5335
5406
  title: hardcodedOAuthSecret.title,
@@ -5361,7 +5432,7 @@ var missingOAuthState = {
5361
5432
  if (isCommentLine(content, m.index)) continue;
5362
5433
  const surrounding = content.substring(m.index, Math.min(content.length, m.index + 500));
5363
5434
  if (/state\s*[=:]/i.test(surrounding)) continue;
5364
- const lineNum = content.substring(0, m.index).split("\n").length;
5435
+ const lineNum = lineNumberAt(content, m.index);
5365
5436
  findings.push({
5366
5437
  rule: "VC120",
5367
5438
  title: missingOAuthState.title,
@@ -5389,7 +5460,7 @@ var unpinnedGitHubAction = {
5389
5460
  let m;
5390
5461
  while ((m = usesPattern.exec(content)) !== null) {
5391
5462
  if (isCommentLine(content, m.index)) continue;
5392
- const lineNum = content.substring(0, m.index).split("\n").length;
5463
+ const lineNum = lineNumberAt(content, m.index);
5393
5464
  findings.push({
5394
5465
  rule: "VC121",
5395
5466
  title: unpinnedGitHubAction.title,
@@ -5423,7 +5494,7 @@ var deprecatedTLS = {
5423
5494
  let m;
5424
5495
  while ((m = pat.exec(content)) !== null) {
5425
5496
  if (isCommentLine(content, m.index)) continue;
5426
- const lineNum = content.substring(0, m.index).split("\n").length;
5497
+ const lineNum = lineNumberAt(content, m.index);
5427
5498
  findings.push({
5428
5499
  rule: "VC122",
5429
5500
  title: deprecatedTLS.title,
@@ -5459,7 +5530,7 @@ var weakRSAKeySize = {
5459
5530
  let m;
5460
5531
  while ((m = pat.exec(content)) !== null) {
5461
5532
  if (isCommentLine(content, m.index)) continue;
5462
- const lineNum = content.substring(0, m.index).split("\n").length;
5533
+ const lineNum = lineNumberAt(content, m.index);
5463
5534
  findings.push({
5464
5535
  rule: "VC123",
5465
5536
  title: weakRSAKeySize.title,
@@ -5495,7 +5566,7 @@ var ecbModeEncryption = {
5495
5566
  let m;
5496
5567
  while ((m = pat.exec(content)) !== null) {
5497
5568
  if (isCommentLine(content, m.index)) continue;
5498
- const lineNum = content.substring(0, m.index).split("\n").length;
5569
+ const lineNum = lineNumberAt(content, m.index);
5499
5570
  findings.push({
5500
5571
  rule: "VC124",
5501
5572
  title: ecbModeEncryption.title,
@@ -5526,7 +5597,7 @@ var insecurePasswordReset = {
5526
5597
  let m;
5527
5598
  while ((m = weakTokens.exec(content)) !== null) {
5528
5599
  if (isCommentLine(content, m.index)) continue;
5529
- const lineNum = content.substring(0, m.index).split("\n").length;
5600
+ const lineNum = lineNumberAt(content, m.index);
5530
5601
  findings.push({
5531
5602
  rule: "VC125",
5532
5603
  title: insecurePasswordReset.title,
@@ -5543,7 +5614,7 @@ var insecurePasswordReset = {
5543
5614
  if (isCommentLine(content, m.index)) continue;
5544
5615
  const surrounding = content.substring(Math.max(0, m.index - 500), m.index);
5545
5616
  if (!/(?:reset|forgot).*password/i.test(surrounding)) continue;
5546
- const lineNum = content.substring(0, m.index).split("\n").length;
5617
+ const lineNum = lineNumberAt(content, m.index);
5547
5618
  findings.push({
5548
5619
  rule: "VC125",
5549
5620
  title: insecurePasswordReset.title,
@@ -5604,7 +5675,7 @@ var insecureHTTPMethods = {
5604
5675
  if (isCommentLine(content, m.index)) continue;
5605
5676
  const handlerBlock = content.substring(m.index, Math.min(content.length, m.index + 500));
5606
5677
  if (/auth|authenticate|authorize|requireAuth|isAuthenticated|protect|guard|middleware|session|jwt|verify|clerk|getAuth/i.test(handlerBlock)) continue;
5607
- const lineNum = content.substring(0, m.index).split("\n").length;
5678
+ const lineNum = lineNumberAt(content, m.index);
5608
5679
  findings.push({
5609
5680
  rule: "VC127",
5610
5681
  title: insecureHTTPMethods.title,
@@ -5638,7 +5709,7 @@ var httpRequestSmuggling = {
5638
5709
  let m;
5639
5710
  while ((m = pat.exec(content)) !== null) {
5640
5711
  if (isCommentLine(content, m.index)) continue;
5641
- const lineNum = content.substring(0, m.index).split("\n").length;
5712
+ const lineNum = lineNumberAt(content, m.index);
5642
5713
  findings.push({
5643
5714
  rule: "VC128",
5644
5715
  title: httpRequestSmuggling.title,
@@ -5672,7 +5743,7 @@ var unencryptedPII = {
5672
5743
  let m;
5673
5744
  while ((m = pat.exec(content)) !== null) {
5674
5745
  if (isCommentLine(content, m.index)) continue;
5675
- const lineNum = content.substring(0, m.index).split("\n").length;
5746
+ const lineNum = lineNumberAt(content, m.index);
5676
5747
  findings.push({
5677
5748
  rule: "VC129",
5678
5749
  title: unencryptedPII.title,
@@ -5704,7 +5775,7 @@ var missingAuthRateLimit = {
5704
5775
  if (isCommentLine(content, m.index)) continue;
5705
5776
  const surrounding = content.substring(Math.max(0, m.index - 300), Math.min(content.length, m.index + 300));
5706
5777
  if (/rateLimit|rateLimiter|throttle|slowDown|express-rate-limit|rate_limit|RateLimiter|limiter/i.test(surrounding)) continue;
5707
- const lineNum = content.substring(0, m.index).split("\n").length;
5778
+ const lineNum = lineNumberAt(content, m.index);
5708
5779
  findings.push({
5709
5780
  rule: "VC130",
5710
5781
  title: missingAuthRateLimit.title,
@@ -5742,7 +5813,7 @@ var vulnerableDependencies = {
5742
5813
  for (const [pat, message] of vulnerablePackages) {
5743
5814
  let m;
5744
5815
  while ((m = pat.exec(content)) !== null) {
5745
- const lineNum = content.substring(0, m.index).split("\n").length;
5816
+ const lineNum = lineNumberAt(content, m.index);
5746
5817
  findings.push({
5747
5818
  rule: "VC131",
5748
5819
  title: vulnerableDependencies.title,
@@ -5774,7 +5845,7 @@ function secretRuleCheck(content, filePath, pattern, ruleId, title, severity, fi
5774
5845
  const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
5775
5846
  const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
5776
5847
  if (PLACEHOLDER_RE.test(lineText)) continue;
5777
- const lineNum = content.substring(0, m.index).split("\n").length;
5848
+ const lineNum = lineNumberAt(content, m.index);
5778
5849
  findings.push({
5779
5850
  rule: ruleId,
5780
5851
  title,
@@ -5875,7 +5946,7 @@ var hardcodedGCPServiceAccount = {
5875
5946
  const findings = [];
5876
5947
  const m = content.match(/"type"\s*:\s*"service_account"/);
5877
5948
  if (m && m.index !== void 0) {
5878
- const lineNum = content.substring(0, m.index).split("\n").length;
5949
+ const lineNum = lineNumberAt(content, m.index);
5879
5950
  findings.push({
5880
5951
  rule: "VC136",
5881
5952
  title: this.title,
@@ -5981,7 +6052,7 @@ var hardcodedDatadogKey = {
5981
6052
  const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
5982
6053
  const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
5983
6054
  if (PLACEHOLDER_RE.test(lineText)) continue;
5984
- const lineNum = content.substring(0, m.index).split("\n").length;
6055
+ const lineNum = lineNumberAt(content, m.index);
5985
6056
  findings.push({
5986
6057
  rule: "VC141",
5987
6058
  title: this.title,
@@ -6015,7 +6086,7 @@ var hardcodedVercelToken = {
6015
6086
  const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
6016
6087
  const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
6017
6088
  if (PLACEHOLDER_RE.test(lineText)) continue;
6018
- const lineNum = content.substring(0, m.index).split("\n").length;
6089
+ const lineNum = lineNumberAt(content, m.index);
6019
6090
  findings.push({
6020
6091
  rule: "VC142",
6021
6092
  title: this.title,
@@ -6049,7 +6120,7 @@ var hardcodedSupabaseServiceRole = {
6049
6120
  const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
6050
6121
  const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
6051
6122
  if (PLACEHOLDER_RE.test(lineText)) continue;
6052
- const lineNum = content.substring(0, m.index).split("\n").length;
6123
+ const lineNum = lineNumberAt(content, m.index);
6053
6124
  findings.push({
6054
6125
  rule: "VC143",
6055
6126
  title: this.title,
@@ -6113,7 +6184,7 @@ function contextSecretRuleCheck(content, filePath, pattern, ruleId, title, sever
6113
6184
  const lineStart = content.lastIndexOf("\n", m.index - 1) + 1;
6114
6185
  const lineText = content.substring(lineStart, content.indexOf("\n", m.index));
6115
6186
  if (PLACEHOLDER_RE.test(lineText)) continue;
6116
- const lineNum = content.substring(0, m.index).split("\n").length;
6187
+ const lineNum = lineNumberAt(content, m.index);
6117
6188
  findings.push({
6118
6189
  rule: ruleId,
6119
6190
  title,
@@ -6593,7 +6664,7 @@ var ghaPullRequestTargetCheckout = {
6593
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;
6594
6665
  let m;
6595
6666
  while ((m = checkoutPattern.exec(content)) !== null) {
6596
- const lineNum = content.substring(0, m.index).split("\n").length;
6667
+ const lineNum = lineNumberAt(content, m.index);
6597
6668
  findings.push({
6598
6669
  rule: "VC184",
6599
6670
  title: ghaPullRequestTargetCheckout.title,
@@ -6677,7 +6748,7 @@ var ghaThirdPartyActionWithSecrets = {
6677
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;
6678
6749
  let m;
6679
6750
  while ((m = pattern.exec(content)) !== null) {
6680
- const lineNum = content.substring(0, m.index).split("\n").length;
6751
+ const lineNum = lineNumberAt(content, m.index);
6681
6752
  findings.push({
6682
6753
  rule: "VC187",
6683
6754
  title: ghaThirdPartyActionWithSecrets.title,
@@ -6842,7 +6913,7 @@ var pyRequestsVerifyFalse = {
6842
6913
  let m;
6843
6914
  while ((m = pattern.exec(content)) !== null) {
6844
6915
  if (isCommentLine(content, m.index)) continue;
6845
- const lineNum = content.substring(0, m.index).split("\n").length;
6916
+ const lineNum = lineNumberAt(content, m.index);
6846
6917
  findings.push({
6847
6918
  rule: "VC191",
6848
6919
  title: pyRequestsVerifyFalse.title,
@@ -6873,7 +6944,7 @@ var pyJinja2AutoescapeOff = {
6873
6944
  let m;
6874
6945
  while ((m = pattern.exec(content)) !== null) {
6875
6946
  if (isCommentLine(content, m.index)) continue;
6876
- const lineNum = content.substring(0, m.index).split("\n").length;
6947
+ const lineNum = lineNumberAt(content, m.index);
6877
6948
  findings.push({
6878
6949
  rule: "VC192",
6879
6950
  title: pyJinja2AutoescapeOff.title,
@@ -6902,7 +6973,7 @@ var pyTempfileMktemp = {
6902
6973
  let m;
6903
6974
  while ((m = pattern.exec(content)) !== null) {
6904
6975
  if (isCommentLine(content, m.index)) continue;
6905
- const lineNum = content.substring(0, m.index).split("\n").length;
6976
+ const lineNum = lineNumberAt(content, m.index);
6906
6977
  findings.push({
6907
6978
  rule: "VC193",
6908
6979
  title: pyTempfileMktemp.title,
@@ -6944,7 +7015,7 @@ var pyDjangoMarkSafe = {
6944
7015
  let m;
6945
7016
  while ((m = pattern.exec(content)) !== null) {
6946
7017
  if (isCommentLine(content, m.index)) continue;
6947
- const lineNum = content.substring(0, m.index).split("\n").length;
7018
+ const lineNum = lineNumberAt(content, m.index);
6948
7019
  if (seenLines.has(lineNum)) continue;
6949
7020
  seenLines.add(lineNum);
6950
7021
  findings.push({
@@ -6981,7 +7052,7 @@ var pyParamikoAutoAdd = {
6981
7052
  let m;
6982
7053
  while ((m = pattern.exec(content)) !== null) {
6983
7054
  if (isCommentLine(content, m.index)) continue;
6984
- const lineNum = content.substring(0, m.index).split("\n").length;
7055
+ const lineNum = lineNumberAt(content, m.index);
6985
7056
  if (seenLines.has(lineNum)) continue;
6986
7057
  seenLines.add(lineNum);
6987
7058
  findings.push({
@@ -7014,7 +7085,7 @@ var pyDjangoAllowedHostsWildcard = {
7014
7085
  let m;
7015
7086
  while ((m = pattern.exec(content)) !== null) {
7016
7087
  if (isCommentLine(content, m.index)) continue;
7017
- const lineNum = content.substring(0, m.index).split("\n").length;
7088
+ const lineNum = lineNumberAt(content, m.index);
7018
7089
  findings.push({
7019
7090
  rule: "VC196",
7020
7091
  title: pyDjangoAllowedHostsWildcard.title,
@@ -7052,7 +7123,7 @@ var pyJWTDecodeWeakConfig = {
7052
7123
  if (isCommentLine(content, m.index)) continue;
7053
7124
  const args = m[1];
7054
7125
  if (/\balgorithms\s*=/.test(args)) continue;
7055
- const lineNum = content.substring(0, m.index).split("\n").length;
7126
+ const lineNum = lineNumberAt(content, m.index);
7056
7127
  if (seenLines.has(lineNum)) continue;
7057
7128
  seenLines.add(lineNum);
7058
7129
  findings.push({
@@ -7103,7 +7174,7 @@ var llmPromptInjection = {
7103
7174
  let m;
7104
7175
  while ((m = pattern.exec(content)) !== null) {
7105
7176
  if (isCommentLine(content, m.index)) continue;
7106
- const lineNum = content.substring(0, m.index).split("\n").length;
7177
+ const lineNum = lineNumberAt(content, m.index);
7107
7178
  if (seenLines.has(lineNum)) continue;
7108
7179
  seenLines.add(lineNum);
7109
7180
  findings.push({
@@ -7142,7 +7213,7 @@ var llmSystemPromptInjection = {
7142
7213
  let m;
7143
7214
  while ((m = pattern.exec(content)) !== null) {
7144
7215
  if (isCommentLine(content, m.index)) continue;
7145
- const lineNum = content.substring(0, m.index).split("\n").length;
7216
+ const lineNum = lineNumberAt(content, m.index);
7146
7217
  findings.push({
7147
7218
  rule: "VC199",
7148
7219
  title: llmSystemPromptInjection.title,
@@ -7184,7 +7255,7 @@ var llmOutputAsHTML = {
7184
7255
  let m;
7185
7256
  while ((m = pattern.exec(content)) !== null) {
7186
7257
  if (isCommentLine(content, m.index)) continue;
7187
- const lineNum = content.substring(0, m.index).split("\n").length;
7258
+ const lineNum = lineNumberAt(content, m.index);
7188
7259
  if (seenLines.has(lineNum)) continue;
7189
7260
  seenLines.add(lineNum);
7190
7261
  findings.push({
@@ -7221,7 +7292,7 @@ var vectorStoreQueryNoUserFilter = {
7221
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;
7222
7293
  if (/\bnamespace\s*[:=]\s*[`"']?[^,)`"']*(?:user|tenant|org|owner|account|customer|workspace)/i.test(args)) continue;
7223
7294
  if (/\bfilter\s*[:=][\s\S]*?(?:\buser|\btenant|\borg|\bowner|\baccount|\bcustomer|\bworkspace)/i.test(args)) continue;
7224
- const lineNum = content.substring(0, m.index).split("\n").length;
7295
+ const lineNum = lineNumberAt(content, m.index);
7225
7296
  findings.push({
7226
7297
  rule: "VC201",
7227
7298
  title: vectorStoreQueryNoUserFilter.title,
@@ -7258,7 +7329,7 @@ var vectorStoreUpsertNoMetadata = {
7258
7329
  if (/\bnamespace\s*[:=]\s*[`"']?[^,)`"']*(?:user|tenant|org)/i.test(args)) {
7259
7330
  continue;
7260
7331
  }
7261
- const lineNum = content.substring(0, m.index).split("\n").length;
7332
+ const lineNum = lineNumberAt(content, m.index);
7262
7333
  findings.push({
7263
7334
  rule: "VC202",
7264
7335
  title: vectorStoreUpsertNoMetadata.title,
@@ -7312,7 +7383,7 @@ var llmCallNoMaxTokens = {
7312
7383
  if (depth !== 0) continue;
7313
7384
  const args = content.substring(openIdx + 1, i).replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "").replace(/#[^\n]*/g, "");
7314
7385
  if (TOKEN_KW_RE.test(args)) continue;
7315
- const lineNum = content.substring(0, m.index).split("\n").length;
7386
+ const lineNum = lineNumberAt(content, m.index);
7316
7387
  findings.push({
7317
7388
  rule: "VC203",
7318
7389
  title: llmCallNoMaxTokens.title,
@@ -7352,7 +7423,7 @@ var graphqlNoDepthLimit = {
7352
7423
  let m;
7353
7424
  while ((m = anchorRe.exec(content)) !== null) {
7354
7425
  if (isCommentLine(content, m.index)) continue;
7355
- const lineNum = content.substring(0, m.index).split("\n").length;
7426
+ const lineNum = lineNumberAt(content, m.index);
7356
7427
  findings.push({
7357
7428
  rule: "VC204",
7358
7429
  title: graphqlNoDepthLimit.title,
@@ -7386,7 +7457,7 @@ var graphqlNoComplexityLimit = {
7386
7457
  let m;
7387
7458
  while ((m = anchorRe.exec(content)) !== null) {
7388
7459
  if (isCommentLine(content, m.index)) continue;
7389
- const lineNum = content.substring(0, m.index).split("\n").length;
7460
+ const lineNum = lineNumberAt(content, m.index);
7390
7461
  findings.push({
7391
7462
  rule: "VC205",
7392
7463
  title: graphqlNoComplexityLimit.title,
@@ -7416,7 +7487,7 @@ var graphqlCSRFDisabled = {
7416
7487
  let m;
7417
7488
  while ((m = pattern.exec(content)) !== null) {
7418
7489
  if (isCommentLine(content, m.index)) continue;
7419
- const lineNum = content.substring(0, m.index).split("\n").length;
7490
+ const lineNum = lineNumberAt(content, m.index);
7420
7491
  findings.push({
7421
7492
  rule: "VC206",
7422
7493
  title: graphqlCSRFDisabled.title,
@@ -7512,7 +7583,7 @@ var webhookMissingIdempotency = {
7512
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 [];
7513
7584
  const m = content.match(/constructEvent|new\s+Webhook\s*\(|verifyHeader/i);
7514
7585
  if (!m || m.index === void 0) return [];
7515
- const line = content.substring(0, m.index).split("\n").length;
7586
+ const line = lineNumberAt(content, m.index);
7516
7587
  return filterSilenced([{
7517
7588
  rule: "VC209",
7518
7589
  title: webhookMissingIdempotency.title,
@@ -7539,7 +7610,7 @@ var middlewareMatcherExcludesApi = {
7539
7610
  if (!/\(\?!\s*[^)]*\bapi\b/.test(content)) return [];
7540
7611
  const m = content.match(/matcher\s*:/);
7541
7612
  if (!m || m.index === void 0) return [];
7542
- const line = content.substring(0, m.index).split("\n").length;
7613
+ const line = lineNumberAt(content, m.index);
7543
7614
  return filterSilenced([{
7544
7615
  rule: "VC210",
7545
7616
  title: middlewareMatcherExcludesApi.title,
@@ -7577,7 +7648,7 @@ var secretInURLParam = {
7577
7648
  if (isCommentLine(content, m.index)) continue;
7578
7649
  if (isInsideFixMessage(content, m.index)) continue;
7579
7650
  if (isInlineSilenced(content, m.index, "VC146")) 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: "VC146",
7583
7654
  title: this.title,
@@ -7608,7 +7679,7 @@ var secretLoggedToConsole = {
7608
7679
  while ((m = pattern.exec(content)) !== null) {
7609
7680
  if (isCommentLine(content, m.index)) continue;
7610
7681
  if (isInsideFixMessage(content, m.index)) continue;
7611
- const lineNum = content.substring(0, m.index).split("\n").length;
7682
+ const lineNum = lineNumberAt(content, m.index);
7612
7683
  findings.push({
7613
7684
  rule: "VC147",
7614
7685
  title: this.title,
@@ -7638,7 +7709,7 @@ var secretInErrorResponse = {
7638
7709
  while ((m = pattern.exec(content)) !== null) {
7639
7710
  if (isCommentLine(content, m.index)) continue;
7640
7711
  if (isInsideFixMessage(content, m.index)) continue;
7641
- const lineNum = content.substring(0, m.index).split("\n").length;
7712
+ const lineNum = lineNumberAt(content, m.index);
7642
7713
  findings.push({
7643
7714
  rule: "VC148",
7644
7715
  title: this.title,
@@ -7677,7 +7748,7 @@ var secretInBundleConfig = {
7677
7748
  while ((m = re.exec(content)) !== null) {
7678
7749
  if (isCommentLine(content, m.index)) continue;
7679
7750
  if (isInsideFixMessage(content, m.index)) continue;
7680
- const lineNum = content.substring(0, m.index).split("\n").length;
7751
+ const lineNum = lineNumberAt(content, m.index);
7681
7752
  findings.push({
7682
7753
  rule: "VC149",
7683
7754
  title: this.title,
@@ -7715,7 +7786,7 @@ var secretInHTMLAttribute = {
7715
7786
  while ((m = re.exec(content)) !== null) {
7716
7787
  if (isCommentLine(content, m.index)) continue;
7717
7788
  if (isInsideFixMessage(content, m.index)) continue;
7718
- const lineNum = content.substring(0, m.index).split("\n").length;
7789
+ const lineNum = lineNumberAt(content, m.index);
7719
7790
  findings.push({
7720
7791
  rule: "VC150",
7721
7792
  title: this.title,
@@ -7742,9 +7813,9 @@ var secretInCLIArgument = {
7742
7813
  if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb)$/)) return [];
7743
7814
  const findings = [];
7744
7815
  const patterns = [
7745
- /(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]*\$\{[^}]*(?:api[_-]?key|secret|token|password|credentials)/gi,
7746
- /(?:exec|execSync|spawn|spawnSync|child_process)\s*\([^)]*["']\s*\+\s*(?:api[_-]?key|secret|token|password|credentials)/gi,
7747
- /(?:subprocess|os\.system|os\.popen)\s*\([^)]*\{[^}]*(?:api[_-]?key|secret|token|password|credentials)/gi
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
7748
7819
  ];
7749
7820
  for (const p of patterns) {
7750
7821
  let m;
@@ -7752,7 +7823,7 @@ var secretInCLIArgument = {
7752
7823
  while ((m = re.exec(content)) !== null) {
7753
7824
  if (isCommentLine(content, m.index)) continue;
7754
7825
  if (isInsideFixMessage(content, m.index)) continue;
7755
- const lineNum = content.substring(0, m.index).split("\n").length;
7826
+ const lineNum = lineNumberAt(content, m.index);
7756
7827
  findings.push({
7757
7828
  rule: "VC151",
7758
7829
  title: this.title,
@@ -7798,7 +7869,7 @@ var webhookSignatureVerification = {
7798
7869
  if (svc.verify.test(content)) continue;
7799
7870
  const m = content.match(/export\s+(?:async\s+)?function\s+POST/);
7800
7871
  if (!m || m.index === void 0) continue;
7801
- const lineNum = content.substring(0, m.index).split("\n").length;
7872
+ const lineNum = lineNumberAt(content, m.index);
7802
7873
  findings.push({
7803
7874
  rule: "VC152",
7804
7875
  title: `${svc.name} Webhook Missing Signature Verification`,
@@ -7865,7 +7936,7 @@ var missingRequestValidation = {
7865
7936
  if (/if\s*\(\s*!(?:body|parsed|data|payload)\.|throw.*(?:missing|invalid|required)/i.test(content)) return [];
7866
7937
  const m = content.match(/export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH)/);
7867
7938
  if (!m || m.index === void 0) return [];
7868
- const lineNum = content.substring(0, m.index).split("\n").length;
7939
+ const lineNum = lineNumberAt(content, m.index);
7869
7940
  return [{
7870
7941
  rule: "VC154",
7871
7942
  title: this.title,
@@ -7892,7 +7963,7 @@ var missingAIRateLimit = {
7892
7963
  if (/rateLimit|rateLimiter|throttle|checkRateLimit|limiter|slowDown|express-rate-limit/i.test(content)) return [];
7893
7964
  const m = content.match(/export\s+(?:async\s+)?function\s+(?:POST|GET)/i) || content.match(/\.(post|get)\s*\(/i);
7894
7965
  if (!m || m.index === void 0) return [];
7895
- const lineNum = content.substring(0, m.index).split("\n").length;
7966
+ const lineNum = lineNumberAt(content, m.index);
7896
7967
  return [{
7897
7968
  rule: "VC155",
7898
7969
  title: this.title,
@@ -7921,7 +7992,7 @@ var missingPagination = {
7921
7992
  if (/findUnique|findFirst|findById|\.findOne|WHERE.*id\s*=/i.test(content)) return [];
7922
7993
  const m = content.match(/export\s+(?:async\s+)?function\s+GET/);
7923
7994
  if (!m || m.index === void 0) return [];
7924
- const lineNum = content.substring(0, m.index).split("\n").length;
7995
+ const lineNum = lineNumberAt(content, m.index);
7925
7996
  return [{
7926
7997
  rule: "VC156",
7927
7998
  title: this.title,
@@ -7982,7 +8053,7 @@ var insecureDirectObjectReference = {
7982
8053
  if (/requireUser|requireUserForApi|getServerSession|auth\(\)/i.test(content)) return [];
7983
8054
  const m = content.match(/params\.id|params\.(?:slug|uuid)|req\.params\./);
7984
8055
  if (!m || m.index === void 0) return [];
7985
- const lineNum = content.substring(0, m.index).split("\n").length;
8056
+ const lineNum = lineNumberAt(content, m.index);
7986
8057
  return [{
7987
8058
  rule: "VC158",
7988
8059
  title: this.title,
@@ -8288,6 +8359,17 @@ function runCustomRules(content, filePath, disabledRules = [], tier = "free", ex
8288
8359
  return findings;
8289
8360
  }
8290
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;
8291
8373
  if (/onboarding|demo-data|example-vulnerable|code-sample/i.test(filePath)) return findings;
8292
8374
  const ruleset = tier === "pro" && extraRules.length > 0 ? [...freeRules, ...extraRules] : freeRules;
8293
8375
  for (const rule of ruleset) {