seaworthycode 1.2.18 → 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
@@ -207,6 +207,7 @@ var registry = new CheckRegistry();
207
207
  var SKIP_DIRS = /* @__PURE__ */ new Set([
208
208
  "node_modules",
209
209
  ".git",
210
+ ".claude",
210
211
  "dist",
211
212
  "build",
212
213
  ".next",
@@ -640,6 +641,14 @@ function filterByMinConfidence(findings, minConfidence) {
640
641
  if (!minConfidence) return findings;
641
642
  return findings.filter((f) => meetsMinConfidence(f.confidence, minConfidence));
642
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
+ }
643
652
  var SITE_URL = process.env.SEAWORTHY_SITE_URL ?? "https://seaworthycode.com";
644
653
  var whyCopy = {
645
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).",
@@ -1059,6 +1068,8 @@ var copy = {
1059
1068
  "checks.verbose-errors.description": "Error responses include stack traces or internal details, leaking implementation information to clients.",
1060
1069
  "checks.pii-in-responses.name": "PII in API responses",
1061
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.`,
1062
1073
  "checks.no-response-filtering.name": "No response filtering",
1063
1074
  "checks.no-response-filtering.description": "API responses return full database rows without filtering sensitive or unnecessary fields.",
1064
1075
  "checks.missing-lockfile.name": "Missing lockfile",
@@ -1727,6 +1738,7 @@ var ScanRunner = class {
1727
1738
  dedupeDegradedFindings(dedupeCrossCheck(dedupeFindings(results)))
1728
1739
  );
1729
1740
  findings = filterByMinConfidence(findings, options.minConfidence);
1741
+ findings = filterByMinSeverity(findings, options.minSeverity);
1730
1742
  if (options.baseline) {
1731
1743
  if (options.baseline === "save") {
1732
1744
  const baselinePath = resolveBaselinePath(options.targetDir);
@@ -3903,6 +3915,14 @@ function setAnalysisType(finding, analysisType = "pattern") {
3903
3915
  finding.properties.analysisType = analysisType;
3904
3916
  }
3905
3917
  var resolveCopy = getCopy;
3918
+ function getTreeSitterMaxFiles() {
3919
+ const env2 = process.env.SEAWORTHY_MECHANICAL_MAX_FILES;
3920
+ if (env2 !== void 0) {
3921
+ const n = parseInt(env2, 10);
3922
+ if (!isNaN(n) && n > 0) return n;
3923
+ }
3924
+ return 2e3;
3925
+ }
3906
3926
  function createTreeSitterCheck(options) {
3907
3927
  const {
3908
3928
  id,
@@ -3935,13 +3955,19 @@ function createTreeSitterCheck(options) {
3935
3955
  async run(ctx) {
3936
3956
  const findings = [];
3937
3957
  const degradedLanguages = /* @__PURE__ */ new Set();
3938
- const eligibleFiles = ctx.files.filter((file) => {
3958
+ const allEligibleFiles = ctx.files.filter((file) => {
3939
3959
  const query = queries[file.language];
3940
3960
  if (!query) return false;
3941
3961
  if (isMechanicalCheckExcludedPath(file.relativePath)) return false;
3942
3962
  if (fileFilter && !fileFilter(file)) return false;
3943
3963
  return true;
3944
3964
  });
3965
+ const maxFiles = getTreeSitterMaxFiles();
3966
+ let eligibleFiles = allEligibleFiles;
3967
+ if (allEligibleFiles.length > maxFiles) {
3968
+ console.error(`[seaworthy] ${id}: ${allEligibleFiles.length} eligible files \u2014 capping tree-sitter scan to ${maxFiles}`);
3969
+ eligibleFiles = allEligibleFiles.slice(0, maxFiles);
3970
+ }
3945
3971
  const fileFindings = await runWithConcurrencyCollect(
3946
3972
  eligibleFiles,
3947
3973
  async (file) => {
@@ -6189,6 +6215,10 @@ async function runMechanicalCheck(ctx, template) {
6189
6215
  }
6190
6216
  for (let lineNum = 0; lineNum < lines.length; lineNum++) {
6191
6217
  const line = lines[lineNum];
6218
+ if (pattern.skipCommentLines) {
6219
+ const trimmed = line.trimStart();
6220
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*")) continue;
6221
+ }
6192
6222
  if (pattern.regex.test(line)) {
6193
6223
  results.push(
6194
6224
  makeMechanicalFinding(template, pattern, file.relativePath, lineNum + 1)
@@ -7082,12 +7112,13 @@ registry.register({
7082
7112
  return runLLMCheck(ctx, promptTemplate8);
7083
7113
  }
7084
7114
  });
7085
- var PROMPT_VERSION9 = "1.2.0";
7115
+ var PROMPT_VERSION9 = "1.3.0";
7086
7116
  var fallbackPatterns9 = [
7087
7117
  {
7088
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,
7089
7119
  message: "Outbound HTTP call found \u2014 verify timeout is configured",
7090
- severity: "medium"
7120
+ severity: "medium",
7121
+ skipCommentLines: true
7091
7122
  }
7092
7123
  ];
7093
7124
  async function buildPrompt9(sourceFiles) {
@@ -7361,12 +7392,13 @@ registry.register({
7361
7392
  return runLLMCheck(ctx, promptTemplate12);
7362
7393
  }
7363
7394
  });
7364
- var PROMPT_VERSION13 = "1.1.0";
7395
+ var PROMPT_VERSION13 = "1.2.0";
7365
7396
  var fallbackPatterns13 = [
7366
7397
  {
7367
7398
  regex: /console\.(?:log|error|warn)\b/i,
7368
7399
  message: "Console logging used \u2014 structured logging recommended",
7369
- severity: "low"
7400
+ severity: "low",
7401
+ skipCommentLines: true
7370
7402
  }
7371
7403
  ];
7372
7404
  async function buildPrompt13(sourceFiles) {
@@ -7503,16 +7535,18 @@ registry.register({
7503
7535
  return runLLMCheck(ctx, promptTemplate15);
7504
7536
  }
7505
7537
  });
7506
- var PROMPT_VERSION16 = "1.0.0";
7538
+ var PROMPT_VERSION16 = "1.1.0";
7507
7539
  var fallbackPatterns16 = [
7508
7540
  {
7509
- 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,
7510
7542
  message: "Default credentials possibly in use",
7511
7543
  severity: "high"
7512
7544
  }
7513
7545
  ];
7514
7546
  async function buildPrompt16(sourceFiles) {
7515
- 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
+ ];
7516
7550
  for (const file of sourceFiles) {
7517
7551
  const content = await file.content();
7518
7552
  parts.push(`--- ${file.relativePath} ---
@@ -7559,7 +7593,13 @@ function isDefaultCredential(password2) {
7559
7593
  "secret",
7560
7594
  "test",
7561
7595
  "postgres",
7562
- "root"
7596
+ "root",
7597
+ "default",
7598
+ "pass",
7599
+ "1234",
7600
+ "12345",
7601
+ "qwerty",
7602
+ "guest"
7563
7603
  ]);
7564
7604
  return defaults.has(normalized);
7565
7605
  }
@@ -7691,6 +7731,115 @@ async function scanBashDefaultCredentials(ctx) {
7691
7731
  return lineMatch(line, index, /(?:^|\s)(?:export\s+)?[A-Z0-9_]*(?:PASS|PASSWORD|PWD|SECRET|TOKEN)[A-Z0-9_]*=(["'])([^"']{1,64})\1/i);
7692
7732
  });
7693
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
+ });
7694
7843
  var sqlCheck = {
7695
7844
  id: CHECK_ID8,
7696
7845
  name: getCopy("checks.default-credentials.name"),
@@ -7699,12 +7848,14 @@ var sqlCheck = {
7699
7848
  severity: "high",
7700
7849
  minTier: "pro",
7701
7850
  async run(ctx) {
7702
- const [sqlFindings, bashFindings, llmFindings] = await Promise.all([
7851
+ const [sqlFindings, bashFindings, llmFindings, astFindings] = await Promise.all([
7703
7852
  scanSqlDefaultCredentials(ctx),
7704
7853
  scanBashDefaultCredentials(ctx),
7705
- runLLMCheck(ctx, promptTemplate16)
7854
+ runLLMCheck(ctx, promptTemplate16),
7855
+ astCheck.run(ctx)
7706
7856
  ]);
7707
- return [...sqlFindings, ...bashFindings, ...llmFindings];
7857
+ const nonAstFindings = [...sqlFindings, ...bashFindings, ...llmFindings];
7858
+ return mergeFindings(astFindings, nonAstFindings);
7708
7859
  }
7709
7860
  };
7710
7861
  registry.register(sqlCheck);
@@ -7766,16 +7917,17 @@ registry.register({
7766
7917
  return [...bashFindings, ...llmFindings];
7767
7918
  }
7768
7919
  });
7769
- var PROMPT_VERSION18 = "1.0.0";
7920
+ var PROMPT_VERSION18 = "1.1.0";
7770
7921
  var fallbackPatterns18 = [
7771
7922
  {
7772
7923
  regex: /\b(?:ssn|social_security|date_of_birth|dob|passport|national_id)\b/i,
7773
7924
  message: "Potentially sensitive field name found \u2014 verify it is not exposed in API responses",
7774
- severity: "high"
7925
+ severity: "high",
7926
+ skipCommentLines: true
7775
7927
  }
7776
7928
  ];
7777
7929
  async function buildPrompt18(sourceFiles) {
7778
- 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."];
7779
7931
  for (const file of sourceFiles) {
7780
7932
  const content = await file.content();
7781
7933
  parts.push(`--- ${file.relativePath} ---
@@ -7793,15 +7945,63 @@ var promptTemplate18 = {
7793
7945
  parseResponse: parseResponse18,
7794
7946
  fallbackPatterns: fallbackPatterns18
7795
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
+ });
7796
7992
  registry.register({
7797
- id: "data-exposure.pii-in-responses",
7798
- name: "PII in API responses",
7799
- 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"),
7800
7996
  category: "data-exposure",
7801
7997
  severity: "high",
7802
7998
  minTier: "pro",
7803
7999
  async run(ctx) {
7804
- 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);
7805
8005
  }
7806
8006
  });
7807
8007
  var PROMPT_VERSION19 = "1.0.0";
@@ -7847,7 +8047,7 @@ registry.register({
7847
8047
  return runLLMCheck(ctx, promptTemplate19);
7848
8048
  }
7849
8049
  });
7850
- var CHECK_ID9 = "data-exposure.html-form-pii-exposure";
8050
+ var CHECK_ID10 = "data-exposure.html-form-pii-exposure";
7851
8051
  var REMEDIATION12 = getCopy("remediation.data-exposure.html-form-pii-exposure");
7852
8052
  var INPUT_PATTERN = /<input\b[^>]*?>/gi;
7853
8053
  var AUTOCOMPLETE_PATTERN = /\bautocomplete\s*=\s*["']([^"']+)["']/i;
@@ -7874,8 +8074,8 @@ function lineAndColumn4(content, index) {
7874
8074
  function pushFinding2(findings, filePath, content, index, matchText) {
7875
8075
  const { line, column } = lineAndColumn4(content, index);
7876
8076
  findings.push({
7877
- id: `${CHECK_ID9}:${filePath}:${line}:${column}`,
7878
- checkId: CHECK_ID9,
8077
+ id: `${CHECK_ID10}:${filePath}:${line}:${column}`,
8078
+ checkId: CHECK_ID10,
7879
8079
  category: "data-exposure",
7880
8080
  severity: "medium",
7881
8081
  message: getCopy("checks.html-form-pii-exposure.message", matchText),
@@ -7901,7 +8101,7 @@ function hasSensitiveField(tagText) {
7901
8101
  return SENSITIVE_TYPES.test(type.trim()) || SENSITIVE_NAME.test(name);
7902
8102
  }
7903
8103
  registry.register({
7904
- id: CHECK_ID9,
8104
+ id: CHECK_ID10,
7905
8105
  name: getCopy("checks.html-form-pii-exposure.name"),
7906
8106
  description: getCopy("checks.html-form-pii-exposure.description"),
7907
8107
  category: "data-exposure",
@@ -7930,7 +8130,7 @@ registry.register({
7930
8130
  return findings;
7931
8131
  }
7932
8132
  });
7933
- var CHECK_ID10 = "security.xss-surface";
8133
+ var CHECK_ID11 = "security.xss-surface";
7934
8134
  var CGI_PATH_HINT = new RegExp("(^|[/_-])cgi([/_.-]|$)", "i");
7935
8135
  function isLikelyCgiBashScript(file) {
7936
8136
  return CGI_PATH_HINT.test(file.relativePath);
@@ -8209,7 +8409,7 @@ var QUERIES = {
8209
8409
  };
8210
8410
  registry.register(
8211
8411
  createTreeSitterCheck({
8212
- id: CHECK_ID10,
8412
+ id: CHECK_ID11,
8213
8413
  nameKey: "checks.xss-surface.name",
8214
8414
  descriptionKey: "checks.xss-surface.description",
8215
8415
  category: "security",
@@ -8224,7 +8424,7 @@ registry.register(
8224
8424
  fileFilter: (file) => file.language !== "bash" || isLikelyCgiBashScript(file)
8225
8425
  })
8226
8426
  );
8227
- var CHECK_ID11 = "security.os-command-injection";
8427
+ var CHECK_ID12 = "security.os-command-injection";
8228
8428
  function isStaticShellLikeLiteral(text22) {
8229
8429
  const t = text22.trim();
8230
8430
  if (t.length < 2) return false;
@@ -8354,7 +8554,7 @@ var QUERIES2 = {
8354
8554
  };
8355
8555
  registry.register(
8356
8556
  createTreeSitterCheck({
8357
- id: CHECK_ID11,
8557
+ id: CHECK_ID12,
8358
8558
  nameKey: "checks.os-command-injection.name",
8359
8559
  descriptionKey: "checks.os-command-injection.description",
8360
8560
  category: "security",
@@ -8371,7 +8571,7 @@ registry.register(
8371
8571
  }
8372
8572
  })
8373
8573
  );
8374
- var CHECK_ID12 = "security.shell-arbitrary-evaluation";
8574
+ var CHECK_ID13 = "security.shell-arbitrary-evaluation";
8375
8575
  var QUERY = `
8376
8576
  (command
8377
8577
  name: (command_name (word) @cmd)
@@ -8385,7 +8585,7 @@ var QUERY = `
8385
8585
  `;
8386
8586
  registry.register(
8387
8587
  createTreeSitterCheck({
8388
- id: CHECK_ID12,
8588
+ id: CHECK_ID13,
8389
8589
  nameKey: "checks.shell-arbitrary-evaluation.name",
8390
8590
  descriptionKey: "checks.shell-arbitrary-evaluation.description",
8391
8591
  category: "security",
@@ -8397,7 +8597,7 @@ registry.register(
8397
8597
  remediationKey: "remediation.security.shell-arbitrary-evaluation"
8398
8598
  })
8399
8599
  );
8400
- var CHECK_ID13 = "security.shell-unescaped-path-deletion";
8600
+ var CHECK_ID14 = "security.shell-unescaped-path-deletion";
8401
8601
  var QUERY2 = `
8402
8602
  (command
8403
8603
  name: (command_name (word) @cmd)
@@ -8427,7 +8627,7 @@ var QUERY2 = `
8427
8627
  `;
8428
8628
  registry.register(
8429
8629
  createTreeSitterCheck({
8430
- id: CHECK_ID13,
8630
+ id: CHECK_ID14,
8431
8631
  nameKey: "checks.shell-unescaped-path-deletion.name",
8432
8632
  descriptionKey: "checks.shell-unescaped-path-deletion.description",
8433
8633
  category: "security",
@@ -8439,7 +8639,7 @@ registry.register(
8439
8639
  remediationKey: "remediation.security.shell-unescaped-path-deletion"
8440
8640
  })
8441
8641
  );
8442
- var CHECK_ID14 = "security.file-inclusion";
8642
+ var CHECK_ID15 = "security.file-inclusion";
8443
8643
  var QUERIES3 = {
8444
8644
  php: `
8445
8645
  (include_expression
@@ -8469,7 +8669,7 @@ var QUERIES3 = {
8469
8669
  };
8470
8670
  registry.register(
8471
8671
  createTreeSitterCheck({
8472
- id: CHECK_ID14,
8672
+ id: CHECK_ID15,
8473
8673
  nameKey: "checks.file-inclusion.name",
8474
8674
  descriptionKey: "checks.file-inclusion.description",
8475
8675
  category: "security",
@@ -8481,7 +8681,7 @@ registry.register(
8481
8681
  remediationKey: "remediation.security.file-inclusion"
8482
8682
  })
8483
8683
  );
8484
- var CHECK_ID15 = "security.open-redirect";
8684
+ var CHECK_ID16 = "security.open-redirect";
8485
8685
  var QUERIES4 = {
8486
8686
  php: `
8487
8687
  (function_call_expression
@@ -8651,7 +8851,7 @@ var QUERIES4 = {
8651
8851
  };
8652
8852
  registry.register(
8653
8853
  createTreeSitterCheck({
8654
- id: CHECK_ID15,
8854
+ id: CHECK_ID16,
8655
8855
  nameKey: "checks.open-redirect.name",
8656
8856
  descriptionKey: "checks.open-redirect.description",
8657
8857
  category: "security",
@@ -8663,7 +8863,7 @@ registry.register(
8663
8863
  remediationKey: "remediation.security.open-redirect"
8664
8864
  })
8665
8865
  );
8666
- var CHECK_ID16 = "security.unsafe-file-upload";
8866
+ var CHECK_ID17 = "security.unsafe-file-upload";
8667
8867
  var QUERIES5 = {
8668
8868
  javascript: `
8669
8869
  (call_expression
@@ -8835,7 +9035,7 @@ var QUERIES5 = {
8835
9035
  };
8836
9036
  registry.register(
8837
9037
  createTreeSitterCheck({
8838
- id: CHECK_ID16,
9038
+ id: CHECK_ID17,
8839
9039
  nameKey: "checks.unsafe-file-upload.name",
8840
9040
  descriptionKey: "checks.unsafe-file-upload.description",
8841
9041
  category: "security",
@@ -8851,7 +9051,7 @@ registry.register(
8851
9051
  }
8852
9052
  })
8853
9053
  );
8854
- var CHECK_ID17 = "security.unsafe-deserialization";
9054
+ var CHECK_ID18 = "security.unsafe-deserialization";
8855
9055
  var QUERIES6 = {
8856
9056
  javascript: `
8857
9057
  (call_expression
@@ -8988,7 +9188,7 @@ var QUERIES6 = {
8988
9188
  };
8989
9189
  registry.register(
8990
9190
  createTreeSitterCheck({
8991
- id: CHECK_ID17,
9191
+ id: CHECK_ID18,
8992
9192
  nameKey: "checks.unsafe-deserialization.name",
8993
9193
  descriptionKey: "checks.unsafe-deserialization.description",
8994
9194
  category: "security",
@@ -9006,7 +9206,7 @@ registry.register(
9006
9206
  }
9007
9207
  })
9008
9208
  );
9009
- var CHECK_ID18 = "security.hardcoded-passwords";
9209
+ var CHECK_ID19 = "security.hardcoded-passwords";
9010
9210
  var PASSWORD_NAME_REGEX = /^(\$)?(password|pwd|passwd|secret|pass)$/i;
9011
9211
  var REMEDIATION13 = getCopy("remediation.security.hardcoded-passwords");
9012
9212
  var HTML_CSS_PASSWORD_PATTERNS = [
@@ -9087,7 +9287,7 @@ var QUERIES7 = {
9087
9287
  dockerfile: "((identifier) @name) @match"
9088
9288
  };
9089
9289
  var regexCheck4 = {
9090
- id: CHECK_ID18,
9290
+ id: CHECK_ID19,
9091
9291
  name: getCopy("checks.hardcoded-passwords.name"),
9092
9292
  description: getCopy("checks.hardcoded-passwords.description"),
9093
9293
  category: "security",
@@ -9112,8 +9312,8 @@ var regexCheck4 = {
9112
9312
  const value = match[2] ?? match[1] ?? "";
9113
9313
  if (match[2] === void 0 && line.toLowerCase().includes('type="password"')) {
9114
9314
  findings.push({
9115
- id: `${CHECK_ID18}:${file.relativePath}:${lineNum + 1}:${(match.index ?? 0) + 1}:${matchIdx}`,
9116
- checkId: CHECK_ID18,
9315
+ id: `${CHECK_ID19}:${file.relativePath}:${lineNum + 1}:${(match.index ?? 0) + 1}:${matchIdx}`,
9316
+ checkId: CHECK_ID19,
9117
9317
  category: "security",
9118
9318
  severity: "critical",
9119
9319
  message: getCopy("checks.hardcoded-passwords.message", "password"),
@@ -9128,8 +9328,8 @@ var regexCheck4 = {
9128
9328
  if (!PASSWORD_NAME_REGEX.test(name)) continue;
9129
9329
  if ((value ?? "").trim().length === 0) continue;
9130
9330
  findings.push({
9131
- id: `${CHECK_ID18}:${file.relativePath}:${lineNum + 1}:${(match.index ?? 0) + 1}:${matchIdx}`,
9132
- checkId: CHECK_ID18,
9331
+ id: `${CHECK_ID19}:${file.relativePath}:${lineNum + 1}:${(match.index ?? 0) + 1}:${matchIdx}`,
9332
+ checkId: CHECK_ID19,
9133
9333
  category: "security",
9134
9334
  severity: "critical",
9135
9335
  message: getCopy("checks.hardcoded-passwords.message", name),
@@ -9147,7 +9347,7 @@ var regexCheck4 = {
9147
9347
  }
9148
9348
  };
9149
9349
  var treeSitterCheck = createTreeSitterCheck({
9150
- id: CHECK_ID18,
9350
+ id: CHECK_ID19,
9151
9351
  nameKey: "checks.hardcoded-passwords.name",
9152
9352
  descriptionKey: "checks.hardcoded-passwords.description",
9153
9353
  category: "security",
@@ -9343,7 +9543,7 @@ function isFullHtmlDocument(content) {
9343
9543
  function hasHeadElement(content) {
9344
9544
  return /<head\b/i.test(content);
9345
9545
  }
9346
- var CHECK_ID19 = "security.html-missing-csp";
9546
+ var CHECK_ID20 = "security.html-missing-csp";
9347
9547
  var REMEDIATION14 = getCopy("remediation.security.html-missing-csp");
9348
9548
  var META_CSP_PATTERN = /<meta\b[^>]*\bhttp-equiv\s*=\s*["']content-security-policy["'][^>]*>/gi;
9349
9549
  var CSP_CONTENT_PATTERN = /\bcontent\s*=\s*(["'])([\s\S]*?)\1/i;
@@ -9351,7 +9551,7 @@ function firstHtmlAnchor(files) {
9351
9551
  return files.filter((file) => file.language === "html" && !isMechanicalCheckExcludedPath(file.relativePath)).map((file) => file.relativePath).sort()[0];
9352
9552
  }
9353
9553
  registry.register({
9354
- id: CHECK_ID19,
9554
+ id: CHECK_ID20,
9355
9555
  name: getCopy("checks.html-missing-csp.name"),
9356
9556
  description: getCopy("checks.html-missing-csp.description"),
9357
9557
  category: "security",
@@ -9381,8 +9581,8 @@ registry.register({
9381
9581
  if (/default-src\s+\*|unsafe-inline|unsafe-eval/i.test(cspValue)) {
9382
9582
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
9383
9583
  findings.push({
9384
- id: `${CHECK_ID19}:${file.relativePath}:${line}:${column}`,
9385
- checkId: CHECK_ID19,
9584
+ id: `${CHECK_ID20}:${file.relativePath}:${line}:${column}`,
9585
+ checkId: CHECK_ID20,
9386
9586
  category: "security",
9387
9587
  severity: "medium",
9388
9588
  message: getCopy("checks.html-missing-csp.message", "weak Content Security Policy"),
@@ -9399,8 +9599,8 @@ registry.register({
9399
9599
  }
9400
9600
  if (findings.length === 0 && sawFullHtmlDoc && !sawValidMetaCsp) {
9401
9601
  findings.push({
9402
- id: `${CHECK_ID19}:${anchor}:1:1`,
9403
- checkId: CHECK_ID19,
9602
+ id: `${CHECK_ID20}:${anchor}:1:1`,
9603
+ checkId: CHECK_ID20,
9404
9604
  category: "security",
9405
9605
  severity: "medium",
9406
9606
  message: getCopy("checks.html-missing-csp.message", "no Content Security Policy found"),
@@ -9414,10 +9614,10 @@ registry.register({
9414
9614
  return findings;
9415
9615
  }
9416
9616
  });
9417
- var CHECK_ID20 = "security.html-static-host-headers";
9617
+ var CHECK_ID21 = "security.html-static-host-headers";
9418
9618
  var REMEDIATION15 = getCopy("remediation.security.html-static-host-headers");
9419
9619
  registry.register({
9420
- id: CHECK_ID20,
9620
+ id: CHECK_ID21,
9421
9621
  name: getCopy("checks.html-static-host-headers.name"),
9422
9622
  description: getCopy("checks.html-static-host-headers.description"),
9423
9623
  category: "security",
@@ -9437,8 +9637,8 @@ registry.register({
9437
9637
  if (!anchor) return [];
9438
9638
  return [
9439
9639
  {
9440
- id: `${CHECK_ID20}:${anchor}:1:1`,
9441
- checkId: CHECK_ID20,
9640
+ id: `${CHECK_ID21}:${anchor}:1:1`,
9641
+ checkId: CHECK_ID21,
9442
9642
  category: "security",
9443
9643
  severity: "medium",
9444
9644
  message: getCopy("checks.html-static-host-headers.message", missing.join(", ")),
@@ -9451,7 +9651,7 @@ registry.register({
9451
9651
  ];
9452
9652
  }
9453
9653
  });
9454
- var CHECK_ID21 = "security.html-inline-unsafe-script";
9654
+ var CHECK_ID22 = "security.html-inline-unsafe-script";
9455
9655
  var REMEDIATION16 = getCopy("remediation.security.html-inline-unsafe-script");
9456
9656
  var SCRIPT_BLOCK_PATTERN2 = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
9457
9657
  var EVENT_HANDLER_PATTERN = /\son[a-z]+\s*=\s*["']([^"']+)["']/gi;
@@ -9508,7 +9708,7 @@ function findEventHandlerMatches(content) {
9508
9708
  return results;
9509
9709
  }
9510
9710
  registry.register({
9511
- id: CHECK_ID21,
9711
+ id: CHECK_ID22,
9512
9712
  name: getCopy("checks.html-inline-unsafe-script.name"),
9513
9713
  description: getCopy("checks.html-inline-unsafe-script.description"),
9514
9714
  category: "security",
@@ -9523,8 +9723,8 @@ registry.register({
9523
9723
  for (const match of findScriptMatches(content)) {
9524
9724
  const { line, column } = lineAndColumn6(content, match.index);
9525
9725
  findings.push({
9526
- id: `${CHECK_ID21}:${file.relativePath}:${line}:${column}`,
9527
- checkId: CHECK_ID21,
9726
+ id: `${CHECK_ID22}:${file.relativePath}:${line}:${column}`,
9727
+ checkId: CHECK_ID22,
9528
9728
  category: "security",
9529
9729
  severity: "high",
9530
9730
  message: getCopy("checks.html-inline-unsafe-script.message", match.text),
@@ -9538,8 +9738,8 @@ registry.register({
9538
9738
  for (const match of findEventHandlerMatches(content)) {
9539
9739
  const { line, column } = lineAndColumn6(content, match.index);
9540
9740
  findings.push({
9541
- id: `${CHECK_ID21}:${file.relativePath}:${line}:${column}:event`,
9542
- checkId: CHECK_ID21,
9741
+ id: `${CHECK_ID22}:${file.relativePath}:${line}:${column}:event`,
9742
+ checkId: CHECK_ID22,
9543
9743
  category: "security",
9544
9744
  severity: "high",
9545
9745
  message: getCopy("checks.html-inline-unsafe-script.message", match.text),
@@ -9554,10 +9754,10 @@ registry.register({
9554
9754
  return findings;
9555
9755
  }
9556
9756
  });
9557
- var CHECK_ID22 = "security.html-referrer-leak";
9757
+ var CHECK_ID23 = "security.html-referrer-leak";
9558
9758
  var REMEDIATION17 = getCopy("remediation.security.html-referrer-leak");
9559
9759
  registry.register({
9560
- id: CHECK_ID22,
9760
+ id: CHECK_ID23,
9561
9761
  name: getCopy("checks.html-referrer-leak.name"),
9562
9762
  description: getCopy("checks.html-referrer-leak.description"),
9563
9763
  category: "security",
@@ -9573,8 +9773,8 @@ registry.register({
9573
9773
  if (target.hasRel) continue;
9574
9774
  const { line, column } = lineAndColumn5(content, target.index);
9575
9775
  findings.push({
9576
- id: `${CHECK_ID22}:${file.relativePath}:${line}:${column}`,
9577
- checkId: CHECK_ID22,
9776
+ id: `${CHECK_ID23}:${file.relativePath}:${line}:${column}`,
9777
+ checkId: CHECK_ID23,
9578
9778
  category: "security",
9579
9779
  severity: "medium",
9580
9780
  message: getCopy("checks.html-referrer-leak.message", target.text),
@@ -9589,7 +9789,7 @@ registry.register({
9589
9789
  return findings;
9590
9790
  }
9591
9791
  });
9592
- var CHECK_ID23 = "security.html-css-comments-secrets";
9792
+ var CHECK_ID24 = "security.html-css-comments-secrets";
9593
9793
  var REMEDIATION18 = getCopy("remediation.security.html-css-comments-secrets");
9594
9794
  var SECRET_MARKER = /(?:api[_-]?key|firebase[_-]?config|google[_-]?api|token|secret|password|private[_-]?key|access[_-]?key)/i;
9595
9795
  var SECRET_VALUE = /(?:[:=]\s*|["'`])([A-Za-z0-9+/=_-]{8,}|sk_[A-Za-z0-9/+=_-]{8,})/i;
@@ -9604,8 +9804,8 @@ function scanPattern(content, filePath, checkPrefix, pattern, messageText, findi
9604
9804
  const value = valueMatch?.[1] ?? text22;
9605
9805
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
9606
9806
  findings.push({
9607
- id: `${CHECK_ID23}:${filePath}:${line}:${column}:${checkPrefix}`,
9608
- checkId: CHECK_ID23,
9807
+ id: `${CHECK_ID24}:${filePath}:${line}:${column}:${checkPrefix}`,
9808
+ checkId: CHECK_ID24,
9609
9809
  category: "security",
9610
9810
  severity: "critical",
9611
9811
  message: getCopy("checks.html-css-comments-secrets.message", messageText),
@@ -9618,7 +9818,7 @@ function scanPattern(content, filePath, checkPrefix, pattern, messageText, findi
9618
9818
  }
9619
9819
  }
9620
9820
  registry.register({
9621
- id: CHECK_ID23,
9821
+ id: CHECK_ID24,
9622
9822
  name: getCopy("checks.html-css-comments-secrets.name"),
9623
9823
  description: getCopy("checks.html-css-comments-secrets.description"),
9624
9824
  category: "security",
@@ -9637,7 +9837,7 @@ registry.register({
9637
9837
  return findings;
9638
9838
  }
9639
9839
  });
9640
- var CHECK_ID24 = "security.css-insecure-import";
9840
+ var CHECK_ID25 = "security.css-insecure-import";
9641
9841
  var REMEDIATION19 = getCopy("remediation.security.css-insecure-import");
9642
9842
  var HTTP_URL_PATTERN = /^http:\/\//i;
9643
9843
  var IMPORT_PATTERN = /@import\s+(?:url\(\s*)?(["']?)([^"')\s;]+)\1\s*\)?\s*;/gi;
@@ -9650,8 +9850,8 @@ function lineText(content, index) {
9650
9850
  function pushFinding3(findings, filePath, content, matchText, index) {
9651
9851
  const { line, column } = lineAndColumn5(content, index);
9652
9852
  findings.push({
9653
- id: `${CHECK_ID24}:${filePath}:${line}:${column}`,
9654
- checkId: CHECK_ID24,
9853
+ id: `${CHECK_ID25}:${filePath}:${line}:${column}`,
9854
+ checkId: CHECK_ID25,
9655
9855
  category: "security",
9656
9856
  severity: "medium",
9657
9857
  message: getCopy("checks.css-insecure-import.message", matchText),
@@ -9663,7 +9863,7 @@ function pushFinding3(findings, filePath, content, matchText, index) {
9663
9863
  });
9664
9864
  }
9665
9865
  registry.register({
9666
- id: CHECK_ID24,
9866
+ id: CHECK_ID25,
9667
9867
  name: getCopy("checks.css-insecure-import.name"),
9668
9868
  description: getCopy("checks.css-insecure-import.description"),
9669
9869
  category: "security",
@@ -9690,7 +9890,7 @@ registry.register({
9690
9890
  return findings;
9691
9891
  }
9692
9892
  });
9693
- var CHECK_ID25 = "security.html-mixed-content";
9893
+ var CHECK_ID26 = "security.html-mixed-content";
9694
9894
  var REMEDIATION20 = getCopy("remediation.security.html-mixed-content");
9695
9895
  var TAG_RESOURCE_PATTERN = /<(script|link|iframe|img|form|object|embed)\b[^>]*?\b(src|href|action|data)\s*=\s*["'](http:\/\/[^"']+)["'][^>]*?>/gi;
9696
9896
  var STYLE_ATTR_PATTERN = /\bstyle\s*=\s*["']([\s\S]*?http:\/\/[\s\S]*?)["']/gi;
@@ -9699,8 +9899,8 @@ var CSS_URL_PATTERN = /url\(\s*(["']?)(http:\/\/[^"')]+)\1\s*\)/gi;
9699
9899
  function pushFinding4(findings, filePath, content, index, matchText) {
9700
9900
  const { line, column } = lineAndColumn5(content, index);
9701
9901
  findings.push({
9702
- id: `${CHECK_ID25}:${filePath}:${line}:${column}`,
9703
- checkId: CHECK_ID25,
9902
+ id: `${CHECK_ID26}:${filePath}:${line}:${column}`,
9903
+ checkId: CHECK_ID26,
9704
9904
  category: "security",
9705
9905
  severity: "medium",
9706
9906
  message: getCopy("checks.html-mixed-content.message", matchText),
@@ -9712,7 +9912,7 @@ function pushFinding4(findings, filePath, content, index, matchText) {
9712
9912
  });
9713
9913
  }
9714
9914
  registry.register({
9715
- id: CHECK_ID25,
9915
+ id: CHECK_ID26,
9716
9916
  name: getCopy("checks.html-mixed-content.name"),
9717
9917
  description: getCopy("checks.html-mixed-content.description"),
9718
9918
  category: "security",
@@ -9750,7 +9950,7 @@ registry.register({
9750
9950
  return findings;
9751
9951
  }
9752
9952
  });
9753
- var CHECK_ID26 = "security.html-external-asset-integrity";
9953
+ var CHECK_ID27 = "security.html-external-asset-integrity";
9754
9954
  var REMEDIATION21 = getCopy("remediation.security.html-external-asset-integrity");
9755
9955
  var SCRIPT_PATTERN = /<script\b[^>]*?\bsrc\s*=\s*["'](https:\/\/[^"']+)["'][^>]*?>/gi;
9756
9956
  var STYLESHEET_PATTERN = /<link\b(?=[^>]*\brel\s*=\s*["'][^"']*\bstylesheet\b[^"']*["'])[^>]*?\bhref\s*=\s*["'](https:\/\/[^"']+)["'][^>]*?>/gi;
@@ -9765,7 +9965,7 @@ function isLocalDevelopmentUrl(rawUrl) {
9765
9965
  }
9766
9966
  }
9767
9967
  registry.register({
9768
- id: CHECK_ID26,
9968
+ id: CHECK_ID27,
9769
9969
  name: getCopy("checks.html-external-asset-integrity.name"),
9770
9970
  description: getCopy("checks.html-external-asset-integrity.description"),
9771
9971
  category: "security",
@@ -9786,8 +9986,8 @@ registry.register({
9786
9986
  if (hasIntegrity && hasCrossorigin) continue;
9787
9987
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
9788
9988
  findings.push({
9789
- id: `${CHECK_ID26}:${file.relativePath}:${line}:${column}`,
9790
- checkId: CHECK_ID26,
9989
+ id: `${CHECK_ID27}:${file.relativePath}:${line}:${column}`,
9990
+ checkId: CHECK_ID27,
9791
9991
  category: "security",
9792
9992
  severity: "medium",
9793
9993
  message: getCopy("checks.html-external-asset-integrity.message", tagText),
@@ -9807,8 +10007,8 @@ registry.register({
9807
10007
  if (hasIntegrity && hasCrossorigin) continue;
9808
10008
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
9809
10009
  findings.push({
9810
- id: `${CHECK_ID26}:${file.relativePath}:${line}:${column}`,
9811
- checkId: CHECK_ID26,
10010
+ id: `${CHECK_ID27}:${file.relativePath}:${line}:${column}`,
10011
+ checkId: CHECK_ID27,
9812
10012
  category: "security",
9813
10013
  severity: "medium",
9814
10014
  message: getCopy("checks.html-external-asset-integrity.message", tagText),
@@ -9823,7 +10023,7 @@ registry.register({
9823
10023
  return findings;
9824
10024
  }
9825
10025
  });
9826
- var CHECK_ID27 = "security.html-insecure-form-action";
10026
+ var CHECK_ID28 = "security.html-insecure-form-action";
9827
10027
  var REMEDIATION22 = getCopy("remediation.security.html-insecure-form-action");
9828
10028
  var FORM_ACTION_PATTERN = /<form\b[^>]*?\baction\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*?>/gi;
9829
10029
  var LOCAL_DEV_URL_PATTERN = /^(?:https?:)?\/\/(?:localhost|127\.0\.0\.1|\[::1\]|::1)(?::\d+)?(?:\/|$)/i;
@@ -9837,7 +10037,7 @@ function lineAndColumn7(content, index) {
9837
10037
  };
9838
10038
  }
9839
10039
  registry.register({
9840
- id: CHECK_ID27,
10040
+ id: CHECK_ID28,
9841
10041
  name: getCopy("checks.html-insecure-form-action.name"),
9842
10042
  description: getCopy("checks.html-insecure-form-action.description"),
9843
10043
  category: "security",
@@ -9855,8 +10055,8 @@ registry.register({
9855
10055
  if (LOCAL_DEV_URL_PATTERN.test(action)) continue;
9856
10056
  const { line, column } = lineAndColumn7(content, match.index ?? 0);
9857
10057
  findings.push({
9858
- id: `${CHECK_ID27}:${file.relativePath}:${line}:${column}`,
9859
- checkId: CHECK_ID27,
10058
+ id: `${CHECK_ID28}:${file.relativePath}:${line}:${column}`,
10059
+ checkId: CHECK_ID28,
9860
10060
  category: "security",
9861
10061
  severity: "medium",
9862
10062
  message: getCopy("checks.html-insecure-form-action.message", match[0] ?? action),
@@ -9871,7 +10071,7 @@ registry.register({
9871
10071
  return findings;
9872
10072
  }
9873
10073
  });
9874
- var CHECK_ID28 = "security.html-iframe-sandbox";
10074
+ var CHECK_ID29 = "security.html-iframe-sandbox";
9875
10075
  var REMEDIATION23 = getCopy("remediation.security.html-iframe-sandbox");
9876
10076
  var IFRAME_PATTERN = /<iframe\b[^>]*?>/gi;
9877
10077
  var SRC_PATTERN = /\bsrc\s*=\s*["']([^"']+)["']/i;
@@ -9886,7 +10086,7 @@ function hasSandboxAttribute(tagText) {
9886
10086
  return { present: true, value: match[1] ?? match[2] ?? match[3] ?? "" };
9887
10087
  }
9888
10088
  registry.register({
9889
- id: CHECK_ID28,
10089
+ id: CHECK_ID29,
9890
10090
  name: getCopy("checks.html-iframe-sandbox.name"),
9891
10091
  description: getCopy("checks.html-iframe-sandbox.description"),
9892
10092
  category: "security",
@@ -9909,8 +10109,8 @@ registry.register({
9909
10109
  if (sandbox.present && !hasBroadEscape) continue;
9910
10110
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
9911
10111
  findings.push({
9912
- id: `${CHECK_ID28}:${file.relativePath}:${line}:${column}`,
9913
- checkId: CHECK_ID28,
10112
+ id: `${CHECK_ID29}:${file.relativePath}:${line}:${column}`,
10113
+ checkId: CHECK_ID29,
9914
10114
  category: "security",
9915
10115
  severity: "medium",
9916
10116
  message: getCopy("checks.html-iframe-sandbox.message", tagText),
@@ -9925,7 +10125,7 @@ registry.register({
9925
10125
  return findings;
9926
10126
  }
9927
10127
  });
9928
- var CHECK_ID29 = "security.html-unsafe-embed";
10128
+ var CHECK_ID30 = "security.html-unsafe-embed";
9929
10129
  var REMEDIATION24 = getCopy("remediation.security.html-unsafe-embed");
9930
10130
  var OBJECT_PATTERN = /<object\b[^>]*?\bdata\s*=\s*["'](https:\/\/[^"']+)["'][^>]*?>/gi;
9931
10131
  var EMBED_PATTERN = /<embed\b[^>]*?\bsrc\s*=\s*["'](https:\/\/[^"']+)["'][^>]*?>/gi;
@@ -9946,8 +10146,8 @@ function getTypeValue(tagText) {
9946
10146
  function pushFindings(findings, filePath, content, index, tagText) {
9947
10147
  const { line, column } = lineAndColumn8(content, index);
9948
10148
  findings.push({
9949
- id: `${CHECK_ID29}:${filePath}:${line}:${column}`,
9950
- checkId: CHECK_ID29,
10149
+ id: `${CHECK_ID30}:${filePath}:${line}:${column}`,
10150
+ checkId: CHECK_ID30,
9951
10151
  category: "security",
9952
10152
  severity: "medium",
9953
10153
  message: getCopy("checks.html-unsafe-embed.message", tagText),
@@ -9959,7 +10159,7 @@ function pushFindings(findings, filePath, content, index, tagText) {
9959
10159
  });
9960
10160
  }
9961
10161
  registry.register({
9962
- id: CHECK_ID29,
10162
+ id: CHECK_ID30,
9963
10163
  name: getCopy("checks.html-unsafe-embed.name"),
9964
10164
  description: getCopy("checks.html-unsafe-embed.description"),
9965
10165
  category: "security",
@@ -9991,7 +10191,7 @@ registry.register({
9991
10191
  return findings;
9992
10192
  }
9993
10193
  });
9994
- var CHECK_ID30 = "security.html-meta-redirect";
10194
+ var CHECK_ID31 = "security.html-meta-redirect";
9995
10195
  var REMEDIATION25 = getCopy("remediation.security.html-meta-redirect");
9996
10196
  var META_REFRESH_PATTERN = /<meta\b[^>]*\bhttp-equiv\s*=\s*["']refresh["'][^>]*>/gi;
9997
10197
  var CONTENT_PATTERN = /\bcontent\s*=\s*["']([^"']+)["']/i;
@@ -10008,7 +10208,7 @@ function parseRefreshContent(value) {
10008
10208
  };
10009
10209
  }
10010
10210
  registry.register({
10011
- id: CHECK_ID30,
10211
+ id: CHECK_ID31,
10012
10212
  name: getCopy("checks.html-meta-redirect.name"),
10013
10213
  description: getCopy("checks.html-meta-redirect.description"),
10014
10214
  category: "security",
@@ -10031,8 +10231,8 @@ registry.register({
10031
10231
  if (LOCAL_DEV_URL_PATTERN4.test(parsed.url)) continue;
10032
10232
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
10033
10233
  findings.push({
10034
- id: `${CHECK_ID30}:${file.relativePath}:${line}:${column}`,
10035
- checkId: CHECK_ID30,
10234
+ id: `${CHECK_ID31}:${file.relativePath}:${line}:${column}`,
10235
+ checkId: CHECK_ID31,
10036
10236
  category: "security",
10037
10237
  severity: "medium",
10038
10238
  message: getCopy("checks.html-meta-redirect.message", tagText),
@@ -10047,7 +10247,7 @@ registry.register({
10047
10247
  return findings;
10048
10248
  }
10049
10249
  });
10050
- var CHECK_ID31 = "security.html-base-tag-hijack";
10250
+ var CHECK_ID32 = "security.html-base-tag-hijack";
10051
10251
  var REMEDIATION26 = getCopy("remediation.security.html-base-tag-hijack");
10052
10252
  var BASE_PATTERN = /<base\b[^>]*?>/gi;
10053
10253
  var HREF_PATTERN = /\bhref\s*=\s*["']([^"']+)["']/i;
@@ -10063,7 +10263,7 @@ function isOutsideHead(content, index) {
10063
10263
  return false;
10064
10264
  }
10065
10265
  registry.register({
10066
- id: CHECK_ID31,
10266
+ id: CHECK_ID32,
10067
10267
  name: getCopy("checks.html-base-tag-hijack.name"),
10068
10268
  description: getCopy("checks.html-base-tag-hijack.description"),
10069
10269
  category: "security",
@@ -10085,8 +10285,8 @@ registry.register({
10085
10285
  if (!externalHref && !targetBlank && !outsideHead) continue;
10086
10286
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
10087
10287
  findings.push({
10088
- id: `${CHECK_ID31}:${file.relativePath}:${line}:${column}`,
10089
- checkId: CHECK_ID31,
10288
+ id: `${CHECK_ID32}:${file.relativePath}:${line}:${column}`,
10289
+ checkId: CHECK_ID32,
10090
10290
  category: "security",
10091
10291
  severity: "medium",
10092
10292
  message: getCopy("checks.html-base-tag-hijack.message", tagText),
@@ -10101,12 +10301,12 @@ registry.register({
10101
10301
  return findings;
10102
10302
  }
10103
10303
  });
10104
- var CHECK_ID32 = "security.html-javascript-uri";
10304
+ var CHECK_ID33 = "security.html-javascript-uri";
10105
10305
  var REMEDIATION27 = getCopy("remediation.security.html-javascript-uri");
10106
10306
  var TAG_PATTERN = /<(a|form|iframe|object|embed)\b[^>]*?\b(href|action|src|data)\s*=\s*["'](javascript:[^"']+)["'][^>]*?>/gi;
10107
10307
  var VOID_PATTERN = /^javascript:\s*(?:void\s*\(?\s*0\s*\)?)(?:\s*;)?\s*$/i;
10108
10308
  registry.register({
10109
- id: CHECK_ID32,
10309
+ id: CHECK_ID33,
10110
10310
  name: getCopy("checks.html-javascript-uri.name"),
10111
10311
  description: getCopy("checks.html-javascript-uri.description"),
10112
10312
  category: "security",
@@ -10124,8 +10324,8 @@ registry.register({
10124
10324
  if (VOID_PATTERN.test(jsUrl)) continue;
10125
10325
  const { line, column } = lineAndColumn5(content, match.index ?? 0);
10126
10326
  findings.push({
10127
- id: `${CHECK_ID32}:${file.relativePath}:${line}:${column}`,
10128
- checkId: CHECK_ID32,
10327
+ id: `${CHECK_ID33}:${file.relativePath}:${line}:${column}`,
10328
+ checkId: CHECK_ID33,
10129
10329
  category: "security",
10130
10330
  severity: "low",
10131
10331
  message: getCopy("checks.html-javascript-uri.message", tagText),
@@ -10140,7 +10340,7 @@ registry.register({
10140
10340
  return findings;
10141
10341
  }
10142
10342
  });
10143
- var CHECK_ID33 = "security.html-import-map-integrity";
10343
+ var CHECK_ID34 = "security.html-import-map-integrity";
10144
10344
  var REMEDIATION28 = getCopy("remediation.security.html-import-map-integrity");
10145
10345
  var IMPORTMAP_SCRIPT_PATTERN = /<script\b[^>]*\btype\s*=\s*["']importmap["'][^>]*>([\s\S]*?)<\/script>/gi;
10146
10346
  var IMPORTMAP_SRC_PATTERN = /<script\b[^>]*\btype\s*=\s*["']importmap["'][^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/gi;
@@ -10161,8 +10361,8 @@ function isExternalUrl(value) {
10161
10361
  function pushFinding5(findings, filePath, content, index, matchText) {
10162
10362
  const { line, column } = lineAndColumn9(content, index);
10163
10363
  findings.push({
10164
- id: `${CHECK_ID33}:${filePath}:${line}:${column}`,
10165
- checkId: CHECK_ID33,
10364
+ id: `${CHECK_ID34}:${filePath}:${line}:${column}`,
10365
+ checkId: CHECK_ID34,
10166
10366
  category: "security",
10167
10367
  severity: "medium",
10168
10368
  message: getCopy("checks.html-import-map-integrity.message", matchText),
@@ -10187,7 +10387,7 @@ function scanImportMapValue(findings, filePath, content, value, contextLabel, in
10187
10387
  }
10188
10388
  }
10189
10389
  registry.register({
10190
- id: CHECK_ID33,
10390
+ id: CHECK_ID34,
10191
10391
  name: getCopy("checks.html-import-map-integrity.name"),
10192
10392
  description: getCopy("checks.html-import-map-integrity.description"),
10193
10393
  category: "security",
@@ -10220,7 +10420,7 @@ registry.register({
10220
10420
  return findings;
10221
10421
  }
10222
10422
  });
10223
- var CHECK_ID34 = "security.css-data-exfiltration";
10423
+ var CHECK_ID35 = "security.css-data-exfiltration";
10224
10424
  var REMEDIATION29 = getCopy("remediation.security.css-data-exfiltration");
10225
10425
  var ATTR_SELECTOR_PATTERN = /\[[^\]]*(?:value|type|name|email|tel|address|cc-number|cc-exp)[^\]]*(?:\^=|\$=|\*=|~=|\|=)[^\]]*\]/i;
10226
10426
  var EXTERNAL_HTTP_URL_PATTERN = /url\(\s*(["']?)http:\/\/[^"')]+\1\s*\)/i;
@@ -10238,8 +10438,8 @@ function lineAndColumn10(content, index) {
10238
10438
  function pushFinding6(findings, filePath, content, index, matchText) {
10239
10439
  const { line, column } = lineAndColumn10(content, index);
10240
10440
  findings.push({
10241
- id: `${CHECK_ID34}:${filePath}:${line}:${column}`,
10242
- checkId: CHECK_ID34,
10441
+ id: `${CHECK_ID35}:${filePath}:${line}:${column}`,
10442
+ checkId: CHECK_ID35,
10243
10443
  category: "security",
10244
10444
  severity: "medium",
10245
10445
  message: getCopy("checks.css-data-exfiltration.message", matchText),
@@ -10260,7 +10460,7 @@ function scanCss(findings, filePath, scanContent, originalContent, offset = 0) {
10260
10460
  }
10261
10461
  }
10262
10462
  registry.register({
10263
- id: CHECK_ID34,
10463
+ id: CHECK_ID35,
10264
10464
  name: getCopy("checks.css-data-exfiltration.name"),
10265
10465
  description: getCopy("checks.css-data-exfiltration.description"),
10266
10466
  category: "security",
@@ -10292,7 +10492,7 @@ registry.register({
10292
10492
  return findings;
10293
10493
  }
10294
10494
  });
10295
- var CHECK_ID35 = "security.css-external-font-integrity";
10495
+ var CHECK_ID36 = "security.css-external-font-integrity";
10296
10496
  var REMEDIATION30 = getCopy("remediation.security.css-external-font-integrity");
10297
10497
  var FONT_FACE_PATTERN = /@font-face\b[\s\S]*?\}/gi;
10298
10498
  var URL_PATTERN = /url\(\s*(["']?)([^"')]+)\1\s*\)/gi;
@@ -10324,7 +10524,7 @@ function isPublicCdnFont(url) {
10324
10524
  }
10325
10525
  }
10326
10526
  registry.register({
10327
- id: CHECK_ID35,
10527
+ id: CHECK_ID36,
10328
10528
  name: getCopy("checks.css-external-font-integrity.name"),
10329
10529
  description: getCopy("checks.css-external-font-integrity.description"),
10330
10530
  category: "security",
@@ -10345,8 +10545,8 @@ registry.register({
10345
10545
  if (!isPublicCdnFont(url)) continue;
10346
10546
  const { line, column } = lineAndColumn11(content, face.index ?? 0);
10347
10547
  findings.push({
10348
- id: `${CHECK_ID35}:${file.relativePath}:${line}:${column}`,
10349
- checkId: CHECK_ID35,
10548
+ id: `${CHECK_ID36}:${file.relativePath}:${line}:${column}`,
10549
+ checkId: CHECK_ID36,
10350
10550
  category: "security",
10351
10551
  severity: "medium",
10352
10552
  message: getCopy("checks.css-external-font-integrity.message", match[0] ?? url),
@@ -10362,7 +10562,7 @@ registry.register({
10362
10562
  return findings;
10363
10563
  }
10364
10564
  });
10365
- var CHECK_ID36 = "security.insecure-random";
10565
+ var CHECK_ID37 = "security.insecure-random";
10366
10566
  var QUERIES8 = {
10367
10567
  javascript: `
10368
10568
  (call_expression
@@ -10431,7 +10631,7 @@ var QUERIES8 = {
10431
10631
  };
10432
10632
  registry.register(
10433
10633
  createTreeSitterCheck({
10434
- id: CHECK_ID36,
10634
+ id: CHECK_ID37,
10435
10635
  nameKey: "checks.insecure-random.name",
10436
10636
  descriptionKey: "checks.insecure-random.description",
10437
10637
  category: "security",
@@ -10443,7 +10643,7 @@ registry.register(
10443
10643
  remediationKey: "remediation.security.insecure-random"
10444
10644
  })
10445
10645
  );
10446
- var CHECK_ID37 = "security.weak-key-entropy";
10646
+ var CHECK_ID38 = "security.weak-key-entropy";
10447
10647
  var REMEDIATION31 = getCopy("remediation.security.weak-key-entropy");
10448
10648
  var RANDOM_BYTES_PATTERN = /randomBytes\s*\(\s*(\d+)\s*\)/g;
10449
10649
  var WEAK_KEY_CONTEXT = /(?:key|token|secret|id|license|password|session|csrf|nonce|salt|iv)/i;
@@ -10456,7 +10656,7 @@ function lineAndColumn12(content, index) {
10456
10656
  };
10457
10657
  }
10458
10658
  registry.register({
10459
- id: CHECK_ID37,
10659
+ id: CHECK_ID38,
10460
10660
  name: getCopy("checks.weak-key-entropy.name"),
10461
10661
  description: getCopy("checks.weak-key-entropy.description"),
10462
10662
  category: "security",
@@ -10478,8 +10678,8 @@ registry.register({
10478
10678
  if (!WEAK_KEY_CONTEXT.test(lineContent) && !WEAK_KEY_CONTEXT.test(content.slice(Math.max(0, matchIndex - 200), matchIndex))) continue;
10479
10679
  const { line, column } = lineAndColumn12(content, matchIndex);
10480
10680
  findings.push({
10481
- id: `${CHECK_ID37}:${file.relativePath}:${line}:${column}`,
10482
- checkId: CHECK_ID37,
10681
+ id: `${CHECK_ID38}:${file.relativePath}:${line}:${column}`,
10682
+ checkId: CHECK_ID38,
10483
10683
  category: "security",
10484
10684
  severity: "high",
10485
10685
  message: getCopy("checks.weak-key-entropy.message", match[0], String(byteCount)),
@@ -10494,7 +10694,7 @@ registry.register({
10494
10694
  return findings;
10495
10695
  }
10496
10696
  });
10497
- var CHECK_ID38 = "security.insecure-api-url-default";
10697
+ var CHECK_ID39 = "security.insecure-api-url-default";
10498
10698
  var REMEDIATION32 = getCopy("remediation.security.insecure-api-url-default");
10499
10699
  var HTTP_URL_DEFAULT_PATTERN = /(?:['"`])(https?:\/\/[^\s'"`]+)(?:['"`])/g;
10500
10700
  var API_CONTEXT = /(?:api[_-]?url|base[_-]?url|endpoint|server|host|webhook|resend|paddle|stripe|sendgrid|mailgun|postmark)/i;
@@ -10509,7 +10709,7 @@ function lineAndColumn13(content, index) {
10509
10709
  };
10510
10710
  }
10511
10711
  registry.register({
10512
- id: CHECK_ID38,
10712
+ id: CHECK_ID39,
10513
10713
  name: getCopy("checks.insecure-api-url-default.name"),
10514
10714
  description: getCopy("checks.insecure-api-url-default.description"),
10515
10715
  category: "security",
@@ -10534,8 +10734,8 @@ registry.register({
10534
10734
  if (!isEnvDefault && !API_CONTEXT.test(matchLine)) continue;
10535
10735
  const { line, column } = lineAndColumn13(content, matchIndex);
10536
10736
  findings.push({
10537
- id: `${CHECK_ID38}:${file.relativePath}:${line}:${column}`,
10538
- checkId: CHECK_ID38,
10737
+ id: `${CHECK_ID39}:${file.relativePath}:${line}:${column}`,
10738
+ checkId: CHECK_ID39,
10539
10739
  category: "security",
10540
10740
  severity: "high",
10541
10741
  message: getCopy("checks.insecure-api-url-default.message", url),
@@ -10550,7 +10750,7 @@ registry.register({
10550
10750
  return findings;
10551
10751
  }
10552
10752
  });
10553
- var CHECK_ID39 = "security.dynamic-sql-columns";
10753
+ var CHECK_ID40 = "security.dynamic-sql-columns";
10554
10754
  var REMEDIATION33 = getCopy("remediation.security.dynamic-sql-columns");
10555
10755
  var SQL_SET_INTERPOLATION = /\bSET\s+.*\$\{[^}]*\}/gis;
10556
10756
  var SQL_SET_TEMPLATE = /\bSET\s+.*`[^`]*\$\{[^}]*\}[^`]*`/gis;
@@ -10564,7 +10764,7 @@ function lineAndColumn14(content, index) {
10564
10764
  };
10565
10765
  }
10566
10766
  registry.register({
10567
- id: CHECK_ID39,
10767
+ id: CHECK_ID40,
10568
10768
  name: getCopy("checks.dynamic-sql-columns.name"),
10569
10769
  description: getCopy("checks.dynamic-sql-columns.description"),
10570
10770
  category: "security",
@@ -10585,11 +10785,11 @@ registry.register({
10585
10785
  for (const match of content.matchAll(regex)) {
10586
10786
  const matchIndex = match.index ?? 0;
10587
10787
  const { line, column } = lineAndColumn14(content, matchIndex);
10588
- const dedupKey = `${CHECK_ID39}:${file.relativePath}:${line}`;
10788
+ const dedupKey = `${CHECK_ID40}:${file.relativePath}:${line}`;
10589
10789
  if (findings.some((f) => f.id === dedupKey)) continue;
10590
10790
  findings.push({
10591
10791
  id: dedupKey,
10592
- checkId: CHECK_ID39,
10792
+ checkId: CHECK_ID40,
10593
10793
  category: "security",
10594
10794
  severity: "medium",
10595
10795
  message: getCopy("checks.dynamic-sql-columns.message", label),
@@ -10605,7 +10805,7 @@ registry.register({
10605
10805
  return findings;
10606
10806
  }
10607
10807
  });
10608
- var CHECK_ID40 = "security.weak-email-validation";
10808
+ var CHECK_ID41 = "security.weak-email-validation";
10609
10809
  var REMEDIATION34 = getCopy("remediation.security.weak-email-validation");
10610
10810
  var WEAK_EMAIL_REGEX_LINE = /\/\^[^/]*@[^/]*\\\.[^/]{0,4}\$\/[gimsuy]*/g;
10611
10811
  var LOOSE_REGEX_CLASS = /\[\^?\\?s@\]\+@\[\^?\\?s@\]\+\\\.\[\^?\\?s@\]\+/g;
@@ -10619,7 +10819,7 @@ function lineAndColumn15(content, index) {
10619
10819
  };
10620
10820
  }
10621
10821
  registry.register({
10622
- id: CHECK_ID40,
10822
+ id: CHECK_ID41,
10623
10823
  name: getCopy("checks.weak-email-validation.name"),
10624
10824
  description: getCopy("checks.weak-email-validation.description"),
10625
10825
  category: "security",
@@ -10640,11 +10840,11 @@ registry.register({
10640
10840
  for (const match of content.matchAll(regex)) {
10641
10841
  const matchIndex = match.index ?? 0;
10642
10842
  const { line, column } = lineAndColumn15(content, matchIndex);
10643
- const dedupKey = `${CHECK_ID40}:${file.relativePath}:${line}`;
10843
+ const dedupKey = `${CHECK_ID41}:${file.relativePath}:${line}`;
10644
10844
  if (findings.some((f) => f.id === dedupKey)) continue;
10645
10845
  findings.push({
10646
10846
  id: dedupKey,
10647
- checkId: CHECK_ID40,
10847
+ checkId: CHECK_ID41,
10648
10848
  category: "security",
10649
10849
  severity: "medium",
10650
10850
  message: getCopy("checks.weak-email-validation.message", `${match[0]} (${label})`),
@@ -10660,7 +10860,7 @@ registry.register({
10660
10860
  return findings;
10661
10861
  }
10662
10862
  });
10663
- var CHECK_ID41 = "security.idempotency-key-leak";
10863
+ var CHECK_ID42 = "security.idempotency-key-leak";
10664
10864
  var REMEDIATION35 = getCopy("remediation.security.idempotency-key-leak");
10665
10865
  var IDEMPOTENCY_CONTEXT = /idempotency[_-]?key|idempotency[_-]?token/i;
10666
10866
  var TEMPLATE_LITERAL_INTERPOLATION = new RegExp("`[^`]*\\$\\{[^}]+\\}[^`]*`", "g");
@@ -10674,7 +10874,7 @@ function lineAndColumn16(content, index) {
10674
10874
  };
10675
10875
  }
10676
10876
  registry.register({
10677
- id: CHECK_ID41,
10877
+ id: CHECK_ID42,
10678
10878
  name: getCopy("checks.idempotency-key-leak.name"),
10679
10879
  description: getCopy("checks.idempotency-key-leak.description"),
10680
10880
  category: "security",
@@ -10693,11 +10893,11 @@ registry.register({
10693
10893
  if (!STRUCTURED_ID_PATTERN.test(templateStr)) continue;
10694
10894
  const matchIndex = match.index ?? 0;
10695
10895
  const { line, column } = lineAndColumn16(content, matchIndex);
10696
- const dedupKey = `${CHECK_ID41}:${file.relativePath}:${line}`;
10896
+ const dedupKey = `${CHECK_ID42}:${file.relativePath}:${line}`;
10697
10897
  if (findings.some((f) => f.id === dedupKey)) continue;
10698
10898
  findings.push({
10699
10899
  id: dedupKey,
10700
- checkId: CHECK_ID41,
10900
+ checkId: CHECK_ID42,
10701
10901
  category: "security",
10702
10902
  severity: "medium",
10703
10903
  message: getCopy("checks.idempotency-key-leak.message", templateStr.slice(0, 80)),
@@ -10712,7 +10912,7 @@ registry.register({
10712
10912
  return findings;
10713
10913
  }
10714
10914
  });
10715
- var CHECK_ID42 = "data-exposure.plaintext-secret-in-email";
10915
+ var CHECK_ID43 = "data-exposure.plaintext-secret-in-email";
10716
10916
  var REMEDIATION36 = getCopy("remediation.data-exposure.plaintext-secret-in-email");
10717
10917
  var EMAIL_FILE_PATTERN = /email|mail|notify|notification/i;
10718
10918
  var SECRET_IN_TEXT_PATTERN = /(?:license[_-]?key|api[_-]?key|secret[_-]?key|token|password|auth[_-]?code|verification[_-]?code)\b/i;
@@ -10726,7 +10926,7 @@ function lineAndColumn17(content, index) {
10726
10926
  };
10727
10927
  }
10728
10928
  registry.register({
10729
- id: CHECK_ID42,
10929
+ id: CHECK_ID43,
10730
10930
  name: getCopy("checks.plaintext-secret-in-email.name"),
10731
10931
  description: getCopy("checks.plaintext-secret-in-email.description"),
10732
10932
  category: "data-exposure",
@@ -10748,8 +10948,8 @@ registry.register({
10748
10948
  const { line: lineNum, column } = lineAndColumn17(content, lineIndex);
10749
10949
  const secretMatch = SECRET_IN_TEXT_PATTERN.exec(lineText2);
10750
10950
  findings.push({
10751
- id: `${CHECK_ID42}:${file.relativePath}:${lineNum}:${column}`,
10752
- checkId: CHECK_ID42,
10951
+ id: `${CHECK_ID43}:${file.relativePath}:${lineNum}:${column}`,
10952
+ checkId: CHECK_ID43,
10753
10953
  category: "data-exposure",
10754
10954
  severity: "medium",
10755
10955
  message: getCopy("checks.plaintext-secret-in-email.message", secretMatch?.[0] ?? "secret"),
@@ -12959,7 +13159,7 @@ registry.register(
12959
13159
  attribution: "CWE-94"
12960
13160
  })
12961
13161
  );
12962
- var CHECK_ID43 = "security.missing-csrf-protection";
13162
+ var CHECK_ID44 = "security.missing-csrf-protection";
12963
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;
12964
13164
  var QUERIES9 = {
12965
13165
  php: `
@@ -13016,7 +13216,7 @@ async function hasProjectCsrfProtection(files) {
13016
13216
  return false;
13017
13217
  }
13018
13218
  var check = {
13019
- id: CHECK_ID43,
13219
+ id: CHECK_ID44,
13020
13220
  name: resolveCopy3("checks.missing-csrf-protection.name"),
13021
13221
  description: resolveCopy3("checks.missing-csrf-protection.description"),
13022
13222
  category: "security",
@@ -13056,8 +13256,8 @@ var check = {
13056
13256
  const sample = eligible.find((f) => f.language === lang);
13057
13257
  if (!sample) continue;
13058
13258
  findings.push({
13059
- id: `${CHECK_ID43}:${sample.relativePath}:degraded:${lang}`,
13060
- checkId: CHECK_ID43,
13259
+ id: `${CHECK_ID44}:${sample.relativePath}:degraded:${lang}`,
13260
+ checkId: CHECK_ID44,
13061
13261
  category: "security",
13062
13262
  severity: "info",
13063
13263
  message: resolveCopy3("checks.missing-csrf-protection.degraded", sample.relativePath, lang),
@@ -13078,8 +13278,8 @@ var check = {
13078
13278
  handlerCount > 1 ? `${handlerCount} state-changing handlers (e.g. ${exampleRoute})` : exampleRoute
13079
13279
  );
13080
13280
  const finding = {
13081
- id: `${CHECK_ID43}:project:${anchor.relativePath}:1`,
13082
- checkId: CHECK_ID43,
13281
+ id: `${CHECK_ID44}:project:${anchor.relativePath}:1`,
13282
+ checkId: CHECK_ID44,
13083
13283
  category: "security",
13084
13284
  severity: "medium",
13085
13285
  message,
@@ -13094,7 +13294,7 @@ var check = {
13094
13294
  }
13095
13295
  };
13096
13296
  registry.register(check);
13097
- var CHECK_ID44 = "security.weak-session-id";
13297
+ var CHECK_ID45 = "security.weak-session-id";
13098
13298
  var QUERIES10 = {
13099
13299
  php: `
13100
13300
  (function_call_expression
@@ -13181,7 +13381,7 @@ var QUERIES10 = {
13181
13381
  };
13182
13382
  registry.register(
13183
13383
  createTreeSitterCheck({
13184
- id: CHECK_ID44,
13384
+ id: CHECK_ID45,
13185
13385
  nameKey: "checks.weak-session-id.name",
13186
13386
  descriptionKey: "checks.weak-session-id.description",
13187
13387
  category: "security",
@@ -13199,7 +13399,7 @@ registry.register(
13199
13399
  }
13200
13400
  })
13201
13401
  );
13202
- var CHECK_ID45 = "security.rails-mass-assignment";
13402
+ var CHECK_ID46 = "security.rails-mass-assignment";
13203
13403
  var QUERIES11 = {
13204
13404
  ruby: `
13205
13405
  (call
@@ -13222,7 +13422,7 @@ var QUERIES11 = {
13222
13422
  };
13223
13423
  registry.register(
13224
13424
  createTreeSitterCheck({
13225
- id: CHECK_ID45,
13425
+ id: CHECK_ID46,
13226
13426
  nameKey: "checks.rails-mass-assignment.name",
13227
13427
  descriptionKey: "checks.rails-mass-assignment.description",
13228
13428
  category: "security",
@@ -13234,7 +13434,7 @@ registry.register(
13234
13434
  remediationKey: "remediation.security.rails-mass-assignment"
13235
13435
  })
13236
13436
  );
13237
- var CHECK_ID46 = "security.rails-insecure-session-config";
13437
+ var CHECK_ID47 = "security.rails-insecure-session-config";
13238
13438
  var REMEDIATION37 = getCopy("remediation.security.rails-insecure-session-config");
13239
13439
  var SESSION_STORE_PATH = /config\/initializers\/session_store\.rb$/;
13240
13440
  function hasSecureFlag(content) {
@@ -13247,7 +13447,7 @@ function hasSameSiteFlag(content) {
13247
13447
  return /\bsame_site:\s*:(strict|lax)\b/.test(content);
13248
13448
  }
13249
13449
  registry.register({
13250
- id: CHECK_ID46,
13450
+ id: CHECK_ID47,
13251
13451
  name: getCopy("checks.rails-insecure-session-config.name"),
13252
13452
  description: getCopy("checks.rails-insecure-session-config.description"),
13253
13453
  category: "security",
@@ -13267,8 +13467,8 @@ registry.register({
13267
13467
  if (!hasSameSiteFlag(content)) missing.push("same_site: :strict or :lax");
13268
13468
  if (missing.length > 0) {
13269
13469
  findings.push({
13270
- id: `${CHECK_ID46}:${file.relativePath}:1`,
13271
- checkId: CHECK_ID46,
13470
+ id: `${CHECK_ID47}:${file.relativePath}:1`,
13471
+ checkId: CHECK_ID47,
13272
13472
  category: "security",
13273
13473
  severity: "medium",
13274
13474
  message: getCopy(
@@ -13285,7 +13485,7 @@ registry.register({
13285
13485
  return findings;
13286
13486
  }
13287
13487
  });
13288
- var CHECK_ID47 = "security.xxe-surface";
13488
+ var CHECK_ID48 = "security.xxe-surface";
13289
13489
  var LIBXML_PARSE_QUERY = `
13290
13490
  (call_expression
13291
13491
  function: (member_expression
@@ -13332,7 +13532,7 @@ var QUERIES12 = {
13332
13532
  };
13333
13533
  registry.register(
13334
13534
  createTreeSitterCheck({
13335
- id: CHECK_ID47,
13535
+ id: CHECK_ID48,
13336
13536
  nameKey: "checks.xxe-surface.name",
13337
13537
  descriptionKey: "checks.xxe-surface.description",
13338
13538
  category: "security",
@@ -13531,12 +13731,13 @@ registry.register({
13531
13731
  return runLLMCheck(ctx, promptTemplate22);
13532
13732
  }
13533
13733
  });
13534
- var PROMPT_VERSION23 = "1.0.0";
13734
+ var PROMPT_VERSION23 = "1.1.0";
13535
13735
  var fallbackPatterns23 = [
13536
13736
  {
13537
13737
  regex: /while\s*\(\s*(?:true|1)\s*\)|while\s+True\s*:/i,
13538
13738
  message: "Infinite loop found \u2014 verify retry logic has a maximum attempt limit",
13539
- severity: "medium"
13739
+ severity: "medium",
13740
+ skipCommentLines: true
13540
13741
  }
13541
13742
  ];
13542
13743
  async function buildPrompt23(sourceFiles) {
@@ -13598,12 +13799,13 @@ registry.register({
13598
13799
  return [...bashFindings, ...llmFindings];
13599
13800
  }
13600
13801
  });
13601
- var PROMPT_VERSION24 = "1.1.0";
13802
+ var PROMPT_VERSION24 = "1.2.0";
13602
13803
  var fallbackPatterns24 = [
13603
13804
  {
13604
13805
  regex: /console\.(?:log|error|warn)\b/,
13605
13806
  message: "Unstructured logging detected \u2014 verify observability hooks are present",
13606
- severity: "medium"
13807
+ severity: "medium",
13808
+ skipCommentLines: true
13607
13809
  }
13608
13810
  ];
13609
13811
  async function buildPrompt24(sourceFiles) {
@@ -13826,12 +14028,13 @@ registry.register({
13826
14028
  return runLLMCheck(ctx, promptTemplate27);
13827
14029
  }
13828
14030
  });
13829
- var PROMPT_VERSION28 = "1.0.0";
14031
+ var PROMPT_VERSION28 = "1.1.0";
13830
14032
  var fallbackPatterns28 = [
13831
14033
  {
13832
14034
  regex: /\b(?:fingerprint|deviceId|visitorId|trackingId|correlationId|analyticsId)\b/i,
13833
14035
  message: "Derived identifier may link records across sessions or users",
13834
- severity: "medium"
14036
+ severity: "medium",
14037
+ skipCommentLines: true
13835
14038
  },
13836
14039
  {
13837
14040
  regex: /\bhash\s*\(\s*(?:email|phone|userId|id)\s*\)/i,
@@ -13865,15 +14068,64 @@ var promptTemplate28 = {
13865
14068
  parseResponse: parseResponse28,
13866
14069
  fallbackPatterns: fallbackPatterns28
13867
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
+ });
13868
14116
  registry.register({
13869
- id: "data-exposure.derived-identifiers",
14117
+ id: CHECK_ID49,
13870
14118
  name: getCopy("checks.derived-identifiers.name"),
13871
14119
  description: getCopy("checks.derived-identifiers.description"),
13872
14120
  category: "data-exposure",
13873
14121
  severity: "medium",
13874
14122
  minTier: "pro",
13875
14123
  async run(ctx) {
13876
- 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);
13877
14129
  }
13878
14130
  });
13879
14131
  var PROMPT_VERSION29 = "1.0.0";
@@ -13939,12 +14191,12 @@ registry.register({
13939
14191
  return [...bashFindings, ...llmFindings];
13940
14192
  }
13941
14193
  });
13942
- var CHECK_ID48 = "security.permissive-grants";
14194
+ var CHECK_ID50 = "security.permissive-grants";
13943
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;
13944
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;
13945
14197
  var REMEDIATION38 = getCopy("remediation.security.permissive-grants");
13946
14198
  var permissiveGrantsCheck = {
13947
- id: CHECK_ID48,
14199
+ id: CHECK_ID50,
13948
14200
  name: getCopy("checks.permissive-grants.name"),
13949
14201
  description: getCopy("checks.permissive-grants.description"),
13950
14202
  category: "security",
@@ -13961,8 +14213,8 @@ var permissiveGrantsCheck = {
13961
14213
  const beforeMatch = content.slice(0, match.index);
13962
14214
  const lineNum = beforeMatch.split("\n").length;
13963
14215
  findings.push({
13964
- id: `${CHECK_ID48}:${file.relativePath}:${lineNum}:1`,
13965
- checkId: CHECK_ID48,
14216
+ id: `${CHECK_ID50}:${file.relativePath}:${lineNum}:1`,
14217
+ checkId: CHECK_ID50,
13966
14218
  category: "security",
13967
14219
  severity: "high",
13968
14220
  message: getCopy("checks.permissive-grants.message", match[0].trim()),
@@ -13979,8 +14231,8 @@ var permissiveGrantsCheck = {
13979
14231
  const beforeMatch = content.slice(0, altMatch.index);
13980
14232
  const lineNum = beforeMatch.split("\n").length;
13981
14233
  findings.push({
13982
- id: `${CHECK_ID48}:${file.relativePath}:${lineNum}:1`,
13983
- checkId: CHECK_ID48,
14234
+ id: `${CHECK_ID50}:${file.relativePath}:${lineNum}:1`,
14235
+ checkId: CHECK_ID50,
13984
14236
  category: "security",
13985
14237
  severity: "high",
13986
14238
  message: getCopy("checks.permissive-grants.message", altMatch[0].trim()),
@@ -14020,10 +14272,10 @@ function classifySqlDialect(content) {
14020
14272
  }
14021
14273
  return "unknown";
14022
14274
  }
14023
- var CHECK_ID49 = "security.sql-rls-static";
14275
+ var CHECK_ID51 = "security.sql-rls-static";
14024
14276
  var REMEDIATION39 = getCopy("remediation.security.sql-rls-static");
14025
14277
  var sqlRlsStaticCheck = {
14026
- id: CHECK_ID49,
14278
+ id: CHECK_ID51,
14027
14279
  name: getCopy("checks.sql-rls-static.name"),
14028
14280
  description: getCopy("checks.sql-rls-static.description"),
14029
14281
  category: "security",
@@ -14078,8 +14330,8 @@ var sqlRlsStaticCheck = {
14078
14330
  const isRlsEnabled = enabledTables.has(table.name);
14079
14331
  if (!isRlsEnabled) {
14080
14332
  findings.push({
14081
- id: `${CHECK_ID49}:${file.relativePath}:${table.line}:1:missing-rls`,
14082
- checkId: CHECK_ID49,
14333
+ id: `${CHECK_ID51}:${file.relativePath}:${table.line}:1:missing-rls`,
14334
+ checkId: CHECK_ID51,
14083
14335
  category: "security",
14084
14336
  severity: "high",
14085
14337
  message: getCopy("checks.sql-rls-static.message", `Table "${table.name}" contains tenant columns but Row-Level Security is not enabled.`),
@@ -14093,8 +14345,8 @@ var sqlRlsStaticCheck = {
14093
14345
  const isRlsForced = forcedTables.has(table.name);
14094
14346
  if (!isRlsForced) {
14095
14347
  findings.push({
14096
- id: `${CHECK_ID49}:${file.relativePath}:${table.line}:1:missing-force-rls`,
14097
- checkId: CHECK_ID49,
14348
+ id: `${CHECK_ID51}:${file.relativePath}:${table.line}:1:missing-force-rls`,
14349
+ checkId: CHECK_ID51,
14098
14350
  category: "security",
14099
14351
  severity: "high",
14100
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.`),
@@ -14118,8 +14370,8 @@ var sqlRlsStaticCheck = {
14118
14370
  const beforeMatch = content.slice(0, policyMatch.index);
14119
14371
  const lineNum = beforeMatch.split("\n").length;
14120
14372
  findings.push({
14121
- id: `${CHECK_ID49}:${file.relativePath}:${lineNum}:1:weak-policy`,
14122
- checkId: CHECK_ID49,
14373
+ id: `${CHECK_ID51}:${file.relativePath}:${lineNum}:1:weak-policy`,
14374
+ checkId: CHECK_ID51,
14123
14375
  category: "security",
14124
14376
  severity: "high",
14125
14377
  message: getCopy("checks.sql-rls-static.message", `Policy ${policyName} on table "${tableName}" uses an unrestrictive tautology (like true or 1=1).`),
@@ -14137,10 +14389,10 @@ var sqlRlsStaticCheck = {
14137
14389
  }
14138
14390
  };
14139
14391
  registry.register(sqlRlsStaticCheck);
14140
- var CHECK_ID50 = "security.sql-security-definer-search-path";
14392
+ var CHECK_ID52 = "security.sql-security-definer-search-path";
14141
14393
  var REMEDIATION40 = getCopy("remediation.security.sql-security-definer-search-path");
14142
14394
  var sqlSecurityDefinerSearchPathCheck = {
14143
- id: CHECK_ID50,
14395
+ id: CHECK_ID52,
14144
14396
  name: getCopy("checks.sql-security-definer-search-path.name"),
14145
14397
  description: getCopy("checks.sql-security-definer-search-path.description"),
14146
14398
  category: "security",
@@ -14166,8 +14418,8 @@ var sqlSecurityDefinerSearchPathCheck = {
14166
14418
  const beforeMatch = content.slice(0, match.index);
14167
14419
  const lineNum = beforeMatch.split("\n").length;
14168
14420
  findings.push({
14169
- id: `${CHECK_ID50}:${file.relativePath}:${lineNum}:1`,
14170
- checkId: CHECK_ID50,
14421
+ id: `${CHECK_ID52}:${file.relativePath}:${lineNum}:1`,
14422
+ checkId: CHECK_ID52,
14171
14423
  category: "security",
14172
14424
  severity: "high",
14173
14425
  message: getCopy("checks.sql-security-definer-search-path.message", `Function "${functionName}" is defined as SECURITY DEFINER but is missing a locked search_path.`),
@@ -14186,7 +14438,7 @@ var sqlSecurityDefinerSearchPathCheck = {
14186
14438
  }
14187
14439
  };
14188
14440
  registry.register(sqlSecurityDefinerSearchPathCheck);
14189
- var CHECK_ID51 = "resilience.missing-transaction-guard";
14441
+ var CHECK_ID53 = "resilience.missing-transaction-guard";
14190
14442
  var REMEDIATION41 = getCopy("remediation.resilience.missing-transaction-guard");
14191
14443
  var MUTATION_KEYWORDS = [
14192
14444
  /\binsert\s+into\b/i,
@@ -14199,7 +14451,7 @@ var MUTATION_KEYWORDS = [
14199
14451
  /\bcreate\s+table\s+\w+\s+as\b/i
14200
14452
  ];
14201
14453
  var missingTransactionGuardCheck = {
14202
- id: CHECK_ID51,
14454
+ id: CHECK_ID53,
14203
14455
  name: getCopy("checks.missing-transaction-guard.name"),
14204
14456
  description: getCopy("checks.missing-transaction-guard.description"),
14205
14457
  category: "resilience",
@@ -14229,8 +14481,8 @@ var missingTransactionGuardCheck = {
14229
14481
  }
14230
14482
  if (mutationCount > 5) {
14231
14483
  findings.push({
14232
- id: `${CHECK_ID51}:${file.relativePath}:1:1`,
14233
- checkId: CHECK_ID51,
14484
+ id: `${CHECK_ID53}:${file.relativePath}:1:1`,
14485
+ checkId: CHECK_ID53,
14234
14486
  category: "resilience",
14235
14487
  severity: "medium",
14236
14488
  message: getCopy("checks.missing-transaction-guard.message"),
@@ -14245,10 +14497,10 @@ var missingTransactionGuardCheck = {
14245
14497
  }
14246
14498
  };
14247
14499
  registry.register(missingTransactionGuardCheck);
14248
- var CHECK_ID52 = "resilience.destructive-sql-migration";
14500
+ var CHECK_ID54 = "resilience.destructive-sql-migration";
14249
14501
  var REMEDIATION42 = getCopy("remediation.resilience.destructive-sql-migration");
14250
14502
  var destructiveSqlMigrationCheck = {
14251
- id: CHECK_ID52,
14503
+ id: CHECK_ID54,
14252
14504
  name: getCopy("checks.destructive-sql-migration.name"),
14253
14505
  description: getCopy("checks.destructive-sql-migration.description"),
14254
14506
  category: "resilience",
@@ -14269,8 +14521,8 @@ var destructiveSqlMigrationCheck = {
14269
14521
  const highMatch = highRiskRegex.exec(line);
14270
14522
  if (highMatch) {
14271
14523
  findings.push({
14272
- id: `${CHECK_ID52}:${file.relativePath}:${lineNum + 1}:${(highMatch.index ?? 0) + 1}:high`,
14273
- checkId: CHECK_ID52,
14524
+ id: `${CHECK_ID54}:${file.relativePath}:${lineNum + 1}:${(highMatch.index ?? 0) + 1}:high`,
14525
+ checkId: CHECK_ID54,
14274
14526
  category: "resilience",
14275
14527
  severity: "high",
14276
14528
  message: getCopy("checks.destructive-sql-migration.message", highMatch[0]),
@@ -14283,8 +14535,8 @@ var destructiveSqlMigrationCheck = {
14283
14535
  const truncateMatch = truncateRegex.exec(line);
14284
14536
  if (truncateMatch) {
14285
14537
  findings.push({
14286
- id: `${CHECK_ID52}:${file.relativePath}:${lineNum + 1}:${(truncateMatch.index ?? 0) + 1}:high`,
14287
- checkId: CHECK_ID52,
14538
+ id: `${CHECK_ID54}:${file.relativePath}:${lineNum + 1}:${(truncateMatch.index ?? 0) + 1}:high`,
14539
+ checkId: CHECK_ID54,
14288
14540
  category: "resilience",
14289
14541
  severity: "high",
14290
14542
  message: getCopy("checks.destructive-sql-migration.message", truncateMatch[0]),
@@ -14300,8 +14552,8 @@ var destructiveSqlMigrationCheck = {
14300
14552
  const colMatch = dropColumnRegex.exec(line);
14301
14553
  if (colMatch) {
14302
14554
  findings.push({
14303
- id: `${CHECK_ID52}:${file.relativePath}:${lineNum + 1}:${(colMatch.index ?? 0) + 1}:col`,
14304
- checkId: CHECK_ID52,
14555
+ id: `${CHECK_ID54}:${file.relativePath}:${lineNum + 1}:${(colMatch.index ?? 0) + 1}:col`,
14556
+ checkId: CHECK_ID54,
14305
14557
  category: "resilience",
14306
14558
  severity: "medium",
14307
14559
  message: getCopy("checks.destructive-sql-migration.message", colMatch[0]),
@@ -14314,8 +14566,8 @@ var destructiveSqlMigrationCheck = {
14314
14566
  const policyMatch = dropPolicyRegex.exec(line);
14315
14567
  if (policyMatch) {
14316
14568
  findings.push({
14317
- id: `${CHECK_ID52}:${file.relativePath}:${lineNum + 1}:${(policyMatch.index ?? 0) + 1}:policy`,
14318
- checkId: CHECK_ID52,
14569
+ id: `${CHECK_ID54}:${file.relativePath}:${lineNum + 1}:${(policyMatch.index ?? 0) + 1}:policy`,
14570
+ checkId: CHECK_ID54,
14319
14571
  category: "resilience",
14320
14572
  severity: "medium",
14321
14573
  message: getCopy("checks.destructive-sql-migration.message", policyMatch[0]),
@@ -14328,8 +14580,8 @@ var destructiveSqlMigrationCheck = {
14328
14580
  const indexMatch = dropIndexRegex.exec(line);
14329
14581
  if (indexMatch) {
14330
14582
  findings.push({
14331
- id: `${CHECK_ID52}:${file.relativePath}:${lineNum + 1}:${(indexMatch.index ?? 0) + 1}:index`,
14332
- checkId: CHECK_ID52,
14583
+ id: `${CHECK_ID54}:${file.relativePath}:${lineNum + 1}:${(indexMatch.index ?? 0) + 1}:index`,
14584
+ checkId: CHECK_ID54,
14333
14585
  category: "resilience",
14334
14586
  severity: "medium",
14335
14587
  message: getCopy("checks.destructive-sql-migration.message", indexMatch[0]),
@@ -14345,7 +14597,7 @@ var destructiveSqlMigrationCheck = {
14345
14597
  }
14346
14598
  };
14347
14599
  registry.register(destructiveSqlMigrationCheck);
14348
- var CHECK_ID53 = "resilience.sql-broad-mutation";
14600
+ var CHECK_ID55 = "resilience.sql-broad-mutation";
14349
14601
  var REMEDIATION43 = getCopy("remediation.resilience.sql-broad-mutation");
14350
14602
  function splitSqlStatements(content) {
14351
14603
  const statements = [];
@@ -14383,7 +14635,7 @@ function splitSqlStatements(content) {
14383
14635
  return statements;
14384
14636
  }
14385
14637
  var sqlBroadMutationCheck = {
14386
- id: CHECK_ID53,
14638
+ id: CHECK_ID55,
14387
14639
  name: getCopy("checks.sql-broad-mutation.name"),
14388
14640
  description: getCopy("checks.sql-broad-mutation.description"),
14389
14641
  category: "resilience",
@@ -14429,8 +14681,8 @@ var sqlBroadMutationCheck = {
14429
14681
  const beforeStatement = content.slice(0, charOffset + leadingWhitespace);
14430
14682
  const lineNum = beforeStatement.split("\n").length;
14431
14683
  findings.push({
14432
- id: `${CHECK_ID53}:${file.relativePath}:${lineNum}:1`,
14433
- checkId: CHECK_ID53,
14684
+ id: `${CHECK_ID55}:${file.relativePath}:${lineNum}:1`,
14685
+ checkId: CHECK_ID55,
14434
14686
  category: "resilience",
14435
14687
  severity: "high",
14436
14688
  message: getCopy("checks.sql-broad-mutation.message", matchedKeyword),
@@ -14448,7 +14700,7 @@ var sqlBroadMutationCheck = {
14448
14700
  }
14449
14701
  };
14450
14702
  registry.register(sqlBroadMutationCheck);
14451
- var CHECK_ID54 = "data-exposure.unprotected-sensitive-sql-columns";
14703
+ var CHECK_ID56 = "data-exposure.unprotected-sensitive-sql-columns";
14452
14704
  var REMEDIATION44 = getCopy("remediation.data-exposure.unprotected-sensitive-sql-columns");
14453
14705
  var SENSITIVE_KEYWORDS = [
14454
14706
  "ssn",
@@ -14480,7 +14732,7 @@ var PROTECTED_SUFFIXES = [
14480
14732
  "_key_id"
14481
14733
  ];
14482
14734
  var unprotectedSensitiveSqlColumnsCheck = {
14483
- id: CHECK_ID54,
14735
+ id: CHECK_ID56,
14484
14736
  name: getCopy("checks.unprotected-sensitive-sql-columns.name"),
14485
14737
  description: getCopy("checks.unprotected-sensitive-sql-columns.description"),
14486
14738
  category: "data-exposure",
@@ -14510,8 +14762,8 @@ var unprotectedSensitiveSqlColumnsCheck = {
14510
14762
  const isProtected = PROTECTED_SUFFIXES.some((suf) => colName.endsWith(suf));
14511
14763
  if (isSensitive && isPlainText && !isProtected) {
14512
14764
  findings.push({
14513
- id: `${CHECK_ID54}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 1}`,
14514
- checkId: CHECK_ID54,
14765
+ id: `${CHECK_ID56}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 1}`,
14766
+ checkId: CHECK_ID56,
14515
14767
  category: "data-exposure",
14516
14768
  severity: "high",
14517
14769
  message: getCopy("checks.unprotected-sensitive-sql-columns.message", `Column "${colNameRaw}" with type "${colTypeRaw}"`),
@@ -14575,9 +14827,9 @@ registry.register({
14575
14827
  return runLLMCheck(ctx, promptTemplate30);
14576
14828
  }
14577
14829
  });
14578
- var CHECK_ID55 = "security.rust-unsafe-blocks";
14830
+ var CHECK_ID57 = "security.rust-unsafe-blocks";
14579
14831
  registry.register({
14580
- id: CHECK_ID55,
14832
+ id: CHECK_ID57,
14581
14833
  name: getCopy("checks.rust-unsafe-blocks.name"),
14582
14834
  description: getCopy("checks.rust-unsafe-blocks.description"),
14583
14835
  category: "security",
@@ -14664,8 +14916,8 @@ registry.register({
14664
14916
  }
14665
14917
  if (!foundSafety) {
14666
14918
  const finding = {
14667
- id: `${CHECK_ID55}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
14668
- checkId: CHECK_ID55,
14919
+ id: `${CHECK_ID57}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
14920
+ checkId: CHECK_ID57,
14669
14921
  category: "security",
14670
14922
  severity: "medium",
14671
14923
  message: getCopy("checks.rust-unsafe-blocks.message", match.nodeText),
@@ -14683,7 +14935,7 @@ registry.register({
14683
14935
  return findings;
14684
14936
  }
14685
14937
  });
14686
- var CHECK_ID56 = "resilience.rust-handler-panics";
14938
+ var CHECK_ID58 = "resilience.rust-handler-panics";
14687
14939
  var RUST_QUERY = `
14688
14940
  (call_expression
14689
14941
  function: (field_expression
@@ -14709,7 +14961,7 @@ function hasAsyncModifier(node) {
14709
14961
  return false;
14710
14962
  }
14711
14963
  registry.register({
14712
- id: CHECK_ID56,
14964
+ id: CHECK_ID58,
14713
14965
  name: getCopy("checks.rust-handler-panics.name"),
14714
14966
  description: getCopy("checks.rust-handler-panics.description"),
14715
14967
  category: "resilience",
@@ -14775,8 +15027,8 @@ registry.register({
14775
15027
  const isAsync = hasAsyncModifier(enclosingFunction);
14776
15028
  if (isAsync) {
14777
15029
  const finding = {
14778
- id: `${CHECK_ID56}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
14779
- checkId: CHECK_ID56,
15030
+ id: `${CHECK_ID58}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
15031
+ checkId: CHECK_ID58,
14780
15032
  category: "resilience",
14781
15033
  severity: "high",
14782
15034
  message: getCopy("checks.rust-handler-panics.message", match.nodeText),
@@ -16214,6 +16466,7 @@ async function executeScan(args, ctx) {
16214
16466
  debug: args.debug,
16215
16467
  baseline: args.baseline,
16216
16468
  minConfidence: args.minConfidence,
16469
+ minSeverity: args.minSeverity,
16217
16470
  onCheckComplete: (completed, total, checkName) => {
16218
16471
  heartbeat();
16219
16472
  if (showProgress) {
@@ -16593,6 +16846,7 @@ async function runCli(options) {
16593
16846
  debug: options.debug,
16594
16847
  baseline: options.baseline,
16595
16848
  minConfidence: options.minConfidence,
16849
+ minSeverity: options.minSeverity,
16596
16850
  showIds: options.showIds,
16597
16851
  all: options.all
16598
16852
  };
@@ -16771,12 +17025,13 @@ var require5 = createRequire4(import.meta.url);
16771
17025
  var { version } = require5("../package.json");
16772
17026
  var cli = cac("seaworthycode");
16773
17027
  cli.version(version);
16774
- 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) => {
16775
17029
  const maxFileSize = options.maxFileSize ? Number(options.maxFileSize) : void 0;
16776
17030
  const tier = options.tier;
16777
17031
  const baseline = options.baseline === "save" ? "save" : options.baseline;
16778
17032
  const minConfidence = options.minConfidence;
16779
- 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 });
16780
17035
  if (result.updateMessage) {
16781
17036
  console.error(result.updateMessage);
16782
17037
  }
@@ -16844,6 +17099,7 @@ cli.help(() => {
16844
17099
  --max-file-size <n> Skip files larger than n bytes (default: 1 MB)
16845
17100
  --baseline [ref] Compare against baseline file or save (--baseline save)
16846
17101
  --min-confidence <level> Hide findings below confidence (high, medium, low)
17102
+ --min-severity <level> Hide findings below severity (critical, high, medium, low, info)
16847
17103
  --show-ids Show check IDs alongside names
16848
17104
  --all List every finding; do not collapse low-severity findings
16849
17105
  --debug Enable debug logging`