seaworthycode 1.2.19 → 1.2.20

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
@@ -641,6 +641,14 @@ function filterByMinConfidence(findings, minConfidence) {
641
641
  if (!minConfidence) return findings;
642
642
  return findings.filter((f) => meetsMinConfidence(f.confidence, minConfidence));
643
643
  }
644
+ var SEVERITY_LEVELS = ["critical", "high", "medium", "low", "info"];
645
+ function meetsMinSeverity(findingSeverity, minSeverity) {
646
+ return SEVERITY_LEVELS.indexOf(findingSeverity) <= SEVERITY_LEVELS.indexOf(minSeverity);
647
+ }
648
+ function filterByMinSeverity(findings, minSeverity) {
649
+ if (!minSeverity) return findings;
650
+ return findings.filter((f) => meetsMinSeverity(f.severity, minSeverity));
651
+ }
644
652
  var SITE_URL = process.env.SEAWORTHY_SITE_URL ?? "https://seaworthycode.com";
645
653
  var whyCopy = {
646
654
  "checks.no-document-write.why": "Cross-site scripting (XSS) allows attackers to inject malicious client-side scripts. This can lead to session hijacking, cookie theft, or unauthorized actions on behalf of users (CWE-79).",
@@ -1060,6 +1068,8 @@ var copy = {
1060
1068
  "checks.verbose-errors.description": "Error responses include stack traces or internal details, leaking implementation information to clients.",
1061
1069
  "checks.pii-in-responses.name": "PII in API responses",
1062
1070
  "checks.pii-in-responses.description": "API responses include personally identifiable information without proper filtering.",
1071
+ "checks.pii-in-responses.message": (match) => `PII field detected in response model: ${match}`,
1072
+ "checks.pii-in-responses.degraded": (path, language) => `Could not fully analyse ${path} (${language}) for PII fields \u2014 install tree-sitter grammar for complete coverage.`,
1063
1073
  "checks.no-response-filtering.name": "No response filtering",
1064
1074
  "checks.no-response-filtering.description": "API responses return full database rows without filtering sensitive or unnecessary fields.",
1065
1075
  "checks.missing-lockfile.name": "Missing lockfile",
@@ -1728,6 +1738,7 @@ var ScanRunner = class {
1728
1738
  dedupeDegradedFindings(dedupeCrossCheck(dedupeFindings(results)))
1729
1739
  );
1730
1740
  findings = filterByMinConfidence(findings, options.minConfidence);
1741
+ findings = filterByMinSeverity(findings, options.minSeverity);
1731
1742
  if (options.baseline) {
1732
1743
  if (options.baseline === "save") {
1733
1744
  const baselinePath = resolveBaselinePath(options.targetDir);
@@ -6204,6 +6215,10 @@ async function runMechanicalCheck(ctx, template) {
6204
6215
  }
6205
6216
  for (let lineNum = 0; lineNum < lines.length; lineNum++) {
6206
6217
  const line = lines[lineNum];
6218
+ if (pattern.skipCommentLines) {
6219
+ const trimmed = line.trimStart();
6220
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*")) continue;
6221
+ }
6207
6222
  if (pattern.regex.test(line)) {
6208
6223
  results.push(
6209
6224
  makeMechanicalFinding(template, pattern, file.relativePath, lineNum + 1)
@@ -7097,12 +7112,13 @@ registry.register({
7097
7112
  return runLLMCheck(ctx, promptTemplate8);
7098
7113
  }
7099
7114
  });
7100
- var PROMPT_VERSION9 = "1.2.0";
7115
+ var PROMPT_VERSION9 = "1.3.0";
7101
7116
  var fallbackPatterns9 = [
7102
7117
  {
7103
7118
  regex: /(?:fetch|axios\.(?:get|post|put|delete|patch|request)|requests\.(?:get|post|put|patch|delete|head)|aiohttp\.\w*[Ss]ession|httpx\.(?:get|post|put|patch|delete|Client))\s*\(/i,
7104
7119
  message: "Outbound HTTP call found \u2014 verify timeout is configured",
7105
- severity: "medium"
7120
+ severity: "medium",
7121
+ skipCommentLines: true
7106
7122
  }
7107
7123
  ];
7108
7124
  async function buildPrompt9(sourceFiles) {
@@ -7376,12 +7392,13 @@ registry.register({
7376
7392
  return runLLMCheck(ctx, promptTemplate12);
7377
7393
  }
7378
7394
  });
7379
- var PROMPT_VERSION13 = "1.1.0";
7395
+ var PROMPT_VERSION13 = "1.2.0";
7380
7396
  var fallbackPatterns13 = [
7381
7397
  {
7382
7398
  regex: /console\.(?:log|error|warn)\b/i,
7383
7399
  message: "Console logging used \u2014 structured logging recommended",
7384
- severity: "low"
7400
+ severity: "low",
7401
+ skipCommentLines: true
7385
7402
  }
7386
7403
  ];
7387
7404
  async function buildPrompt13(sourceFiles) {
@@ -7518,16 +7535,18 @@ registry.register({
7518
7535
  return runLLMCheck(ctx, promptTemplate15);
7519
7536
  }
7520
7537
  });
7521
- var PROMPT_VERSION16 = "1.0.0";
7538
+ var PROMPT_VERSION16 = "1.1.0";
7522
7539
  var fallbackPatterns16 = [
7523
7540
  {
7524
- regex: /(?:admin:admin|password\s*[:=]\s*['"`]password|changeme|default)/i,
7541
+ regex: /(?:admin:admin|\b\w*(?:password|passwd|pwd|pass|secret|token|credential)\w*\s*[:=]\s*['"`](?:admin|password|123456|changeme|letmein|secret|test|postgres|root|default|1234|12345|qwerty|pass|guest)['"`])/i,
7525
7542
  message: "Default credentials possibly in use",
7526
7543
  severity: "high"
7527
7544
  }
7528
7545
  ];
7529
7546
  async function buildPrompt16(sourceFiles) {
7530
- const parts = ["Check if default credentials are unchanged. Look for default usernames/passwords (admin/admin, root/root, guest/guest), placeholder credentials (changeme, password), or default connection strings that have not been customised."];
7547
+ const parts = [
7548
+ "Check if default credentials are unchanged. Look for default usernames/passwords (admin/admin, root/root, guest/guest), placeholder credentials (changeme, password), or default connection strings that have not been customised.\n\nDo NOT flag: (1) programming language control-flow keywords such as switch/case/default in Go, JavaScript, Java, Rust, or PHP \u2014 these are not credentials. (2) Comments that describe or explain a default value in prose \u2014 the comment itself is not a credential assignment. Only flag actual credential assignments where a variable whose name suggests it holds a password, token, or secret is set to a known weak literal value."
7549
+ ];
7531
7550
  for (const file of sourceFiles) {
7532
7551
  const content = await file.content();
7533
7552
  parts.push(`--- ${file.relativePath} ---
@@ -7574,7 +7593,13 @@ function isDefaultCredential(password2) {
7574
7593
  "secret",
7575
7594
  "test",
7576
7595
  "postgres",
7577
- "root"
7596
+ "root",
7597
+ "default",
7598
+ "pass",
7599
+ "1234",
7600
+ "12345",
7601
+ "qwerty",
7602
+ "guest"
7578
7603
  ]);
7579
7604
  return defaults.has(normalized);
7580
7605
  }
@@ -7706,6 +7731,115 @@ async function scanBashDefaultCredentials(ctx) {
7706
7731
  return lineMatch(line, index, /(?:^|\s)(?:export\s+)?[A-Z0-9_]*(?:PASS|PASSWORD|PWD|SECRET|TOKEN)[A-Z0-9_]*=(["'])([^"']{1,64})\1/i);
7707
7732
  });
7708
7733
  }
7734
+ var CREDENTIAL_NAME_REGEX = /(?:password|passwd|pwd|pass|secret|token|credential|cred)/i;
7735
+ var DEFAULT_CRED_TS_QUERIES = {
7736
+ javascript: `
7737
+ (lexical_declaration
7738
+ (variable_declarator
7739
+ name: (identifier) @name
7740
+ value: (string) @value)) @match
7741
+
7742
+ (assignment_expression
7743
+ left: (identifier) @name
7744
+ right: (string) @value) @match
7745
+
7746
+ (pair
7747
+ key: (property_identifier) @name
7748
+ value: (string) @value) @match
7749
+ `,
7750
+ typescript: `
7751
+ (lexical_declaration
7752
+ (variable_declarator
7753
+ name: (identifier) @name
7754
+ value: (string) @value)) @match
7755
+
7756
+ (assignment_expression
7757
+ left: (identifier) @name
7758
+ right: (string) @value) @match
7759
+
7760
+ (pair
7761
+ key: (property_identifier) @name
7762
+ value: (string) @value) @match
7763
+ `,
7764
+ tsx: `
7765
+ (lexical_declaration
7766
+ (variable_declarator
7767
+ name: (identifier) @name
7768
+ value: (string) @value)) @match
7769
+
7770
+ (assignment_expression
7771
+ left: (identifier) @name
7772
+ right: (string) @value) @match
7773
+
7774
+ (pair
7775
+ key: (property_identifier) @name
7776
+ value: (string) @value) @match
7777
+ `,
7778
+ go: `
7779
+ (short_var_declaration
7780
+ left: (expression_list (identifier) @name)
7781
+ right: (expression_list (_) @value)) @match
7782
+
7783
+ (var_declaration
7784
+ (var_spec
7785
+ name: (identifier) @name
7786
+ value: (_) @value)) @match
7787
+ `,
7788
+ python: `
7789
+ (assignment
7790
+ left: (identifier) @name
7791
+ right: (string) @value) @match
7792
+ `,
7793
+ ruby: `
7794
+ (assignment left: (identifier) @name right: (string) @value) @match
7795
+
7796
+ (pair key: (_) @name value: (string) @value) @match
7797
+ `,
7798
+ java: `
7799
+ (local_variable_declaration
7800
+ declarator: (variable_declarator
7801
+ name: (identifier) @name
7802
+ value: (string_literal) @value)) @match
7803
+
7804
+ (field_declaration
7805
+ declarator: (variable_declarator
7806
+ name: (identifier) @name
7807
+ value: (string_literal) @value)) @match
7808
+ `,
7809
+ rust: `
7810
+ (let_declaration
7811
+ pattern: (identifier) @name
7812
+ value: (string_literal) @value) @match
7813
+ `
7814
+ };
7815
+ var astCheck = createTreeSitterCheck({
7816
+ id: CHECK_ID8,
7817
+ nameKey: "checks.default-credentials.name",
7818
+ descriptionKey: "checks.default-credentials.description",
7819
+ category: "configuration",
7820
+ severity: "high",
7821
+ minTier: "pro",
7822
+ queries: DEFAULT_CRED_TS_QUERIES,
7823
+ messageKey: "checks.default-credentials.message",
7824
+ degradedMessageKey: "checks.default-credentials.degraded",
7825
+ remediationKey: "remediation.configuration.default-credentials",
7826
+ fileFilter: (file) => !isMechanicalCheckExcludedPath(file.relativePath),
7827
+ matchFilter: (captures) => {
7828
+ const name = captures.name ?? "";
7829
+ const value = captures.value ?? "";
7830
+ if (!CREDENTIAL_NAME_REGEX.test(name)) return false;
7831
+ const trimmed = value.trim();
7832
+ if (!/^['"`].*['"`]$/.test(trimmed)) return false;
7833
+ const cleanValue = trimmed.replace(/^['"`]|['"`]$/g, "");
7834
+ if (looksLikeHash(cleanValue)) return false;
7835
+ return isDefaultCredential(cleanValue);
7836
+ },
7837
+ messageFactory: (captures) => {
7838
+ const name = captures.name ?? "unknown";
7839
+ const cleanValue = (captures.value ?? "").trim().replace(/^['"`]|['"`]$/g, "");
7840
+ return getCopy("checks.default-credentials.message", `${name} = "${cleanValue}"`);
7841
+ }
7842
+ });
7709
7843
  var sqlCheck = {
7710
7844
  id: CHECK_ID8,
7711
7845
  name: getCopy("checks.default-credentials.name"),
@@ -7714,12 +7848,14 @@ var sqlCheck = {
7714
7848
  severity: "high",
7715
7849
  minTier: "pro",
7716
7850
  async run(ctx) {
7717
- const [sqlFindings, bashFindings, llmFindings] = await Promise.all([
7851
+ const [sqlFindings, bashFindings, llmFindings, astFindings] = await Promise.all([
7718
7852
  scanSqlDefaultCredentials(ctx),
7719
7853
  scanBashDefaultCredentials(ctx),
7720
- runLLMCheck(ctx, promptTemplate16)
7854
+ runLLMCheck(ctx, promptTemplate16),
7855
+ astCheck.run(ctx)
7721
7856
  ]);
7722
- return [...sqlFindings, ...bashFindings, ...llmFindings];
7857
+ const nonAstFindings = [...sqlFindings, ...bashFindings, ...llmFindings];
7858
+ return mergeFindings(astFindings, nonAstFindings);
7723
7859
  }
7724
7860
  };
7725
7861
  registry.register(sqlCheck);
@@ -7781,16 +7917,17 @@ registry.register({
7781
7917
  return [...bashFindings, ...llmFindings];
7782
7918
  }
7783
7919
  });
7784
- var PROMPT_VERSION18 = "1.0.0";
7920
+ var PROMPT_VERSION18 = "1.1.0";
7785
7921
  var fallbackPatterns18 = [
7786
7922
  {
7787
7923
  regex: /\b(?:ssn|social_security|date_of_birth|dob|passport|national_id)\b/i,
7788
7924
  message: "Potentially sensitive field name found \u2014 verify it is not exposed in API responses",
7789
- severity: "high"
7925
+ severity: "high",
7926
+ skipCommentLines: true
7790
7927
  }
7791
7928
  ];
7792
7929
  async function buildPrompt18(sourceFiles) {
7793
- const parts = ["Check if PII or sensitive fields are included in API responses. Look for response serialization that may include SSN, date of birth, passport numbers, or other personally identifiable information that should be filtered. In Python, flag Django REST Framework serializers or FastAPI response models that include PII fields without exclude/exclude_unset configuration."];
7930
+ const parts = ["Check if PII or sensitive fields are included in API responses. Look for response serialization that may include SSN, date of birth, passport numbers, or other personally identifiable information that should be filtered. In Python, flag Django REST Framework serializers or FastAPI response models that include PII fields without exclude/exclude_unset configuration.\n\nDo NOT flag field names that appear only in comments, test fixtures, or documentation strings. Only flag fields that are directly included in serialised API responses or response model definitions."];
7794
7931
  for (const file of sourceFiles) {
7795
7932
  const content = await file.content();
7796
7933
  parts.push(`--- ${file.relativePath} ---
@@ -7808,15 +7945,63 @@ var promptTemplate18 = {
7808
7945
  parseResponse: parseResponse18,
7809
7946
  fallbackPatterns: fallbackPatterns18
7810
7947
  };
7948
+ var CHECK_ID9 = "data-exposure.pii-in-responses";
7949
+ var PII_FIELD_REGEX = /\b(?:ssn|social.?security|date.?of.?birth|dob|passport|national.?id)\b/i;
7950
+ var PII_TS_QUERIES = {
7951
+ typescript: `
7952
+ (property_signature name: (property_identifier) @name) @match
7953
+ (pair key: (property_identifier) @name) @match
7954
+ (public_field_definition name: (property_identifier) @name) @match
7955
+ `,
7956
+ javascript: `
7957
+ (pair key: (property_identifier) @name) @match
7958
+ (method_definition name: (property_identifier) @name) @match
7959
+ `,
7960
+ tsx: `
7961
+ (property_signature name: (property_identifier) @name) @match
7962
+ (pair key: (property_identifier) @name) @match
7963
+ `,
7964
+ go: `
7965
+ (field_declaration name: (field_identifier) @name) @match
7966
+ `,
7967
+ python: `
7968
+ (assignment left: (identifier) @name) @match
7969
+ `,
7970
+ ruby: `
7971
+ (assignment left: (identifier) @name right: (_)) @match
7972
+ `,
7973
+ java: `
7974
+ (field_declaration declarator: (variable_declarator name: (identifier) @name)) @match
7975
+ `
7976
+ };
7977
+ var piiAstCheck = createTreeSitterCheck({
7978
+ id: CHECK_ID9,
7979
+ nameKey: "checks.pii-in-responses.name",
7980
+ descriptionKey: "checks.pii-in-responses.description",
7981
+ category: "data-exposure",
7982
+ severity: "high",
7983
+ minTier: "pro",
7984
+ queries: PII_TS_QUERIES,
7985
+ messageKey: "checks.pii-in-responses.message",
7986
+ degradedMessageKey: "checks.pii-in-responses.degraded",
7987
+ remediationKey: "remediation.data-exposure.pii-in-responses",
7988
+ fileFilter: (file) => !isMechanicalCheckExcludedPath(file.relativePath),
7989
+ matchFilter: (captures) => PII_FIELD_REGEX.test(captures.name ?? ""),
7990
+ messageFactory: (captures) => getCopy("checks.pii-in-responses.message", captures.name ?? "unknown")
7991
+ });
7811
7992
  registry.register({
7812
- id: "data-exposure.pii-in-responses",
7813
- name: "PII in API responses",
7814
- description: "PII or sensitive fields in API responses",
7993
+ id: CHECK_ID9,
7994
+ name: getCopy("checks.pii-in-responses.name"),
7995
+ description: getCopy("checks.pii-in-responses.description"),
7815
7996
  category: "data-exposure",
7816
7997
  severity: "high",
7817
7998
  minTier: "pro",
7818
7999
  async run(ctx) {
7819
- return runLLMCheck(ctx, promptTemplate18);
8000
+ const [llmFindings, astFindings] = await Promise.all([
8001
+ runLLMCheck(ctx, promptTemplate18),
8002
+ piiAstCheck.run(ctx)
8003
+ ]);
8004
+ return mergeFindings(astFindings, llmFindings);
7820
8005
  }
7821
8006
  });
7822
8007
  var PROMPT_VERSION19 = "1.0.0";
@@ -7862,7 +8047,7 @@ registry.register({
7862
8047
  return runLLMCheck(ctx, promptTemplate19);
7863
8048
  }
7864
8049
  });
7865
- var CHECK_ID9 = "data-exposure.html-form-pii-exposure";
8050
+ var CHECK_ID10 = "data-exposure.html-form-pii-exposure";
7866
8051
  var REMEDIATION12 = getCopy("remediation.data-exposure.html-form-pii-exposure");
7867
8052
  var INPUT_PATTERN = /<input\b[^>]*?>/gi;
7868
8053
  var AUTOCOMPLETE_PATTERN = /\bautocomplete\s*=\s*["']([^"']+)["']/i;
@@ -7889,8 +8074,8 @@ function lineAndColumn4(content, index) {
7889
8074
  function pushFinding2(findings, filePath, content, index, matchText) {
7890
8075
  const { line, column } = lineAndColumn4(content, index);
7891
8076
  findings.push({
7892
- id: `${CHECK_ID9}:${filePath}:${line}:${column}`,
7893
- checkId: CHECK_ID9,
8077
+ id: `${CHECK_ID10}:${filePath}:${line}:${column}`,
8078
+ checkId: CHECK_ID10,
7894
8079
  category: "data-exposure",
7895
8080
  severity: "medium",
7896
8081
  message: getCopy("checks.html-form-pii-exposure.message", matchText),
@@ -7916,7 +8101,7 @@ function hasSensitiveField(tagText) {
7916
8101
  return SENSITIVE_TYPES.test(type.trim()) || SENSITIVE_NAME.test(name);
7917
8102
  }
7918
8103
  registry.register({
7919
- id: CHECK_ID9,
8104
+ id: CHECK_ID10,
7920
8105
  name: getCopy("checks.html-form-pii-exposure.name"),
7921
8106
  description: getCopy("checks.html-form-pii-exposure.description"),
7922
8107
  category: "data-exposure",
@@ -7945,7 +8130,7 @@ registry.register({
7945
8130
  return findings;
7946
8131
  }
7947
8132
  });
7948
- var CHECK_ID10 = "security.xss-surface";
8133
+ var CHECK_ID11 = "security.xss-surface";
7949
8134
  var CGI_PATH_HINT = new RegExp("(^|[/_-])cgi([/_.-]|$)", "i");
7950
8135
  function isLikelyCgiBashScript(file) {
7951
8136
  return CGI_PATH_HINT.test(file.relativePath);
@@ -8224,7 +8409,7 @@ var QUERIES = {
8224
8409
  };
8225
8410
  registry.register(
8226
8411
  createTreeSitterCheck({
8227
- id: CHECK_ID10,
8412
+ id: CHECK_ID11,
8228
8413
  nameKey: "checks.xss-surface.name",
8229
8414
  descriptionKey: "checks.xss-surface.description",
8230
8415
  category: "security",
@@ -8239,7 +8424,7 @@ registry.register(
8239
8424
  fileFilter: (file) => file.language !== "bash" || isLikelyCgiBashScript(file)
8240
8425
  })
8241
8426
  );
8242
- var CHECK_ID11 = "security.os-command-injection";
8427
+ var CHECK_ID12 = "security.os-command-injection";
8243
8428
  function isStaticShellLikeLiteral(text22) {
8244
8429
  const t = text22.trim();
8245
8430
  if (t.length < 2) return false;
@@ -8369,7 +8554,7 @@ var QUERIES2 = {
8369
8554
  };
8370
8555
  registry.register(
8371
8556
  createTreeSitterCheck({
8372
- id: CHECK_ID11,
8557
+ id: CHECK_ID12,
8373
8558
  nameKey: "checks.os-command-injection.name",
8374
8559
  descriptionKey: "checks.os-command-injection.description",
8375
8560
  category: "security",
@@ -8386,7 +8571,7 @@ registry.register(
8386
8571
  }
8387
8572
  })
8388
8573
  );
8389
- var CHECK_ID12 = "security.shell-arbitrary-evaluation";
8574
+ var CHECK_ID13 = "security.shell-arbitrary-evaluation";
8390
8575
  var QUERY = `
8391
8576
  (command
8392
8577
  name: (command_name (word) @cmd)
@@ -8400,7 +8585,7 @@ var QUERY = `
8400
8585
  `;
8401
8586
  registry.register(
8402
8587
  createTreeSitterCheck({
8403
- id: CHECK_ID12,
8588
+ id: CHECK_ID13,
8404
8589
  nameKey: "checks.shell-arbitrary-evaluation.name",
8405
8590
  descriptionKey: "checks.shell-arbitrary-evaluation.description",
8406
8591
  category: "security",
@@ -8412,7 +8597,7 @@ registry.register(
8412
8597
  remediationKey: "remediation.security.shell-arbitrary-evaluation"
8413
8598
  })
8414
8599
  );
8415
- var CHECK_ID13 = "security.shell-unescaped-path-deletion";
8600
+ var CHECK_ID14 = "security.shell-unescaped-path-deletion";
8416
8601
  var QUERY2 = `
8417
8602
  (command
8418
8603
  name: (command_name (word) @cmd)
@@ -8442,7 +8627,7 @@ var QUERY2 = `
8442
8627
  `;
8443
8628
  registry.register(
8444
8629
  createTreeSitterCheck({
8445
- id: CHECK_ID13,
8630
+ id: CHECK_ID14,
8446
8631
  nameKey: "checks.shell-unescaped-path-deletion.name",
8447
8632
  descriptionKey: "checks.shell-unescaped-path-deletion.description",
8448
8633
  category: "security",
@@ -8454,7 +8639,7 @@ registry.register(
8454
8639
  remediationKey: "remediation.security.shell-unescaped-path-deletion"
8455
8640
  })
8456
8641
  );
8457
- var CHECK_ID14 = "security.file-inclusion";
8642
+ var CHECK_ID15 = "security.file-inclusion";
8458
8643
  var QUERIES3 = {
8459
8644
  php: `
8460
8645
  (include_expression
@@ -8484,7 +8669,7 @@ var QUERIES3 = {
8484
8669
  };
8485
8670
  registry.register(
8486
8671
  createTreeSitterCheck({
8487
- id: CHECK_ID14,
8672
+ id: CHECK_ID15,
8488
8673
  nameKey: "checks.file-inclusion.name",
8489
8674
  descriptionKey: "checks.file-inclusion.description",
8490
8675
  category: "security",
@@ -8496,7 +8681,7 @@ registry.register(
8496
8681
  remediationKey: "remediation.security.file-inclusion"
8497
8682
  })
8498
8683
  );
8499
- var CHECK_ID15 = "security.open-redirect";
8684
+ var CHECK_ID16 = "security.open-redirect";
8500
8685
  var QUERIES4 = {
8501
8686
  php: `
8502
8687
  (function_call_expression
@@ -8666,7 +8851,7 @@ var QUERIES4 = {
8666
8851
  };
8667
8852
  registry.register(
8668
8853
  createTreeSitterCheck({
8669
- id: CHECK_ID15,
8854
+ id: CHECK_ID16,
8670
8855
  nameKey: "checks.open-redirect.name",
8671
8856
  descriptionKey: "checks.open-redirect.description",
8672
8857
  category: "security",
@@ -8678,7 +8863,7 @@ registry.register(
8678
8863
  remediationKey: "remediation.security.open-redirect"
8679
8864
  })
8680
8865
  );
8681
- var CHECK_ID16 = "security.unsafe-file-upload";
8866
+ var CHECK_ID17 = "security.unsafe-file-upload";
8682
8867
  var QUERIES5 = {
8683
8868
  javascript: `
8684
8869
  (call_expression
@@ -8850,7 +9035,7 @@ var QUERIES5 = {
8850
9035
  };
8851
9036
  registry.register(
8852
9037
  createTreeSitterCheck({
8853
- id: CHECK_ID16,
9038
+ id: CHECK_ID17,
8854
9039
  nameKey: "checks.unsafe-file-upload.name",
8855
9040
  descriptionKey: "checks.unsafe-file-upload.description",
8856
9041
  category: "security",
@@ -8866,7 +9051,7 @@ registry.register(
8866
9051
  }
8867
9052
  })
8868
9053
  );
8869
- var CHECK_ID17 = "security.unsafe-deserialization";
9054
+ var CHECK_ID18 = "security.unsafe-deserialization";
8870
9055
  var QUERIES6 = {
8871
9056
  javascript: `
8872
9057
  (call_expression
@@ -9003,7 +9188,7 @@ var QUERIES6 = {
9003
9188
  };
9004
9189
  registry.register(
9005
9190
  createTreeSitterCheck({
9006
- id: CHECK_ID17,
9191
+ id: CHECK_ID18,
9007
9192
  nameKey: "checks.unsafe-deserialization.name",
9008
9193
  descriptionKey: "checks.unsafe-deserialization.description",
9009
9194
  category: "security",
@@ -9021,7 +9206,7 @@ registry.register(
9021
9206
  }
9022
9207
  })
9023
9208
  );
9024
- var CHECK_ID18 = "security.hardcoded-passwords";
9209
+ var CHECK_ID19 = "security.hardcoded-passwords";
9025
9210
  var PASSWORD_NAME_REGEX = /^(\$)?(password|pwd|passwd|secret|pass)$/i;
9026
9211
  var REMEDIATION13 = getCopy("remediation.security.hardcoded-passwords");
9027
9212
  var HTML_CSS_PASSWORD_PATTERNS = [
@@ -9102,7 +9287,7 @@ var QUERIES7 = {
9102
9287
  dockerfile: "((identifier) @name) @match"
9103
9288
  };
9104
9289
  var regexCheck4 = {
9105
- id: CHECK_ID18,
9290
+ id: CHECK_ID19,
9106
9291
  name: getCopy("checks.hardcoded-passwords.name"),
9107
9292
  description: getCopy("checks.hardcoded-passwords.description"),
9108
9293
  category: "security",
@@ -9127,8 +9312,8 @@ var regexCheck4 = {
9127
9312
  const value = match[2] ?? match[1] ?? "";
9128
9313
  if (match[2] === void 0 && line.toLowerCase().includes('type="password"')) {
9129
9314
  findings.push({
9130
- id: `${CHECK_ID18}:${file.relativePath}:${lineNum + 1}:${(match.index ?? 0) + 1}:${matchIdx}`,
9131
- checkId: CHECK_ID18,
9315
+ id: `${CHECK_ID19}:${file.relativePath}:${lineNum + 1}:${(match.index ?? 0) + 1}:${matchIdx}`,
9316
+ checkId: CHECK_ID19,
9132
9317
  category: "security",
9133
9318
  severity: "critical",
9134
9319
  message: getCopy("checks.hardcoded-passwords.message", "password"),
@@ -9143,8 +9328,8 @@ var regexCheck4 = {
9143
9328
  if (!PASSWORD_NAME_REGEX.test(name)) continue;
9144
9329
  if ((value ?? "").trim().length === 0) continue;
9145
9330
  findings.push({
9146
- id: `${CHECK_ID18}:${file.relativePath}:${lineNum + 1}:${(match.index ?? 0) + 1}:${matchIdx}`,
9147
- checkId: CHECK_ID18,
9331
+ id: `${CHECK_ID19}:${file.relativePath}:${lineNum + 1}:${(match.index ?? 0) + 1}:${matchIdx}`,
9332
+ checkId: CHECK_ID19,
9148
9333
  category: "security",
9149
9334
  severity: "critical",
9150
9335
  message: getCopy("checks.hardcoded-passwords.message", name),
@@ -9162,7 +9347,7 @@ var regexCheck4 = {
9162
9347
  }
9163
9348
  };
9164
9349
  var treeSitterCheck = createTreeSitterCheck({
9165
- id: CHECK_ID18,
9350
+ id: CHECK_ID19,
9166
9351
  nameKey: "checks.hardcoded-passwords.name",
9167
9352
  descriptionKey: "checks.hardcoded-passwords.description",
9168
9353
  category: "security",
@@ -9358,7 +9543,7 @@ function isFullHtmlDocument(content) {
9358
9543
  function hasHeadElement(content) {
9359
9544
  return /<head\b/i.test(content);
9360
9545
  }
9361
- var CHECK_ID19 = "security.html-missing-csp";
9546
+ var CHECK_ID20 = "security.html-missing-csp";
9362
9547
  var REMEDIATION14 = getCopy("remediation.security.html-missing-csp");
9363
9548
  var META_CSP_PATTERN = /<meta\b[^>]*\bhttp-equiv\s*=\s*["']content-security-policy["'][^>]*>/gi;
9364
9549
  var CSP_CONTENT_PATTERN = /\bcontent\s*=\s*(["'])([\s\S]*?)\1/i;
@@ -9366,7 +9551,7 @@ function firstHtmlAnchor(files) {
9366
9551
  return files.filter((file) => file.language === "html" && !isMechanicalCheckExcludedPath(file.relativePath)).map((file) => file.relativePath).sort()[0];
9367
9552
  }
9368
9553
  registry.register({
9369
- id: CHECK_ID19,
9554
+ id: CHECK_ID20,
9370
9555
  name: getCopy("checks.html-missing-csp.name"),
9371
9556
  description: getCopy("checks.html-missing-csp.description"),
9372
9557
  category: "security",
@@ -9396,8 +9581,8 @@ registry.register({
9396
9581
  if (/default-src\s+\*|unsafe-inline|unsafe-eval/i.test(cspValue)) {
9397
9582
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
9398
9583
  findings.push({
9399
- id: `${CHECK_ID19}:${file.relativePath}:${line}:${column}`,
9400
- checkId: CHECK_ID19,
9584
+ id: `${CHECK_ID20}:${file.relativePath}:${line}:${column}`,
9585
+ checkId: CHECK_ID20,
9401
9586
  category: "security",
9402
9587
  severity: "medium",
9403
9588
  message: getCopy("checks.html-missing-csp.message", "weak Content Security Policy"),
@@ -9414,8 +9599,8 @@ registry.register({
9414
9599
  }
9415
9600
  if (findings.length === 0 && sawFullHtmlDoc && !sawValidMetaCsp) {
9416
9601
  findings.push({
9417
- id: `${CHECK_ID19}:${anchor}:1:1`,
9418
- checkId: CHECK_ID19,
9602
+ id: `${CHECK_ID20}:${anchor}:1:1`,
9603
+ checkId: CHECK_ID20,
9419
9604
  category: "security",
9420
9605
  severity: "medium",
9421
9606
  message: getCopy("checks.html-missing-csp.message", "no Content Security Policy found"),
@@ -9429,10 +9614,10 @@ registry.register({
9429
9614
  return findings;
9430
9615
  }
9431
9616
  });
9432
- var CHECK_ID20 = "security.html-static-host-headers";
9617
+ var CHECK_ID21 = "security.html-static-host-headers";
9433
9618
  var REMEDIATION15 = getCopy("remediation.security.html-static-host-headers");
9434
9619
  registry.register({
9435
- id: CHECK_ID20,
9620
+ id: CHECK_ID21,
9436
9621
  name: getCopy("checks.html-static-host-headers.name"),
9437
9622
  description: getCopy("checks.html-static-host-headers.description"),
9438
9623
  category: "security",
@@ -9452,8 +9637,8 @@ registry.register({
9452
9637
  if (!anchor) return [];
9453
9638
  return [
9454
9639
  {
9455
- id: `${CHECK_ID20}:${anchor}:1:1`,
9456
- checkId: CHECK_ID20,
9640
+ id: `${CHECK_ID21}:${anchor}:1:1`,
9641
+ checkId: CHECK_ID21,
9457
9642
  category: "security",
9458
9643
  severity: "medium",
9459
9644
  message: getCopy("checks.html-static-host-headers.message", missing.join(", ")),
@@ -9466,7 +9651,7 @@ registry.register({
9466
9651
  ];
9467
9652
  }
9468
9653
  });
9469
- var CHECK_ID21 = "security.html-inline-unsafe-script";
9654
+ var CHECK_ID22 = "security.html-inline-unsafe-script";
9470
9655
  var REMEDIATION16 = getCopy("remediation.security.html-inline-unsafe-script");
9471
9656
  var SCRIPT_BLOCK_PATTERN2 = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
9472
9657
  var EVENT_HANDLER_PATTERN = /\son[a-z]+\s*=\s*["']([^"']+)["']/gi;
@@ -9523,7 +9708,7 @@ function findEventHandlerMatches(content) {
9523
9708
  return results;
9524
9709
  }
9525
9710
  registry.register({
9526
- id: CHECK_ID21,
9711
+ id: CHECK_ID22,
9527
9712
  name: getCopy("checks.html-inline-unsafe-script.name"),
9528
9713
  description: getCopy("checks.html-inline-unsafe-script.description"),
9529
9714
  category: "security",
@@ -9538,8 +9723,8 @@ registry.register({
9538
9723
  for (const match of findScriptMatches(content)) {
9539
9724
  const { line, column } = lineAndColumn6(content, match.index);
9540
9725
  findings.push({
9541
- id: `${CHECK_ID21}:${file.relativePath}:${line}:${column}`,
9542
- checkId: CHECK_ID21,
9726
+ id: `${CHECK_ID22}:${file.relativePath}:${line}:${column}`,
9727
+ checkId: CHECK_ID22,
9543
9728
  category: "security",
9544
9729
  severity: "high",
9545
9730
  message: getCopy("checks.html-inline-unsafe-script.message", match.text),
@@ -9553,8 +9738,8 @@ registry.register({
9553
9738
  for (const match of findEventHandlerMatches(content)) {
9554
9739
  const { line, column } = lineAndColumn6(content, match.index);
9555
9740
  findings.push({
9556
- id: `${CHECK_ID21}:${file.relativePath}:${line}:${column}:event`,
9557
- checkId: CHECK_ID21,
9741
+ id: `${CHECK_ID22}:${file.relativePath}:${line}:${column}:event`,
9742
+ checkId: CHECK_ID22,
9558
9743
  category: "security",
9559
9744
  severity: "high",
9560
9745
  message: getCopy("checks.html-inline-unsafe-script.message", match.text),
@@ -9569,10 +9754,10 @@ registry.register({
9569
9754
  return findings;
9570
9755
  }
9571
9756
  });
9572
- var CHECK_ID22 = "security.html-referrer-leak";
9757
+ var CHECK_ID23 = "security.html-referrer-leak";
9573
9758
  var REMEDIATION17 = getCopy("remediation.security.html-referrer-leak");
9574
9759
  registry.register({
9575
- id: CHECK_ID22,
9760
+ id: CHECK_ID23,
9576
9761
  name: getCopy("checks.html-referrer-leak.name"),
9577
9762
  description: getCopy("checks.html-referrer-leak.description"),
9578
9763
  category: "security",
@@ -9588,8 +9773,8 @@ registry.register({
9588
9773
  if (target.hasRel) continue;
9589
9774
  const { line, column } = lineAndColumn5(content, target.index);
9590
9775
  findings.push({
9591
- id: `${CHECK_ID22}:${file.relativePath}:${line}:${column}`,
9592
- checkId: CHECK_ID22,
9776
+ id: `${CHECK_ID23}:${file.relativePath}:${line}:${column}`,
9777
+ checkId: CHECK_ID23,
9593
9778
  category: "security",
9594
9779
  severity: "medium",
9595
9780
  message: getCopy("checks.html-referrer-leak.message", target.text),
@@ -9604,7 +9789,7 @@ registry.register({
9604
9789
  return findings;
9605
9790
  }
9606
9791
  });
9607
- var CHECK_ID23 = "security.html-css-comments-secrets";
9792
+ var CHECK_ID24 = "security.html-css-comments-secrets";
9608
9793
  var REMEDIATION18 = getCopy("remediation.security.html-css-comments-secrets");
9609
9794
  var SECRET_MARKER = /(?:api[_-]?key|firebase[_-]?config|google[_-]?api|token|secret|password|private[_-]?key|access[_-]?key)/i;
9610
9795
  var SECRET_VALUE = /(?:[:=]\s*|["'`])([A-Za-z0-9+/=_-]{8,}|sk_[A-Za-z0-9/+=_-]{8,})/i;
@@ -9619,8 +9804,8 @@ function scanPattern(content, filePath, checkPrefix, pattern, messageText, findi
9619
9804
  const value = valueMatch?.[1] ?? text22;
9620
9805
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
9621
9806
  findings.push({
9622
- id: `${CHECK_ID23}:${filePath}:${line}:${column}:${checkPrefix}`,
9623
- checkId: CHECK_ID23,
9807
+ id: `${CHECK_ID24}:${filePath}:${line}:${column}:${checkPrefix}`,
9808
+ checkId: CHECK_ID24,
9624
9809
  category: "security",
9625
9810
  severity: "critical",
9626
9811
  message: getCopy("checks.html-css-comments-secrets.message", messageText),
@@ -9633,7 +9818,7 @@ function scanPattern(content, filePath, checkPrefix, pattern, messageText, findi
9633
9818
  }
9634
9819
  }
9635
9820
  registry.register({
9636
- id: CHECK_ID23,
9821
+ id: CHECK_ID24,
9637
9822
  name: getCopy("checks.html-css-comments-secrets.name"),
9638
9823
  description: getCopy("checks.html-css-comments-secrets.description"),
9639
9824
  category: "security",
@@ -9652,7 +9837,7 @@ registry.register({
9652
9837
  return findings;
9653
9838
  }
9654
9839
  });
9655
- var CHECK_ID24 = "security.css-insecure-import";
9840
+ var CHECK_ID25 = "security.css-insecure-import";
9656
9841
  var REMEDIATION19 = getCopy("remediation.security.css-insecure-import");
9657
9842
  var HTTP_URL_PATTERN = /^http:\/\//i;
9658
9843
  var IMPORT_PATTERN = /@import\s+(?:url\(\s*)?(["']?)([^"')\s;]+)\1\s*\)?\s*;/gi;
@@ -9665,8 +9850,8 @@ function lineText(content, index) {
9665
9850
  function pushFinding3(findings, filePath, content, matchText, index) {
9666
9851
  const { line, column } = lineAndColumn5(content, index);
9667
9852
  findings.push({
9668
- id: `${CHECK_ID24}:${filePath}:${line}:${column}`,
9669
- checkId: CHECK_ID24,
9853
+ id: `${CHECK_ID25}:${filePath}:${line}:${column}`,
9854
+ checkId: CHECK_ID25,
9670
9855
  category: "security",
9671
9856
  severity: "medium",
9672
9857
  message: getCopy("checks.css-insecure-import.message", matchText),
@@ -9678,7 +9863,7 @@ function pushFinding3(findings, filePath, content, matchText, index) {
9678
9863
  });
9679
9864
  }
9680
9865
  registry.register({
9681
- id: CHECK_ID24,
9866
+ id: CHECK_ID25,
9682
9867
  name: getCopy("checks.css-insecure-import.name"),
9683
9868
  description: getCopy("checks.css-insecure-import.description"),
9684
9869
  category: "security",
@@ -9705,7 +9890,7 @@ registry.register({
9705
9890
  return findings;
9706
9891
  }
9707
9892
  });
9708
- var CHECK_ID25 = "security.html-mixed-content";
9893
+ var CHECK_ID26 = "security.html-mixed-content";
9709
9894
  var REMEDIATION20 = getCopy("remediation.security.html-mixed-content");
9710
9895
  var TAG_RESOURCE_PATTERN = /<(script|link|iframe|img|form|object|embed)\b[^>]*?\b(src|href|action|data)\s*=\s*["'](http:\/\/[^"']+)["'][^>]*?>/gi;
9711
9896
  var STYLE_ATTR_PATTERN = /\bstyle\s*=\s*["']([\s\S]*?http:\/\/[\s\S]*?)["']/gi;
@@ -9714,8 +9899,8 @@ var CSS_URL_PATTERN = /url\(\s*(["']?)(http:\/\/[^"')]+)\1\s*\)/gi;
9714
9899
  function pushFinding4(findings, filePath, content, index, matchText) {
9715
9900
  const { line, column } = lineAndColumn5(content, index);
9716
9901
  findings.push({
9717
- id: `${CHECK_ID25}:${filePath}:${line}:${column}`,
9718
- checkId: CHECK_ID25,
9902
+ id: `${CHECK_ID26}:${filePath}:${line}:${column}`,
9903
+ checkId: CHECK_ID26,
9719
9904
  category: "security",
9720
9905
  severity: "medium",
9721
9906
  message: getCopy("checks.html-mixed-content.message", matchText),
@@ -9727,7 +9912,7 @@ function pushFinding4(findings, filePath, content, index, matchText) {
9727
9912
  });
9728
9913
  }
9729
9914
  registry.register({
9730
- id: CHECK_ID25,
9915
+ id: CHECK_ID26,
9731
9916
  name: getCopy("checks.html-mixed-content.name"),
9732
9917
  description: getCopy("checks.html-mixed-content.description"),
9733
9918
  category: "security",
@@ -9765,7 +9950,7 @@ registry.register({
9765
9950
  return findings;
9766
9951
  }
9767
9952
  });
9768
- var CHECK_ID26 = "security.html-external-asset-integrity";
9953
+ var CHECK_ID27 = "security.html-external-asset-integrity";
9769
9954
  var REMEDIATION21 = getCopy("remediation.security.html-external-asset-integrity");
9770
9955
  var SCRIPT_PATTERN = /<script\b[^>]*?\bsrc\s*=\s*["'](https:\/\/[^"']+)["'][^>]*?>/gi;
9771
9956
  var STYLESHEET_PATTERN = /<link\b(?=[^>]*\brel\s*=\s*["'][^"']*\bstylesheet\b[^"']*["'])[^>]*?\bhref\s*=\s*["'](https:\/\/[^"']+)["'][^>]*?>/gi;
@@ -9780,7 +9965,7 @@ function isLocalDevelopmentUrl(rawUrl) {
9780
9965
  }
9781
9966
  }
9782
9967
  registry.register({
9783
- id: CHECK_ID26,
9968
+ id: CHECK_ID27,
9784
9969
  name: getCopy("checks.html-external-asset-integrity.name"),
9785
9970
  description: getCopy("checks.html-external-asset-integrity.description"),
9786
9971
  category: "security",
@@ -9801,8 +9986,8 @@ registry.register({
9801
9986
  if (hasIntegrity && hasCrossorigin) continue;
9802
9987
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
9803
9988
  findings.push({
9804
- id: `${CHECK_ID26}:${file.relativePath}:${line}:${column}`,
9805
- checkId: CHECK_ID26,
9989
+ id: `${CHECK_ID27}:${file.relativePath}:${line}:${column}`,
9990
+ checkId: CHECK_ID27,
9806
9991
  category: "security",
9807
9992
  severity: "medium",
9808
9993
  message: getCopy("checks.html-external-asset-integrity.message", tagText),
@@ -9822,8 +10007,8 @@ registry.register({
9822
10007
  if (hasIntegrity && hasCrossorigin) continue;
9823
10008
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
9824
10009
  findings.push({
9825
- id: `${CHECK_ID26}:${file.relativePath}:${line}:${column}`,
9826
- checkId: CHECK_ID26,
10010
+ id: `${CHECK_ID27}:${file.relativePath}:${line}:${column}`,
10011
+ checkId: CHECK_ID27,
9827
10012
  category: "security",
9828
10013
  severity: "medium",
9829
10014
  message: getCopy("checks.html-external-asset-integrity.message", tagText),
@@ -9838,7 +10023,7 @@ registry.register({
9838
10023
  return findings;
9839
10024
  }
9840
10025
  });
9841
- var CHECK_ID27 = "security.html-insecure-form-action";
10026
+ var CHECK_ID28 = "security.html-insecure-form-action";
9842
10027
  var REMEDIATION22 = getCopy("remediation.security.html-insecure-form-action");
9843
10028
  var FORM_ACTION_PATTERN = /<form\b[^>]*?\baction\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*?>/gi;
9844
10029
  var LOCAL_DEV_URL_PATTERN = /^(?:https?:)?\/\/(?:localhost|127\.0\.0\.1|\[::1\]|::1)(?::\d+)?(?:\/|$)/i;
@@ -9852,7 +10037,7 @@ function lineAndColumn7(content, index) {
9852
10037
  };
9853
10038
  }
9854
10039
  registry.register({
9855
- id: CHECK_ID27,
10040
+ id: CHECK_ID28,
9856
10041
  name: getCopy("checks.html-insecure-form-action.name"),
9857
10042
  description: getCopy("checks.html-insecure-form-action.description"),
9858
10043
  category: "security",
@@ -9870,8 +10055,8 @@ registry.register({
9870
10055
  if (LOCAL_DEV_URL_PATTERN.test(action)) continue;
9871
10056
  const { line, column } = lineAndColumn7(content, match.index ?? 0);
9872
10057
  findings.push({
9873
- id: `${CHECK_ID27}:${file.relativePath}:${line}:${column}`,
9874
- checkId: CHECK_ID27,
10058
+ id: `${CHECK_ID28}:${file.relativePath}:${line}:${column}`,
10059
+ checkId: CHECK_ID28,
9875
10060
  category: "security",
9876
10061
  severity: "medium",
9877
10062
  message: getCopy("checks.html-insecure-form-action.message", match[0] ?? action),
@@ -9886,7 +10071,7 @@ registry.register({
9886
10071
  return findings;
9887
10072
  }
9888
10073
  });
9889
- var CHECK_ID28 = "security.html-iframe-sandbox";
10074
+ var CHECK_ID29 = "security.html-iframe-sandbox";
9890
10075
  var REMEDIATION23 = getCopy("remediation.security.html-iframe-sandbox");
9891
10076
  var IFRAME_PATTERN = /<iframe\b[^>]*?>/gi;
9892
10077
  var SRC_PATTERN = /\bsrc\s*=\s*["']([^"']+)["']/i;
@@ -9901,7 +10086,7 @@ function hasSandboxAttribute(tagText) {
9901
10086
  return { present: true, value: match[1] ?? match[2] ?? match[3] ?? "" };
9902
10087
  }
9903
10088
  registry.register({
9904
- id: CHECK_ID28,
10089
+ id: CHECK_ID29,
9905
10090
  name: getCopy("checks.html-iframe-sandbox.name"),
9906
10091
  description: getCopy("checks.html-iframe-sandbox.description"),
9907
10092
  category: "security",
@@ -9924,8 +10109,8 @@ registry.register({
9924
10109
  if (sandbox.present && !hasBroadEscape) continue;
9925
10110
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
9926
10111
  findings.push({
9927
- id: `${CHECK_ID28}:${file.relativePath}:${line}:${column}`,
9928
- checkId: CHECK_ID28,
10112
+ id: `${CHECK_ID29}:${file.relativePath}:${line}:${column}`,
10113
+ checkId: CHECK_ID29,
9929
10114
  category: "security",
9930
10115
  severity: "medium",
9931
10116
  message: getCopy("checks.html-iframe-sandbox.message", tagText),
@@ -9940,7 +10125,7 @@ registry.register({
9940
10125
  return findings;
9941
10126
  }
9942
10127
  });
9943
- var CHECK_ID29 = "security.html-unsafe-embed";
10128
+ var CHECK_ID30 = "security.html-unsafe-embed";
9944
10129
  var REMEDIATION24 = getCopy("remediation.security.html-unsafe-embed");
9945
10130
  var OBJECT_PATTERN = /<object\b[^>]*?\bdata\s*=\s*["'](https:\/\/[^"']+)["'][^>]*?>/gi;
9946
10131
  var EMBED_PATTERN = /<embed\b[^>]*?\bsrc\s*=\s*["'](https:\/\/[^"']+)["'][^>]*?>/gi;
@@ -9961,8 +10146,8 @@ function getTypeValue(tagText) {
9961
10146
  function pushFindings(findings, filePath, content, index, tagText) {
9962
10147
  const { line, column } = lineAndColumn8(content, index);
9963
10148
  findings.push({
9964
- id: `${CHECK_ID29}:${filePath}:${line}:${column}`,
9965
- checkId: CHECK_ID29,
10149
+ id: `${CHECK_ID30}:${filePath}:${line}:${column}`,
10150
+ checkId: CHECK_ID30,
9966
10151
  category: "security",
9967
10152
  severity: "medium",
9968
10153
  message: getCopy("checks.html-unsafe-embed.message", tagText),
@@ -9974,7 +10159,7 @@ function pushFindings(findings, filePath, content, index, tagText) {
9974
10159
  });
9975
10160
  }
9976
10161
  registry.register({
9977
- id: CHECK_ID29,
10162
+ id: CHECK_ID30,
9978
10163
  name: getCopy("checks.html-unsafe-embed.name"),
9979
10164
  description: getCopy("checks.html-unsafe-embed.description"),
9980
10165
  category: "security",
@@ -10006,7 +10191,7 @@ registry.register({
10006
10191
  return findings;
10007
10192
  }
10008
10193
  });
10009
- var CHECK_ID30 = "security.html-meta-redirect";
10194
+ var CHECK_ID31 = "security.html-meta-redirect";
10010
10195
  var REMEDIATION25 = getCopy("remediation.security.html-meta-redirect");
10011
10196
  var META_REFRESH_PATTERN = /<meta\b[^>]*\bhttp-equiv\s*=\s*["']refresh["'][^>]*>/gi;
10012
10197
  var CONTENT_PATTERN = /\bcontent\s*=\s*["']([^"']+)["']/i;
@@ -10023,7 +10208,7 @@ function parseRefreshContent(value) {
10023
10208
  };
10024
10209
  }
10025
10210
  registry.register({
10026
- id: CHECK_ID30,
10211
+ id: CHECK_ID31,
10027
10212
  name: getCopy("checks.html-meta-redirect.name"),
10028
10213
  description: getCopy("checks.html-meta-redirect.description"),
10029
10214
  category: "security",
@@ -10046,8 +10231,8 @@ registry.register({
10046
10231
  if (LOCAL_DEV_URL_PATTERN4.test(parsed.url)) continue;
10047
10232
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
10048
10233
  findings.push({
10049
- id: `${CHECK_ID30}:${file.relativePath}:${line}:${column}`,
10050
- checkId: CHECK_ID30,
10234
+ id: `${CHECK_ID31}:${file.relativePath}:${line}:${column}`,
10235
+ checkId: CHECK_ID31,
10051
10236
  category: "security",
10052
10237
  severity: "medium",
10053
10238
  message: getCopy("checks.html-meta-redirect.message", tagText),
@@ -10062,7 +10247,7 @@ registry.register({
10062
10247
  return findings;
10063
10248
  }
10064
10249
  });
10065
- var CHECK_ID31 = "security.html-base-tag-hijack";
10250
+ var CHECK_ID32 = "security.html-base-tag-hijack";
10066
10251
  var REMEDIATION26 = getCopy("remediation.security.html-base-tag-hijack");
10067
10252
  var BASE_PATTERN = /<base\b[^>]*?>/gi;
10068
10253
  var HREF_PATTERN = /\bhref\s*=\s*["']([^"']+)["']/i;
@@ -10078,7 +10263,7 @@ function isOutsideHead(content, index) {
10078
10263
  return false;
10079
10264
  }
10080
10265
  registry.register({
10081
- id: CHECK_ID31,
10266
+ id: CHECK_ID32,
10082
10267
  name: getCopy("checks.html-base-tag-hijack.name"),
10083
10268
  description: getCopy("checks.html-base-tag-hijack.description"),
10084
10269
  category: "security",
@@ -10100,8 +10285,8 @@ registry.register({
10100
10285
  if (!externalHref && !targetBlank && !outsideHead) continue;
10101
10286
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
10102
10287
  findings.push({
10103
- id: `${CHECK_ID31}:${file.relativePath}:${line}:${column}`,
10104
- checkId: CHECK_ID31,
10288
+ id: `${CHECK_ID32}:${file.relativePath}:${line}:${column}`,
10289
+ checkId: CHECK_ID32,
10105
10290
  category: "security",
10106
10291
  severity: "medium",
10107
10292
  message: getCopy("checks.html-base-tag-hijack.message", tagText),
@@ -10116,12 +10301,12 @@ registry.register({
10116
10301
  return findings;
10117
10302
  }
10118
10303
  });
10119
- var CHECK_ID32 = "security.html-javascript-uri";
10304
+ var CHECK_ID33 = "security.html-javascript-uri";
10120
10305
  var REMEDIATION27 = getCopy("remediation.security.html-javascript-uri");
10121
10306
  var TAG_PATTERN = /<(a|form|iframe|object|embed)\b[^>]*?\b(href|action|src|data)\s*=\s*["'](javascript:[^"']+)["'][^>]*?>/gi;
10122
10307
  var VOID_PATTERN = /^javascript:\s*(?:void\s*\(?\s*0\s*\)?)(?:\s*;)?\s*$/i;
10123
10308
  registry.register({
10124
- id: CHECK_ID32,
10309
+ id: CHECK_ID33,
10125
10310
  name: getCopy("checks.html-javascript-uri.name"),
10126
10311
  description: getCopy("checks.html-javascript-uri.description"),
10127
10312
  category: "security",
@@ -10139,8 +10324,8 @@ registry.register({
10139
10324
  if (VOID_PATTERN.test(jsUrl)) continue;
10140
10325
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
10141
10326
  findings.push({
10142
- id: `${CHECK_ID32}:${file.relativePath}:${line}:${column}`,
10143
- checkId: CHECK_ID32,
10327
+ id: `${CHECK_ID33}:${file.relativePath}:${line}:${column}`,
10328
+ checkId: CHECK_ID33,
10144
10329
  category: "security",
10145
10330
  severity: "low",
10146
10331
  message: getCopy("checks.html-javascript-uri.message", tagText),
@@ -10155,7 +10340,7 @@ registry.register({
10155
10340
  return findings;
10156
10341
  }
10157
10342
  });
10158
- var CHECK_ID33 = "security.html-import-map-integrity";
10343
+ var CHECK_ID34 = "security.html-import-map-integrity";
10159
10344
  var REMEDIATION28 = getCopy("remediation.security.html-import-map-integrity");
10160
10345
  var IMPORTMAP_SCRIPT_PATTERN = /<script\b[^>]*\btype\s*=\s*["']importmap["'][^>]*>([\s\S]*?)<\/script>/gi;
10161
10346
  var IMPORTMAP_SRC_PATTERN = /<script\b[^>]*\btype\s*=\s*["']importmap["'][^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/gi;
@@ -10176,8 +10361,8 @@ function isExternalUrl(value) {
10176
10361
  function pushFinding5(findings, filePath, content, index, matchText) {
10177
10362
  const { line, column } = lineAndColumn9(content, index);
10178
10363
  findings.push({
10179
- id: `${CHECK_ID33}:${filePath}:${line}:${column}`,
10180
- checkId: CHECK_ID33,
10364
+ id: `${CHECK_ID34}:${filePath}:${line}:${column}`,
10365
+ checkId: CHECK_ID34,
10181
10366
  category: "security",
10182
10367
  severity: "medium",
10183
10368
  message: getCopy("checks.html-import-map-integrity.message", matchText),
@@ -10202,7 +10387,7 @@ function scanImportMapValue(findings, filePath, content, value, contextLabel, in
10202
10387
  }
10203
10388
  }
10204
10389
  registry.register({
10205
- id: CHECK_ID33,
10390
+ id: CHECK_ID34,
10206
10391
  name: getCopy("checks.html-import-map-integrity.name"),
10207
10392
  description: getCopy("checks.html-import-map-integrity.description"),
10208
10393
  category: "security",
@@ -10235,7 +10420,7 @@ registry.register({
10235
10420
  return findings;
10236
10421
  }
10237
10422
  });
10238
- var CHECK_ID34 = "security.css-data-exfiltration";
10423
+ var CHECK_ID35 = "security.css-data-exfiltration";
10239
10424
  var REMEDIATION29 = getCopy("remediation.security.css-data-exfiltration");
10240
10425
  var ATTR_SELECTOR_PATTERN = /\[[^\]]*(?:value|type|name|email|tel|address|cc-number|cc-exp)[^\]]*(?:\^=|\$=|\*=|~=|\|=)[^\]]*\]/i;
10241
10426
  var EXTERNAL_HTTP_URL_PATTERN = /url\(\s*(["']?)http:\/\/[^"')]+\1\s*\)/i;
@@ -10253,8 +10438,8 @@ function lineAndColumn10(content, index) {
10253
10438
  function pushFinding6(findings, filePath, content, index, matchText) {
10254
10439
  const { line, column } = lineAndColumn10(content, index);
10255
10440
  findings.push({
10256
- id: `${CHECK_ID34}:${filePath}:${line}:${column}`,
10257
- checkId: CHECK_ID34,
10441
+ id: `${CHECK_ID35}:${filePath}:${line}:${column}`,
10442
+ checkId: CHECK_ID35,
10258
10443
  category: "security",
10259
10444
  severity: "medium",
10260
10445
  message: getCopy("checks.css-data-exfiltration.message", matchText),
@@ -10275,7 +10460,7 @@ function scanCss(findings, filePath, scanContent, originalContent, offset = 0) {
10275
10460
  }
10276
10461
  }
10277
10462
  registry.register({
10278
- id: CHECK_ID34,
10463
+ id: CHECK_ID35,
10279
10464
  name: getCopy("checks.css-data-exfiltration.name"),
10280
10465
  description: getCopy("checks.css-data-exfiltration.description"),
10281
10466
  category: "security",
@@ -10307,7 +10492,7 @@ registry.register({
10307
10492
  return findings;
10308
10493
  }
10309
10494
  });
10310
- var CHECK_ID35 = "security.css-external-font-integrity";
10495
+ var CHECK_ID36 = "security.css-external-font-integrity";
10311
10496
  var REMEDIATION30 = getCopy("remediation.security.css-external-font-integrity");
10312
10497
  var FONT_FACE_PATTERN = /@font-face\b[\s\S]*?\}/gi;
10313
10498
  var URL_PATTERN = /url\(\s*(["']?)([^"')]+)\1\s*\)/gi;
@@ -10339,7 +10524,7 @@ function isPublicCdnFont(url) {
10339
10524
  }
10340
10525
  }
10341
10526
  registry.register({
10342
- id: CHECK_ID35,
10527
+ id: CHECK_ID36,
10343
10528
  name: getCopy("checks.css-external-font-integrity.name"),
10344
10529
  description: getCopy("checks.css-external-font-integrity.description"),
10345
10530
  category: "security",
@@ -10360,8 +10545,8 @@ registry.register({
10360
10545
  if (!isPublicCdnFont(url)) continue;
10361
10546
  const { line, column } = lineAndColumn11(content, face.index ?? 0);
10362
10547
  findings.push({
10363
- id: `${CHECK_ID35}:${file.relativePath}:${line}:${column}`,
10364
- checkId: CHECK_ID35,
10548
+ id: `${CHECK_ID36}:${file.relativePath}:${line}:${column}`,
10549
+ checkId: CHECK_ID36,
10365
10550
  category: "security",
10366
10551
  severity: "medium",
10367
10552
  message: getCopy("checks.css-external-font-integrity.message", match[0] ?? url),
@@ -10377,7 +10562,7 @@ registry.register({
10377
10562
  return findings;
10378
10563
  }
10379
10564
  });
10380
- var CHECK_ID36 = "security.insecure-random";
10565
+ var CHECK_ID37 = "security.insecure-random";
10381
10566
  var QUERIES8 = {
10382
10567
  javascript: `
10383
10568
  (call_expression
@@ -10446,7 +10631,7 @@ var QUERIES8 = {
10446
10631
  };
10447
10632
  registry.register(
10448
10633
  createTreeSitterCheck({
10449
- id: CHECK_ID36,
10634
+ id: CHECK_ID37,
10450
10635
  nameKey: "checks.insecure-random.name",
10451
10636
  descriptionKey: "checks.insecure-random.description",
10452
10637
  category: "security",
@@ -10458,7 +10643,7 @@ registry.register(
10458
10643
  remediationKey: "remediation.security.insecure-random"
10459
10644
  })
10460
10645
  );
10461
- var CHECK_ID37 = "security.weak-key-entropy";
10646
+ var CHECK_ID38 = "security.weak-key-entropy";
10462
10647
  var REMEDIATION31 = getCopy("remediation.security.weak-key-entropy");
10463
10648
  var RANDOM_BYTES_PATTERN = /randomBytes\s*\(\s*(\d+)\s*\)/g;
10464
10649
  var WEAK_KEY_CONTEXT = /(?:key|token|secret|id|license|password|session|csrf|nonce|salt|iv)/i;
@@ -10471,7 +10656,7 @@ function lineAndColumn12(content, index) {
10471
10656
  };
10472
10657
  }
10473
10658
  registry.register({
10474
- id: CHECK_ID37,
10659
+ id: CHECK_ID38,
10475
10660
  name: getCopy("checks.weak-key-entropy.name"),
10476
10661
  description: getCopy("checks.weak-key-entropy.description"),
10477
10662
  category: "security",
@@ -10493,8 +10678,8 @@ registry.register({
10493
10678
  if (!WEAK_KEY_CONTEXT.test(lineContent) && !WEAK_KEY_CONTEXT.test(content.slice(Math.max(0, matchIndex - 200), matchIndex))) continue;
10494
10679
  const { line, column } = lineAndColumn12(content, matchIndex);
10495
10680
  findings.push({
10496
- id: `${CHECK_ID37}:${file.relativePath}:${line}:${column}`,
10497
- checkId: CHECK_ID37,
10681
+ id: `${CHECK_ID38}:${file.relativePath}:${line}:${column}`,
10682
+ checkId: CHECK_ID38,
10498
10683
  category: "security",
10499
10684
  severity: "high",
10500
10685
  message: getCopy("checks.weak-key-entropy.message", match[0], String(byteCount)),
@@ -10509,7 +10694,7 @@ registry.register({
10509
10694
  return findings;
10510
10695
  }
10511
10696
  });
10512
- var CHECK_ID38 = "security.insecure-api-url-default";
10697
+ var CHECK_ID39 = "security.insecure-api-url-default";
10513
10698
  var REMEDIATION32 = getCopy("remediation.security.insecure-api-url-default");
10514
10699
  var HTTP_URL_DEFAULT_PATTERN = /(?:['"`])(https?:\/\/[^\s'"`]+)(?:['"`])/g;
10515
10700
  var API_CONTEXT = /(?:api[_-]?url|base[_-]?url|endpoint|server|host|webhook|resend|paddle|stripe|sendgrid|mailgun|postmark)/i;
@@ -10524,7 +10709,7 @@ function lineAndColumn13(content, index) {
10524
10709
  };
10525
10710
  }
10526
10711
  registry.register({
10527
- id: CHECK_ID38,
10712
+ id: CHECK_ID39,
10528
10713
  name: getCopy("checks.insecure-api-url-default.name"),
10529
10714
  description: getCopy("checks.insecure-api-url-default.description"),
10530
10715
  category: "security",
@@ -10549,8 +10734,8 @@ registry.register({
10549
10734
  if (!isEnvDefault && !API_CONTEXT.test(matchLine)) continue;
10550
10735
  const { line, column } = lineAndColumn13(content, matchIndex);
10551
10736
  findings.push({
10552
- id: `${CHECK_ID38}:${file.relativePath}:${line}:${column}`,
10553
- checkId: CHECK_ID38,
10737
+ id: `${CHECK_ID39}:${file.relativePath}:${line}:${column}`,
10738
+ checkId: CHECK_ID39,
10554
10739
  category: "security",
10555
10740
  severity: "high",
10556
10741
  message: getCopy("checks.insecure-api-url-default.message", url),
@@ -10565,7 +10750,7 @@ registry.register({
10565
10750
  return findings;
10566
10751
  }
10567
10752
  });
10568
- var CHECK_ID39 = "security.dynamic-sql-columns";
10753
+ var CHECK_ID40 = "security.dynamic-sql-columns";
10569
10754
  var REMEDIATION33 = getCopy("remediation.security.dynamic-sql-columns");
10570
10755
  var SQL_SET_INTERPOLATION = /\bSET\s+.*\$\{[^}]*\}/gis;
10571
10756
  var SQL_SET_TEMPLATE = /\bSET\s+.*`[^`]*\$\{[^}]*\}[^`]*`/gis;
@@ -10579,7 +10764,7 @@ function lineAndColumn14(content, index) {
10579
10764
  };
10580
10765
  }
10581
10766
  registry.register({
10582
- id: CHECK_ID39,
10767
+ id: CHECK_ID40,
10583
10768
  name: getCopy("checks.dynamic-sql-columns.name"),
10584
10769
  description: getCopy("checks.dynamic-sql-columns.description"),
10585
10770
  category: "security",
@@ -10600,11 +10785,11 @@ registry.register({
10600
10785
  for (const match of content.matchAll(regex)) {
10601
10786
  const matchIndex = match.index ?? 0;
10602
10787
  const { line, column } = lineAndColumn14(content, matchIndex);
10603
- const dedupKey = `${CHECK_ID39}:${file.relativePath}:${line}`;
10788
+ const dedupKey = `${CHECK_ID40}:${file.relativePath}:${line}`;
10604
10789
  if (findings.some((f) => f.id === dedupKey)) continue;
10605
10790
  findings.push({
10606
10791
  id: dedupKey,
10607
- checkId: CHECK_ID39,
10792
+ checkId: CHECK_ID40,
10608
10793
  category: "security",
10609
10794
  severity: "medium",
10610
10795
  message: getCopy("checks.dynamic-sql-columns.message", label),
@@ -10620,7 +10805,7 @@ registry.register({
10620
10805
  return findings;
10621
10806
  }
10622
10807
  });
10623
- var CHECK_ID40 = "security.weak-email-validation";
10808
+ var CHECK_ID41 = "security.weak-email-validation";
10624
10809
  var REMEDIATION34 = getCopy("remediation.security.weak-email-validation");
10625
10810
  var WEAK_EMAIL_REGEX_LINE = /\/\^[^/]*@[^/]*\\\.[^/]{0,4}\$\/[gimsuy]*/g;
10626
10811
  var LOOSE_REGEX_CLASS = /\[\^?\\?s@\]\+@\[\^?\\?s@\]\+\\\.\[\^?\\?s@\]\+/g;
@@ -10634,7 +10819,7 @@ function lineAndColumn15(content, index) {
10634
10819
  };
10635
10820
  }
10636
10821
  registry.register({
10637
- id: CHECK_ID40,
10822
+ id: CHECK_ID41,
10638
10823
  name: getCopy("checks.weak-email-validation.name"),
10639
10824
  description: getCopy("checks.weak-email-validation.description"),
10640
10825
  category: "security",
@@ -10655,11 +10840,11 @@ registry.register({
10655
10840
  for (const match of content.matchAll(regex)) {
10656
10841
  const matchIndex = match.index ?? 0;
10657
10842
  const { line, column } = lineAndColumn15(content, matchIndex);
10658
- const dedupKey = `${CHECK_ID40}:${file.relativePath}:${line}`;
10843
+ const dedupKey = `${CHECK_ID41}:${file.relativePath}:${line}`;
10659
10844
  if (findings.some((f) => f.id === dedupKey)) continue;
10660
10845
  findings.push({
10661
10846
  id: dedupKey,
10662
- checkId: CHECK_ID40,
10847
+ checkId: CHECK_ID41,
10663
10848
  category: "security",
10664
10849
  severity: "medium",
10665
10850
  message: getCopy("checks.weak-email-validation.message", `${match[0]} (${label})`),
@@ -10675,7 +10860,7 @@ registry.register({
10675
10860
  return findings;
10676
10861
  }
10677
10862
  });
10678
- var CHECK_ID41 = "security.idempotency-key-leak";
10863
+ var CHECK_ID42 = "security.idempotency-key-leak";
10679
10864
  var REMEDIATION35 = getCopy("remediation.security.idempotency-key-leak");
10680
10865
  var IDEMPOTENCY_CONTEXT = /idempotency[_-]?key|idempotency[_-]?token/i;
10681
10866
  var TEMPLATE_LITERAL_INTERPOLATION = new RegExp("`[^`]*\\$\\{[^}]+\\}[^`]*`", "g");
@@ -10689,7 +10874,7 @@ function lineAndColumn16(content, index) {
10689
10874
  };
10690
10875
  }
10691
10876
  registry.register({
10692
- id: CHECK_ID41,
10877
+ id: CHECK_ID42,
10693
10878
  name: getCopy("checks.idempotency-key-leak.name"),
10694
10879
  description: getCopy("checks.idempotency-key-leak.description"),
10695
10880
  category: "security",
@@ -10708,11 +10893,11 @@ registry.register({
10708
10893
  if (!STRUCTURED_ID_PATTERN.test(templateStr)) continue;
10709
10894
  const matchIndex = match.index ?? 0;
10710
10895
  const { line, column } = lineAndColumn16(content, matchIndex);
10711
- const dedupKey = `${CHECK_ID41}:${file.relativePath}:${line}`;
10896
+ const dedupKey = `${CHECK_ID42}:${file.relativePath}:${line}`;
10712
10897
  if (findings.some((f) => f.id === dedupKey)) continue;
10713
10898
  findings.push({
10714
10899
  id: dedupKey,
10715
- checkId: CHECK_ID41,
10900
+ checkId: CHECK_ID42,
10716
10901
  category: "security",
10717
10902
  severity: "medium",
10718
10903
  message: getCopy("checks.idempotency-key-leak.message", templateStr.slice(0, 80)),
@@ -10727,7 +10912,7 @@ registry.register({
10727
10912
  return findings;
10728
10913
  }
10729
10914
  });
10730
- var CHECK_ID42 = "data-exposure.plaintext-secret-in-email";
10915
+ var CHECK_ID43 = "data-exposure.plaintext-secret-in-email";
10731
10916
  var REMEDIATION36 = getCopy("remediation.data-exposure.plaintext-secret-in-email");
10732
10917
  var EMAIL_FILE_PATTERN = /email|mail|notify|notification/i;
10733
10918
  var SECRET_IN_TEXT_PATTERN = /(?:license[_-]?key|api[_-]?key|secret[_-]?key|token|password|auth[_-]?code|verification[_-]?code)\b/i;
@@ -10741,7 +10926,7 @@ function lineAndColumn17(content, index) {
10741
10926
  };
10742
10927
  }
10743
10928
  registry.register({
10744
- id: CHECK_ID42,
10929
+ id: CHECK_ID43,
10745
10930
  name: getCopy("checks.plaintext-secret-in-email.name"),
10746
10931
  description: getCopy("checks.plaintext-secret-in-email.description"),
10747
10932
  category: "data-exposure",
@@ -10763,8 +10948,8 @@ registry.register({
10763
10948
  const { line: lineNum, column } = lineAndColumn17(content, lineIndex);
10764
10949
  const secretMatch = SECRET_IN_TEXT_PATTERN.exec(lineText2);
10765
10950
  findings.push({
10766
- id: `${CHECK_ID42}:${file.relativePath}:${lineNum}:${column}`,
10767
- checkId: CHECK_ID42,
10951
+ id: `${CHECK_ID43}:${file.relativePath}:${lineNum}:${column}`,
10952
+ checkId: CHECK_ID43,
10768
10953
  category: "data-exposure",
10769
10954
  severity: "medium",
10770
10955
  message: getCopy("checks.plaintext-secret-in-email.message", secretMatch?.[0] ?? "secret"),
@@ -12974,7 +13159,7 @@ registry.register(
12974
13159
  attribution: "CWE-94"
12975
13160
  })
12976
13161
  );
12977
- var CHECK_ID43 = "security.missing-csrf-protection";
13162
+ var CHECK_ID44 = "security.missing-csrf-protection";
12978
13163
  var CSRF_MARKERS = /\bcsrf\b|xsrf|csrfmiddlewaretoken|double[\s-]submit[\s-]cookie|csurf\b|cookie-parser.*csrf|protect_from_forgery|verify_authenticity_token/i;
12979
13164
  var QUERIES9 = {
12980
13165
  php: `
@@ -13031,7 +13216,7 @@ async function hasProjectCsrfProtection(files) {
13031
13216
  return false;
13032
13217
  }
13033
13218
  var check = {
13034
- id: CHECK_ID43,
13219
+ id: CHECK_ID44,
13035
13220
  name: resolveCopy3("checks.missing-csrf-protection.name"),
13036
13221
  description: resolveCopy3("checks.missing-csrf-protection.description"),
13037
13222
  category: "security",
@@ -13071,8 +13256,8 @@ var check = {
13071
13256
  const sample = eligible.find((f) => f.language === lang);
13072
13257
  if (!sample) continue;
13073
13258
  findings.push({
13074
- id: `${CHECK_ID43}:${sample.relativePath}:degraded:${lang}`,
13075
- checkId: CHECK_ID43,
13259
+ id: `${CHECK_ID44}:${sample.relativePath}:degraded:${lang}`,
13260
+ checkId: CHECK_ID44,
13076
13261
  category: "security",
13077
13262
  severity: "info",
13078
13263
  message: resolveCopy3("checks.missing-csrf-protection.degraded", sample.relativePath, lang),
@@ -13093,8 +13278,8 @@ var check = {
13093
13278
  handlerCount > 1 ? `${handlerCount} state-changing handlers (e.g. ${exampleRoute})` : exampleRoute
13094
13279
  );
13095
13280
  const finding = {
13096
- id: `${CHECK_ID43}:project:${anchor.relativePath}:1`,
13097
- checkId: CHECK_ID43,
13281
+ id: `${CHECK_ID44}:project:${anchor.relativePath}:1`,
13282
+ checkId: CHECK_ID44,
13098
13283
  category: "security",
13099
13284
  severity: "medium",
13100
13285
  message,
@@ -13109,7 +13294,7 @@ var check = {
13109
13294
  }
13110
13295
  };
13111
13296
  registry.register(check);
13112
- var CHECK_ID44 = "security.weak-session-id";
13297
+ var CHECK_ID45 = "security.weak-session-id";
13113
13298
  var QUERIES10 = {
13114
13299
  php: `
13115
13300
  (function_call_expression
@@ -13196,7 +13381,7 @@ var QUERIES10 = {
13196
13381
  };
13197
13382
  registry.register(
13198
13383
  createTreeSitterCheck({
13199
- id: CHECK_ID44,
13384
+ id: CHECK_ID45,
13200
13385
  nameKey: "checks.weak-session-id.name",
13201
13386
  descriptionKey: "checks.weak-session-id.description",
13202
13387
  category: "security",
@@ -13214,7 +13399,7 @@ registry.register(
13214
13399
  }
13215
13400
  })
13216
13401
  );
13217
- var CHECK_ID45 = "security.rails-mass-assignment";
13402
+ var CHECK_ID46 = "security.rails-mass-assignment";
13218
13403
  var QUERIES11 = {
13219
13404
  ruby: `
13220
13405
  (call
@@ -13237,7 +13422,7 @@ var QUERIES11 = {
13237
13422
  };
13238
13423
  registry.register(
13239
13424
  createTreeSitterCheck({
13240
- id: CHECK_ID45,
13425
+ id: CHECK_ID46,
13241
13426
  nameKey: "checks.rails-mass-assignment.name",
13242
13427
  descriptionKey: "checks.rails-mass-assignment.description",
13243
13428
  category: "security",
@@ -13249,7 +13434,7 @@ registry.register(
13249
13434
  remediationKey: "remediation.security.rails-mass-assignment"
13250
13435
  })
13251
13436
  );
13252
- var CHECK_ID46 = "security.rails-insecure-session-config";
13437
+ var CHECK_ID47 = "security.rails-insecure-session-config";
13253
13438
  var REMEDIATION37 = getCopy("remediation.security.rails-insecure-session-config");
13254
13439
  var SESSION_STORE_PATH = /config\/initializers\/session_store\.rb$/;
13255
13440
  function hasSecureFlag(content) {
@@ -13262,7 +13447,7 @@ function hasSameSiteFlag(content) {
13262
13447
  return /\bsame_site:\s*:(strict|lax)\b/.test(content);
13263
13448
  }
13264
13449
  registry.register({
13265
- id: CHECK_ID46,
13450
+ id: CHECK_ID47,
13266
13451
  name: getCopy("checks.rails-insecure-session-config.name"),
13267
13452
  description: getCopy("checks.rails-insecure-session-config.description"),
13268
13453
  category: "security",
@@ -13282,8 +13467,8 @@ registry.register({
13282
13467
  if (!hasSameSiteFlag(content)) missing.push("same_site: :strict or :lax");
13283
13468
  if (missing.length > 0) {
13284
13469
  findings.push({
13285
- id: `${CHECK_ID46}:${file.relativePath}:1`,
13286
- checkId: CHECK_ID46,
13470
+ id: `${CHECK_ID47}:${file.relativePath}:1`,
13471
+ checkId: CHECK_ID47,
13287
13472
  category: "security",
13288
13473
  severity: "medium",
13289
13474
  message: getCopy(
@@ -13300,7 +13485,7 @@ registry.register({
13300
13485
  return findings;
13301
13486
  }
13302
13487
  });
13303
- var CHECK_ID47 = "security.xxe-surface";
13488
+ var CHECK_ID48 = "security.xxe-surface";
13304
13489
  var LIBXML_PARSE_QUERY = `
13305
13490
  (call_expression
13306
13491
  function: (member_expression
@@ -13347,7 +13532,7 @@ var QUERIES12 = {
13347
13532
  };
13348
13533
  registry.register(
13349
13534
  createTreeSitterCheck({
13350
- id: CHECK_ID47,
13535
+ id: CHECK_ID48,
13351
13536
  nameKey: "checks.xxe-surface.name",
13352
13537
  descriptionKey: "checks.xxe-surface.description",
13353
13538
  category: "security",
@@ -13546,12 +13731,13 @@ registry.register({
13546
13731
  return runLLMCheck(ctx, promptTemplate22);
13547
13732
  }
13548
13733
  });
13549
- var PROMPT_VERSION23 = "1.0.0";
13734
+ var PROMPT_VERSION23 = "1.1.0";
13550
13735
  var fallbackPatterns23 = [
13551
13736
  {
13552
13737
  regex: /while\s*\(\s*(?:true|1)\s*\)|while\s+True\s*:/i,
13553
13738
  message: "Infinite loop found \u2014 verify retry logic has a maximum attempt limit",
13554
- severity: "medium"
13739
+ severity: "medium",
13740
+ skipCommentLines: true
13555
13741
  }
13556
13742
  ];
13557
13743
  async function buildPrompt23(sourceFiles) {
@@ -13613,12 +13799,13 @@ registry.register({
13613
13799
  return [...bashFindings, ...llmFindings];
13614
13800
  }
13615
13801
  });
13616
- var PROMPT_VERSION24 = "1.1.0";
13802
+ var PROMPT_VERSION24 = "1.2.0";
13617
13803
  var fallbackPatterns24 = [
13618
13804
  {
13619
13805
  regex: /console\.(?:log|error|warn)\b/,
13620
13806
  message: "Unstructured logging detected \u2014 verify observability hooks are present",
13621
- severity: "medium"
13807
+ severity: "medium",
13808
+ skipCommentLines: true
13622
13809
  }
13623
13810
  ];
13624
13811
  async function buildPrompt24(sourceFiles) {
@@ -13841,12 +14028,13 @@ registry.register({
13841
14028
  return runLLMCheck(ctx, promptTemplate27);
13842
14029
  }
13843
14030
  });
13844
- var PROMPT_VERSION28 = "1.0.0";
14031
+ var PROMPT_VERSION28 = "1.1.0";
13845
14032
  var fallbackPatterns28 = [
13846
14033
  {
13847
14034
  regex: /\b(?:fingerprint|deviceId|visitorId|trackingId|correlationId|analyticsId)\b/i,
13848
14035
  message: "Derived identifier may link records across sessions or users",
13849
- severity: "medium"
14036
+ severity: "medium",
14037
+ skipCommentLines: true
13850
14038
  },
13851
14039
  {
13852
14040
  regex: /\bhash\s*\(\s*(?:email|phone|userId|id)\s*\)/i,
@@ -13880,15 +14068,64 @@ var promptTemplate28 = {
13880
14068
  parseResponse: parseResponse28,
13881
14069
  fallbackPatterns: fallbackPatterns28
13882
14070
  };
14071
+ var CHECK_ID49 = "data-exposure.derived-identifiers";
14072
+ var DERIVED_ID_REGEX = /\b(?:fingerprint|device.?id|visitor.?id|tracking.?id|correlation.?id|analytics.?id)\b/i;
14073
+ var DERIVED_TS_QUERIES = {
14074
+ javascript: `
14075
+ (lexical_declaration (variable_declarator name: (identifier) @name)) @match
14076
+ (pair key: (property_identifier) @name) @match
14077
+ (assignment_expression left: (identifier) @name) @match
14078
+ `,
14079
+ typescript: `
14080
+ (lexical_declaration (variable_declarator name: (identifier) @name)) @match
14081
+ (pair key: (property_identifier) @name) @match
14082
+ (property_signature name: (property_identifier) @name) @match
14083
+ `,
14084
+ tsx: `
14085
+ (lexical_declaration (variable_declarator name: (identifier) @name)) @match
14086
+ (pair key: (property_identifier) @name) @match
14087
+ (property_signature name: (property_identifier) @name) @match
14088
+ `,
14089
+ go: `
14090
+ (short_var_declaration left: (expression_list (identifier) @name)) @match
14091
+ (var_declaration (var_spec name: (identifier) @name)) @match
14092
+ `,
14093
+ python: `
14094
+ (assignment left: (identifier) @name) @match
14095
+ `,
14096
+ java: `
14097
+ (local_variable_declaration declarator: (variable_declarator name: (identifier) @name)) @match
14098
+ (field_declaration declarator: (variable_declarator name: (identifier) @name)) @match
14099
+ `
14100
+ };
14101
+ var derivedIdAstCheck = createTreeSitterCheck({
14102
+ id: CHECK_ID49,
14103
+ nameKey: "checks.derived-identifiers.name",
14104
+ descriptionKey: "checks.derived-identifiers.description",
14105
+ category: "data-exposure",
14106
+ severity: "medium",
14107
+ minTier: "pro",
14108
+ queries: DERIVED_TS_QUERIES,
14109
+ messageKey: "checks.derived-identifiers.message",
14110
+ degradedMessageKey: "checks.derived-identifiers.degraded",
14111
+ remediationKey: "remediation.data-exposure.derived-identifiers",
14112
+ fileFilter: (file) => !isMechanicalCheckExcludedPath(file.relativePath),
14113
+ matchFilter: (captures) => DERIVED_ID_REGEX.test(captures.name ?? ""),
14114
+ messageFactory: (captures) => getCopy("checks.derived-identifiers.message", captures.name ?? "unknown")
14115
+ });
13883
14116
  registry.register({
13884
- id: "data-exposure.derived-identifiers",
14117
+ id: CHECK_ID49,
13885
14118
  name: getCopy("checks.derived-identifiers.name"),
13886
14119
  description: getCopy("checks.derived-identifiers.description"),
13887
14120
  category: "data-exposure",
13888
14121
  severity: "medium",
13889
14122
  minTier: "pro",
13890
14123
  async run(ctx) {
13891
- return runLLMCheck(ctx, promptTemplate28);
14124
+ const [llmFindings, astFindings] = await Promise.all([
14125
+ runLLMCheck(ctx, promptTemplate28),
14126
+ derivedIdAstCheck.run(ctx)
14127
+ ]);
14128
+ return mergeFindings(astFindings, llmFindings);
13892
14129
  }
13893
14130
  });
13894
14131
  var PROMPT_VERSION29 = "1.0.0";
@@ -13954,12 +14191,12 @@ registry.register({
13954
14191
  return [...bashFindings, ...llmFindings];
13955
14192
  }
13956
14193
  });
13957
- var CHECK_ID48 = "security.permissive-grants";
14194
+ var CHECK_ID50 = "security.permissive-grants";
13958
14195
  var GRANT_PATTERN = /\bgrant\s+(?:all|all\s+privileges|select|insert|update|delete|usage|execute)\b[\s\S]*?\bto\s+(?:public|anon|anonymous|guest)\b/gi;
13959
14196
  var ALTER_DEFAULT_PRIVILEGES_PATTERN = /\balter\s+default\s+privileges\b[\s\S]*?\bgrant\s+[\s\S]*?\bto\s+(?:public|anon|anonymous|guest)\b/gi;
13960
14197
  var REMEDIATION38 = getCopy("remediation.security.permissive-grants");
13961
14198
  var permissiveGrantsCheck = {
13962
- id: CHECK_ID48,
14199
+ id: CHECK_ID50,
13963
14200
  name: getCopy("checks.permissive-grants.name"),
13964
14201
  description: getCopy("checks.permissive-grants.description"),
13965
14202
  category: "security",
@@ -13976,8 +14213,8 @@ var permissiveGrantsCheck = {
13976
14213
  const beforeMatch = content.slice(0, match.index);
13977
14214
  const lineNum = beforeMatch.split("\n").length;
13978
14215
  findings.push({
13979
- id: `${CHECK_ID48}:${file.relativePath}:${lineNum}:1`,
13980
- checkId: CHECK_ID48,
14216
+ id: `${CHECK_ID50}:${file.relativePath}:${lineNum}:1`,
14217
+ checkId: CHECK_ID50,
13981
14218
  category: "security",
13982
14219
  severity: "high",
13983
14220
  message: getCopy("checks.permissive-grants.message", match[0].trim()),
@@ -13994,8 +14231,8 @@ var permissiveGrantsCheck = {
13994
14231
  const beforeMatch = content.slice(0, altMatch.index);
13995
14232
  const lineNum = beforeMatch.split("\n").length;
13996
14233
  findings.push({
13997
- id: `${CHECK_ID48}:${file.relativePath}:${lineNum}:1`,
13998
- checkId: CHECK_ID48,
14234
+ id: `${CHECK_ID50}:${file.relativePath}:${lineNum}:1`,
14235
+ checkId: CHECK_ID50,
13999
14236
  category: "security",
14000
14237
  severity: "high",
14001
14238
  message: getCopy("checks.permissive-grants.message", altMatch[0].trim()),
@@ -14035,10 +14272,10 @@ function classifySqlDialect(content) {
14035
14272
  }
14036
14273
  return "unknown";
14037
14274
  }
14038
- var CHECK_ID49 = "security.sql-rls-static";
14275
+ var CHECK_ID51 = "security.sql-rls-static";
14039
14276
  var REMEDIATION39 = getCopy("remediation.security.sql-rls-static");
14040
14277
  var sqlRlsStaticCheck = {
14041
- id: CHECK_ID49,
14278
+ id: CHECK_ID51,
14042
14279
  name: getCopy("checks.sql-rls-static.name"),
14043
14280
  description: getCopy("checks.sql-rls-static.description"),
14044
14281
  category: "security",
@@ -14093,8 +14330,8 @@ var sqlRlsStaticCheck = {
14093
14330
  const isRlsEnabled = enabledTables.has(table.name);
14094
14331
  if (!isRlsEnabled) {
14095
14332
  findings.push({
14096
- id: `${CHECK_ID49}:${file.relativePath}:${table.line}:1:missing-rls`,
14097
- checkId: CHECK_ID49,
14333
+ id: `${CHECK_ID51}:${file.relativePath}:${table.line}:1:missing-rls`,
14334
+ checkId: CHECK_ID51,
14098
14335
  category: "security",
14099
14336
  severity: "high",
14100
14337
  message: getCopy("checks.sql-rls-static.message", `Table "${table.name}" contains tenant columns but Row-Level Security is not enabled.`),
@@ -14108,8 +14345,8 @@ var sqlRlsStaticCheck = {
14108
14345
  const isRlsForced = forcedTables.has(table.name);
14109
14346
  if (!isRlsForced) {
14110
14347
  findings.push({
14111
- id: `${CHECK_ID49}:${file.relativePath}:${table.line}:1:missing-force-rls`,
14112
- checkId: CHECK_ID49,
14348
+ id: `${CHECK_ID51}:${file.relativePath}:${table.line}:1:missing-force-rls`,
14349
+ checkId: CHECK_ID51,
14113
14350
  category: "security",
14114
14351
  severity: "high",
14115
14352
  message: getCopy("checks.sql-rls-static.message", `Table "${table.name}" has Row-Level Security enabled but lacks FORCE ROW LEVEL SECURITY, creating tenant bypass risks for table owners.`),
@@ -14133,8 +14370,8 @@ var sqlRlsStaticCheck = {
14133
14370
  const beforeMatch = content.slice(0, policyMatch.index);
14134
14371
  const lineNum = beforeMatch.split("\n").length;
14135
14372
  findings.push({
14136
- id: `${CHECK_ID49}:${file.relativePath}:${lineNum}:1:weak-policy`,
14137
- checkId: CHECK_ID49,
14373
+ id: `${CHECK_ID51}:${file.relativePath}:${lineNum}:1:weak-policy`,
14374
+ checkId: CHECK_ID51,
14138
14375
  category: "security",
14139
14376
  severity: "high",
14140
14377
  message: getCopy("checks.sql-rls-static.message", `Policy ${policyName} on table "${tableName}" uses an unrestrictive tautology (like true or 1=1).`),
@@ -14152,10 +14389,10 @@ var sqlRlsStaticCheck = {
14152
14389
  }
14153
14390
  };
14154
14391
  registry.register(sqlRlsStaticCheck);
14155
- var CHECK_ID50 = "security.sql-security-definer-search-path";
14392
+ var CHECK_ID52 = "security.sql-security-definer-search-path";
14156
14393
  var REMEDIATION40 = getCopy("remediation.security.sql-security-definer-search-path");
14157
14394
  var sqlSecurityDefinerSearchPathCheck = {
14158
- id: CHECK_ID50,
14395
+ id: CHECK_ID52,
14159
14396
  name: getCopy("checks.sql-security-definer-search-path.name"),
14160
14397
  description: getCopy("checks.sql-security-definer-search-path.description"),
14161
14398
  category: "security",
@@ -14181,8 +14418,8 @@ var sqlSecurityDefinerSearchPathCheck = {
14181
14418
  const beforeMatch = content.slice(0, match.index);
14182
14419
  const lineNum = beforeMatch.split("\n").length;
14183
14420
  findings.push({
14184
- id: `${CHECK_ID50}:${file.relativePath}:${lineNum}:1`,
14185
- checkId: CHECK_ID50,
14421
+ id: `${CHECK_ID52}:${file.relativePath}:${lineNum}:1`,
14422
+ checkId: CHECK_ID52,
14186
14423
  category: "security",
14187
14424
  severity: "high",
14188
14425
  message: getCopy("checks.sql-security-definer-search-path.message", `Function "${functionName}" is defined as SECURITY DEFINER but is missing a locked search_path.`),
@@ -14201,7 +14438,7 @@ var sqlSecurityDefinerSearchPathCheck = {
14201
14438
  }
14202
14439
  };
14203
14440
  registry.register(sqlSecurityDefinerSearchPathCheck);
14204
- var CHECK_ID51 = "resilience.missing-transaction-guard";
14441
+ var CHECK_ID53 = "resilience.missing-transaction-guard";
14205
14442
  var REMEDIATION41 = getCopy("remediation.resilience.missing-transaction-guard");
14206
14443
  var MUTATION_KEYWORDS = [
14207
14444
  /\binsert\s+into\b/i,
@@ -14214,7 +14451,7 @@ var MUTATION_KEYWORDS = [
14214
14451
  /\bcreate\s+table\s+\w+\s+as\b/i
14215
14452
  ];
14216
14453
  var missingTransactionGuardCheck = {
14217
- id: CHECK_ID51,
14454
+ id: CHECK_ID53,
14218
14455
  name: getCopy("checks.missing-transaction-guard.name"),
14219
14456
  description: getCopy("checks.missing-transaction-guard.description"),
14220
14457
  category: "resilience",
@@ -14244,8 +14481,8 @@ var missingTransactionGuardCheck = {
14244
14481
  }
14245
14482
  if (mutationCount > 5) {
14246
14483
  findings.push({
14247
- id: `${CHECK_ID51}:${file.relativePath}:1:1`,
14248
- checkId: CHECK_ID51,
14484
+ id: `${CHECK_ID53}:${file.relativePath}:1:1`,
14485
+ checkId: CHECK_ID53,
14249
14486
  category: "resilience",
14250
14487
  severity: "medium",
14251
14488
  message: getCopy("checks.missing-transaction-guard.message"),
@@ -14260,10 +14497,10 @@ var missingTransactionGuardCheck = {
14260
14497
  }
14261
14498
  };
14262
14499
  registry.register(missingTransactionGuardCheck);
14263
- var CHECK_ID52 = "resilience.destructive-sql-migration";
14500
+ var CHECK_ID54 = "resilience.destructive-sql-migration";
14264
14501
  var REMEDIATION42 = getCopy("remediation.resilience.destructive-sql-migration");
14265
14502
  var destructiveSqlMigrationCheck = {
14266
- id: CHECK_ID52,
14503
+ id: CHECK_ID54,
14267
14504
  name: getCopy("checks.destructive-sql-migration.name"),
14268
14505
  description: getCopy("checks.destructive-sql-migration.description"),
14269
14506
  category: "resilience",
@@ -14284,8 +14521,8 @@ var destructiveSqlMigrationCheck = {
14284
14521
  const highMatch = highRiskRegex.exec(line);
14285
14522
  if (highMatch) {
14286
14523
  findings.push({
14287
- id: `${CHECK_ID52}:${file.relativePath}:${lineNum + 1}:${(highMatch.index ?? 0) + 1}:high`,
14288
- checkId: CHECK_ID52,
14524
+ id: `${CHECK_ID54}:${file.relativePath}:${lineNum + 1}:${(highMatch.index ?? 0) + 1}:high`,
14525
+ checkId: CHECK_ID54,
14289
14526
  category: "resilience",
14290
14527
  severity: "high",
14291
14528
  message: getCopy("checks.destructive-sql-migration.message", highMatch[0]),
@@ -14298,8 +14535,8 @@ var destructiveSqlMigrationCheck = {
14298
14535
  const truncateMatch = truncateRegex.exec(line);
14299
14536
  if (truncateMatch) {
14300
14537
  findings.push({
14301
- id: `${CHECK_ID52}:${file.relativePath}:${lineNum + 1}:${(truncateMatch.index ?? 0) + 1}:high`,
14302
- checkId: CHECK_ID52,
14538
+ id: `${CHECK_ID54}:${file.relativePath}:${lineNum + 1}:${(truncateMatch.index ?? 0) + 1}:high`,
14539
+ checkId: CHECK_ID54,
14303
14540
  category: "resilience",
14304
14541
  severity: "high",
14305
14542
  message: getCopy("checks.destructive-sql-migration.message", truncateMatch[0]),
@@ -14315,8 +14552,8 @@ var destructiveSqlMigrationCheck = {
14315
14552
  const colMatch = dropColumnRegex.exec(line);
14316
14553
  if (colMatch) {
14317
14554
  findings.push({
14318
- id: `${CHECK_ID52}:${file.relativePath}:${lineNum + 1}:${(colMatch.index ?? 0) + 1}:col`,
14319
- checkId: CHECK_ID52,
14555
+ id: `${CHECK_ID54}:${file.relativePath}:${lineNum + 1}:${(colMatch.index ?? 0) + 1}:col`,
14556
+ checkId: CHECK_ID54,
14320
14557
  category: "resilience",
14321
14558
  severity: "medium",
14322
14559
  message: getCopy("checks.destructive-sql-migration.message", colMatch[0]),
@@ -14329,8 +14566,8 @@ var destructiveSqlMigrationCheck = {
14329
14566
  const policyMatch = dropPolicyRegex.exec(line);
14330
14567
  if (policyMatch) {
14331
14568
  findings.push({
14332
- id: `${CHECK_ID52}:${file.relativePath}:${lineNum + 1}:${(policyMatch.index ?? 0) + 1}:policy`,
14333
- checkId: CHECK_ID52,
14569
+ id: `${CHECK_ID54}:${file.relativePath}:${lineNum + 1}:${(policyMatch.index ?? 0) + 1}:policy`,
14570
+ checkId: CHECK_ID54,
14334
14571
  category: "resilience",
14335
14572
  severity: "medium",
14336
14573
  message: getCopy("checks.destructive-sql-migration.message", policyMatch[0]),
@@ -14343,8 +14580,8 @@ var destructiveSqlMigrationCheck = {
14343
14580
  const indexMatch = dropIndexRegex.exec(line);
14344
14581
  if (indexMatch) {
14345
14582
  findings.push({
14346
- id: `${CHECK_ID52}:${file.relativePath}:${lineNum + 1}:${(indexMatch.index ?? 0) + 1}:index`,
14347
- checkId: CHECK_ID52,
14583
+ id: `${CHECK_ID54}:${file.relativePath}:${lineNum + 1}:${(indexMatch.index ?? 0) + 1}:index`,
14584
+ checkId: CHECK_ID54,
14348
14585
  category: "resilience",
14349
14586
  severity: "medium",
14350
14587
  message: getCopy("checks.destructive-sql-migration.message", indexMatch[0]),
@@ -14360,7 +14597,7 @@ var destructiveSqlMigrationCheck = {
14360
14597
  }
14361
14598
  };
14362
14599
  registry.register(destructiveSqlMigrationCheck);
14363
- var CHECK_ID53 = "resilience.sql-broad-mutation";
14600
+ var CHECK_ID55 = "resilience.sql-broad-mutation";
14364
14601
  var REMEDIATION43 = getCopy("remediation.resilience.sql-broad-mutation");
14365
14602
  function splitSqlStatements(content) {
14366
14603
  const statements = [];
@@ -14398,7 +14635,7 @@ function splitSqlStatements(content) {
14398
14635
  return statements;
14399
14636
  }
14400
14637
  var sqlBroadMutationCheck = {
14401
- id: CHECK_ID53,
14638
+ id: CHECK_ID55,
14402
14639
  name: getCopy("checks.sql-broad-mutation.name"),
14403
14640
  description: getCopy("checks.sql-broad-mutation.description"),
14404
14641
  category: "resilience",
@@ -14444,8 +14681,8 @@ var sqlBroadMutationCheck = {
14444
14681
  const beforeStatement = content.slice(0, charOffset + leadingWhitespace);
14445
14682
  const lineNum = beforeStatement.split("\n").length;
14446
14683
  findings.push({
14447
- id: `${CHECK_ID53}:${file.relativePath}:${lineNum}:1`,
14448
- checkId: CHECK_ID53,
14684
+ id: `${CHECK_ID55}:${file.relativePath}:${lineNum}:1`,
14685
+ checkId: CHECK_ID55,
14449
14686
  category: "resilience",
14450
14687
  severity: "high",
14451
14688
  message: getCopy("checks.sql-broad-mutation.message", matchedKeyword),
@@ -14463,7 +14700,7 @@ var sqlBroadMutationCheck = {
14463
14700
  }
14464
14701
  };
14465
14702
  registry.register(sqlBroadMutationCheck);
14466
- var CHECK_ID54 = "data-exposure.unprotected-sensitive-sql-columns";
14703
+ var CHECK_ID56 = "data-exposure.unprotected-sensitive-sql-columns";
14467
14704
  var REMEDIATION44 = getCopy("remediation.data-exposure.unprotected-sensitive-sql-columns");
14468
14705
  var SENSITIVE_KEYWORDS = [
14469
14706
  "ssn",
@@ -14495,7 +14732,7 @@ var PROTECTED_SUFFIXES = [
14495
14732
  "_key_id"
14496
14733
  ];
14497
14734
  var unprotectedSensitiveSqlColumnsCheck = {
14498
- id: CHECK_ID54,
14735
+ id: CHECK_ID56,
14499
14736
  name: getCopy("checks.unprotected-sensitive-sql-columns.name"),
14500
14737
  description: getCopy("checks.unprotected-sensitive-sql-columns.description"),
14501
14738
  category: "data-exposure",
@@ -14525,8 +14762,8 @@ var unprotectedSensitiveSqlColumnsCheck = {
14525
14762
  const isProtected = PROTECTED_SUFFIXES.some((suf) => colName.endsWith(suf));
14526
14763
  if (isSensitive && isPlainText && !isProtected) {
14527
14764
  findings.push({
14528
- id: `${CHECK_ID54}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 1}`,
14529
- checkId: CHECK_ID54,
14765
+ id: `${CHECK_ID56}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 1}`,
14766
+ checkId: CHECK_ID56,
14530
14767
  category: "data-exposure",
14531
14768
  severity: "high",
14532
14769
  message: getCopy("checks.unprotected-sensitive-sql-columns.message", `Column "${colNameRaw}" with type "${colTypeRaw}"`),
@@ -14590,9 +14827,9 @@ registry.register({
14590
14827
  return runLLMCheck(ctx, promptTemplate30);
14591
14828
  }
14592
14829
  });
14593
- var CHECK_ID55 = "security.rust-unsafe-blocks";
14830
+ var CHECK_ID57 = "security.rust-unsafe-blocks";
14594
14831
  registry.register({
14595
- id: CHECK_ID55,
14832
+ id: CHECK_ID57,
14596
14833
  name: getCopy("checks.rust-unsafe-blocks.name"),
14597
14834
  description: getCopy("checks.rust-unsafe-blocks.description"),
14598
14835
  category: "security",
@@ -14679,8 +14916,8 @@ registry.register({
14679
14916
  }
14680
14917
  if (!foundSafety) {
14681
14918
  const finding = {
14682
- id: `${CHECK_ID55}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
14683
- checkId: CHECK_ID55,
14919
+ id: `${CHECK_ID57}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
14920
+ checkId: CHECK_ID57,
14684
14921
  category: "security",
14685
14922
  severity: "medium",
14686
14923
  message: getCopy("checks.rust-unsafe-blocks.message", match.nodeText),
@@ -14698,7 +14935,7 @@ registry.register({
14698
14935
  return findings;
14699
14936
  }
14700
14937
  });
14701
- var CHECK_ID56 = "resilience.rust-handler-panics";
14938
+ var CHECK_ID58 = "resilience.rust-handler-panics";
14702
14939
  var RUST_QUERY = `
14703
14940
  (call_expression
14704
14941
  function: (field_expression
@@ -14724,7 +14961,7 @@ function hasAsyncModifier(node) {
14724
14961
  return false;
14725
14962
  }
14726
14963
  registry.register({
14727
- id: CHECK_ID56,
14964
+ id: CHECK_ID58,
14728
14965
  name: getCopy("checks.rust-handler-panics.name"),
14729
14966
  description: getCopy("checks.rust-handler-panics.description"),
14730
14967
  category: "resilience",
@@ -14790,8 +15027,8 @@ registry.register({
14790
15027
  const isAsync = hasAsyncModifier(enclosingFunction);
14791
15028
  if (isAsync) {
14792
15029
  const finding = {
14793
- id: `${CHECK_ID56}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
14794
- checkId: CHECK_ID56,
15030
+ id: `${CHECK_ID58}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
15031
+ checkId: CHECK_ID58,
14795
15032
  category: "resilience",
14796
15033
  severity: "high",
14797
15034
  message: getCopy("checks.rust-handler-panics.message", match.nodeText),
@@ -16229,6 +16466,7 @@ async function executeScan(args, ctx) {
16229
16466
  debug: args.debug,
16230
16467
  baseline: args.baseline,
16231
16468
  minConfidence: args.minConfidence,
16469
+ minSeverity: args.minSeverity,
16232
16470
  onCheckComplete: (completed, total, checkName) => {
16233
16471
  heartbeat();
16234
16472
  if (showProgress) {
@@ -16608,6 +16846,7 @@ async function runCli(options) {
16608
16846
  debug: options.debug,
16609
16847
  baseline: options.baseline,
16610
16848
  minConfidence: options.minConfidence,
16849
+ minSeverity: options.minSeverity,
16611
16850
  showIds: options.showIds,
16612
16851
  all: options.all
16613
16852
  };
@@ -16786,12 +17025,13 @@ var require5 = createRequire4(import.meta.url);
16786
17025
  var { version } = require5("../package.json");
16787
17026
  var cli = cac("seaworthycode");
16788
17027
  cli.version(version);
16789
- cli.command("[path]", "Scan a directory for issues").option("--json", "Output JSON to stdout").option("--output <path>", "Write HTML report to file").option("--sarif <path>", "Write SARIF report to file").option("--sarif-stdout", "Write SARIF report to stdout").option("--fail-on-findings", "Exit with code 5 when findings are found").option("--tier <tier>", "Select tier (free, pro)").option("--provider <provider>", "LLM provider (anthropic, openai, kimi, moonshot, deepseek, gemini, minimax, ollama, openrouter)").option("--max-file-size <bytes>", "Skip files larger than this (default: 1 MB)").option("--show-ids", "Show check IDs alongside names in terminal output").option("--debug", "Enable debug logging").option("--baseline [ref]", "Compare against a baseline file or save current findings as baseline (--baseline save)").option("--min-confidence <level>", "Hide findings below this confidence (high, medium, low)").option("--all", "List every finding; do not collapse low-severity findings").action(async (targetDir, options) => {
17028
+ cli.command("[path]", "Scan a directory for issues").option("--json", "Output JSON to stdout").option("--output <path>", "Write HTML report to file").option("--sarif <path>", "Write SARIF report to file").option("--sarif-stdout", "Write SARIF report to stdout").option("--fail-on-findings", "Exit with code 5 when findings are found").option("--tier <tier>", "Select tier (free, pro)").option("--provider <provider>", "LLM provider (anthropic, openai, kimi, moonshot, deepseek, gemini, minimax, ollama, openrouter)").option("--max-file-size <bytes>", "Skip files larger than this (default: 1 MB)").option("--show-ids", "Show check IDs alongside names in terminal output").option("--debug", "Enable debug logging").option("--baseline [ref]", "Compare against a baseline file or save current findings as baseline (--baseline save)").option("--min-confidence <level>", "Hide findings below this confidence (high, medium, low)").option("--min-severity <level>", "Hide findings below this severity (critical, high, medium, low, info)").option("--all", "List every finding; do not collapse low-severity findings").action(async (targetDir, options) => {
16790
17029
  const maxFileSize = options.maxFileSize ? Number(options.maxFileSize) : void 0;
16791
17030
  const tier = options.tier;
16792
17031
  const baseline = options.baseline === "save" ? "save" : options.baseline;
16793
17032
  const minConfidence = options.minConfidence;
16794
- const result = await runCli({ targetDir, json: options.json, output: options.output, sarif: options.sarif, sarifStdout: options.sarifStdout, failOnFindings: options.failOnFindings, tier, provider: options.provider, maxFileSize, showIds: options.showIds, debug: options.debug, baseline, minConfidence, all: options.all });
17033
+ const minSeverity = options.minSeverity;
17034
+ const result = await runCli({ targetDir, json: options.json, output: options.output, sarif: options.sarif, sarifStdout: options.sarifStdout, failOnFindings: options.failOnFindings, tier, provider: options.provider, maxFileSize, showIds: options.showIds, debug: options.debug, baseline, minConfidence, minSeverity, all: options.all });
16795
17035
  if (result.updateMessage) {
16796
17036
  console.error(result.updateMessage);
16797
17037
  }
@@ -16859,6 +17099,7 @@ cli.help(() => {
16859
17099
  --max-file-size <n> Skip files larger than n bytes (default: 1 MB)
16860
17100
  --baseline [ref] Compare against baseline file or save (--baseline save)
16861
17101
  --min-confidence <level> Hide findings below confidence (high, medium, low)
17102
+ --min-severity <level> Hide findings below severity (critical, high, medium, low, info)
16862
17103
  --show-ids Show check IDs alongside names
16863
17104
  --all List every finding; do not collapse low-severity findings
16864
17105
  --debug Enable debug logging`