seaworthycode 1.2.13 → 1.2.16

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
@@ -407,12 +407,12 @@ async function crawl(options) {
407
407
  const { isBinary, isGenerated } = await peekFileHead(fullPath);
408
408
  if (isBinary || isGenerated) continue;
409
409
  if (await shouldSkipMinifiedJs(fullPath, entry.name)) continue;
410
- let cachedContent;
411
410
  files.push({
412
411
  path: fullPath,
413
412
  relativePath: relPath,
414
- content: () => cachedContent ??= fs.readFile(fullPath, "utf-8"),
415
- language: inferLanguage(entry.name)
413
+ content: () => fs.readFile(fullPath, "utf-8"),
414
+ language: inferLanguage(entry.name),
415
+ size: stat2.size
416
416
  });
417
417
  } catch {
418
418
  }
@@ -428,12 +428,12 @@ async function crawl(options) {
428
428
  const { isBinary, isGenerated } = await peekFileHead(fullPath);
429
429
  if (isBinary || isGenerated) continue;
430
430
  if (await shouldSkipMinifiedJs(fullPath, entry.name)) continue;
431
- let cachedContent;
432
431
  files.push({
433
432
  path: fullPath,
434
433
  relativePath: relPath,
435
- content: () => cachedContent ??= fs.readFile(fullPath, "utf-8"),
436
- language: inferLanguage(entry.name)
434
+ content: () => fs.readFile(fullPath, "utf-8"),
435
+ language: inferLanguage(entry.name),
436
+ size: stat2.size
437
437
  });
438
438
  } catch {
439
439
  }
@@ -1630,7 +1630,9 @@ var ScanRunner = class {
1630
1630
  files,
1631
1631
  meta,
1632
1632
  taintCache: /* @__PURE__ */ new Map(),
1633
- astCache: /* @__PURE__ */ new Map(),
1633
+ // astCache is intentionally omitted here — each check gets its own
1634
+ // per-check cache below so content strings are GC'd when the check ends.
1635
+ astCache: void 0,
1634
1636
  bashAstCache: /* @__PURE__ */ new Map()
1635
1637
  };
1636
1638
  const results = [];
@@ -1654,6 +1656,7 @@ var ScanRunner = class {
1654
1656
  let timedOut = false;
1655
1657
  let softTimer;
1656
1658
  let hardTimer;
1659
+ const checkCtx = { ...ctx, astCache: /* @__PURE__ */ new Map() };
1657
1660
  try {
1658
1661
  const findings2 = await new Promise((resolve22, reject) => {
1659
1662
  softTimer = setTimeout(() => {
@@ -1670,7 +1673,7 @@ var ScanRunner = class {
1670
1673
  );
1671
1674
  resolve22([]);
1672
1675
  }, checkTimeoutMs);
1673
- check2.run(ctx).then(resolve22, reject);
1676
+ check2.run(checkCtx).then(resolve22, reject);
1674
1677
  });
1675
1678
  clearTimeout(softTimer);
1676
1679
  clearTimeout(hardTimer);
@@ -6025,21 +6028,14 @@ function downgradeSeverity(severity) {
6025
6028
  if (idx < 0 || idx >= SEVERITY_ORDER3.length - 1) return severity;
6026
6029
  return SEVERITY_ORDER3[idx + 1];
6027
6030
  }
6028
- function estimateTokens(text22) {
6029
- return Math.ceil(text22.length / 4);
6030
- }
6031
- async function filterFilesByBudget(files, budget) {
6031
+ function filterFilesByBudget(files, budget) {
6032
6032
  if (budget === void 0 || budget === Infinity) {
6033
6033
  return files;
6034
6034
  }
6035
- const result = [];
6036
- for (const file of files) {
6037
- const content = await file.content();
6038
- if (estimateTokens(content) <= budget) {
6039
- result.push(file);
6040
- }
6041
- }
6042
- return result;
6035
+ return files.filter((file) => {
6036
+ const tokens2 = file.size !== void 0 ? Math.ceil(file.size / 4) : budget;
6037
+ return tokens2 <= budget;
6038
+ });
6043
6039
  }
6044
6040
  async function buildCacheKey(ctx, template, files) {
6045
6041
  if (!ctx.llm) return void 0;
@@ -6216,14 +6212,21 @@ async function runLLMCheck(ctx, template) {
6216
6212
  return [];
6217
6213
  }
6218
6214
  const proEligibleFiles = ctx.files.filter((f) => isProCheckEligible(f, template));
6219
- const cacheKey = await buildCacheKey(ctx, template, proEligibleFiles);
6215
+ let eligibleFiles = filterFilesByBudget(proEligibleFiles, template.tokenBudget);
6216
+ const maxFiles = getMaxFilesPerLlmCheck();
6217
+ if (eligibleFiles.length > maxFiles) {
6218
+ console.error(
6219
+ `[seaworthy] ${template.checkId}: ${eligibleFiles.length} eligible files \u2014 capping LLM prompt to ${maxFiles}`
6220
+ );
6221
+ eligibleFiles = eligibleFiles.slice(0, maxFiles);
6222
+ }
6223
+ const cacheKey = await buildCacheKey(ctx, template, eligibleFiles);
6220
6224
  if (cacheKey) {
6221
6225
  const cached = readCache(cacheKey);
6222
6226
  if (cached !== null) {
6223
6227
  return cached;
6224
6228
  }
6225
6229
  }
6226
- let eligibleFiles = await filterFilesByBudget(proEligibleFiles, template.tokenBudget);
6227
6230
  if (eligibleFiles.length === 0) {
6228
6231
  let mechFindings = await runMechanicalCheck(ctx, template);
6229
6232
  if (template.projectScoped) {
@@ -6239,13 +6242,6 @@ async function runLLMCheck(ctx, template) {
6239
6242
  }
6240
6243
  return withRemediation;
6241
6244
  }
6242
- const maxFiles = getMaxFilesPerLlmCheck();
6243
- if (eligibleFiles.length > maxFiles) {
6244
- console.error(
6245
- `[seaworthy] ${template.checkId}: ${eligibleFiles.length} eligible files \u2014 capping LLM prompt to ${maxFiles}`
6246
- );
6247
- eligibleFiles = eligibleFiles.slice(0, maxFiles);
6248
- }
6249
6245
  const prompt = `${PROMPT_PREAMBLE}
6250
6246
 
6251
6247
  ${await template.buildPrompt(eligibleFiles)}`;
@@ -6399,7 +6395,7 @@ registry.register({
6399
6395
  return runLLMCheck(ctx, promptTemplate2);
6400
6396
  }
6401
6397
  });
6402
- var PROMPT_VERSION3 = "1.0.0";
6398
+ var PROMPT_VERSION3 = "1.1.0";
6403
6399
  var fallbackPatterns3 = [
6404
6400
  {
6405
6401
  regex: /['"`]\s*(?:SELECT|INSERT|UPDATE|DELETE)\s+.*\$\{/i,
@@ -6413,7 +6409,22 @@ var fallbackPatterns3 = [
6413
6409
  }
6414
6410
  ];
6415
6411
  async function buildPrompt3(sourceFiles) {
6416
- const parts = ["Check for SQL injection vulnerabilities. Look for raw string concatenation or template literals used to build SQL queries. Flag any use of string interpolation in .query(), .raw(), .execute(), or raw SQL strings."];
6412
+ const parts = [
6413
+ `Check for SQL injection: flag ONLY where user-controlled input reaches a SQL query WITHOUT parameterization.
6414
+
6415
+ SAFE \u2014 do NOT flag:
6416
+ - Queries using ? or $N placeholders with a separate args array: db.Query("SELECT * FROM t WHERE id = ?", id)
6417
+ - GORM .Raw()/.Exec() with ? bindings, even when the SQL string contains || (PostgreSQL concat operator inside the string): db.Exec("ST_GeomFromText('POINT(' || ? || ')', 4326)", lat, lng)
6418
+ - String concatenation where ALL operands are string literals (compile-time constants, not variables)
6419
+ - Variables sourced from application config or startup env, not from HTTP requests or user payloads
6420
+
6421
+ UNSAFE \u2014 DO flag:
6422
+ - Template literals / f-strings injecting request values: db.query(\`SELECT * FROM t WHERE id = \${req.params.id}\`)
6423
+ - String concatenation where a variable traces to req.body, req.query, FormValue(), r.URL.Query(), $_GET, $_POST, or similar external input
6424
+ - fmt.Sprintf building a SQL string with user-supplied arguments (not just format specifiers over internal values)
6425
+
6426
+ Only flag if you can trace the injected value to an external input source (HTTP request, message payload, file upload).`
6427
+ ];
6417
6428
  for (const file of sourceFiles) {
6418
6429
  const content = await file.content();
6419
6430
  parts.push(`--- ${file.relativePath} ---
@@ -6455,7 +6466,8 @@ var tsCheck4 = createTreeSitterCheck({
6455
6466
  (#match? @method "^(query|raw|execute)$"))
6456
6467
  arguments: (arguments
6457
6468
  (binary_expression
6458
- operator: "+"))) @match
6469
+ operator: "+"
6470
+ right: (_) @rhs))) @match
6459
6471
 
6460
6472
  (call_expression
6461
6473
  function: (member_expression
@@ -6485,7 +6497,8 @@ var tsCheck4 = createTreeSitterCheck({
6485
6497
  (#match? @method "^(query|raw|execute)$"))
6486
6498
  arguments: (arguments
6487
6499
  (binary_expression
6488
- operator: "+"))) @match
6500
+ operator: "+"
6501
+ right: (_) @rhs))) @match
6489
6502
 
6490
6503
  (call_expression
6491
6504
  function: (member_expression
@@ -6515,7 +6528,8 @@ var tsCheck4 = createTreeSitterCheck({
6515
6528
  (#match? @method "^(query|raw|execute)$"))
6516
6529
  arguments: (arguments
6517
6530
  (binary_expression
6518
- operator: "+"))) @match
6531
+ operator: "+"
6532
+ right: (_) @rhs))) @match
6519
6533
 
6520
6534
  (call_expression
6521
6535
  function: (member_expression
@@ -6545,7 +6559,8 @@ var tsCheck4 = createTreeSitterCheck({
6545
6559
  (#match? @method "^(query|execute|raw)$"))
6546
6560
  arguments: (argument_list
6547
6561
  (binary_operator
6548
- operator: "+"))) @match
6562
+ operator: "+"
6563
+ right: (_) @rhs))) @match
6549
6564
 
6550
6565
  (call
6551
6566
  function: (attribute
@@ -6574,7 +6589,8 @@ var tsCheck4 = createTreeSitterCheck({
6574
6589
  (#match? @method "^(Query|Exec|QueryRow)$"))
6575
6590
  arguments: (argument_list
6576
6591
  (binary_expression
6577
- operator: "+"))) @match
6592
+ operator: "+"
6593
+ right: (_) @rhs))) @match
6578
6594
 
6579
6595
  (call_expression
6580
6596
  function: (selector_expression
@@ -6623,14 +6639,16 @@ var tsCheck4 = createTreeSitterCheck({
6623
6639
  (#match? @method "^execute.*$")
6624
6640
  arguments: (argument_list
6625
6641
  (binary_expression
6626
- operator: "+"))) @match
6642
+ operator: "+"
6643
+ right: (_) @rhs))) @match
6627
6644
 
6628
6645
  (method_invocation
6629
6646
  name: (identifier) @method
6630
6647
  (#eq? @method "prepareStatement")
6631
6648
  arguments: (argument_list
6632
6649
  (binary_expression
6633
- operator: "+"))) @match
6650
+ operator: "+"
6651
+ right: (_) @rhs))) @match
6634
6652
 
6635
6653
  (method_invocation
6636
6654
  object: (identifier) @obj
@@ -6639,7 +6657,8 @@ var tsCheck4 = createTreeSitterCheck({
6639
6657
  (#match? @method "^(query|update)$")
6640
6658
  arguments: (argument_list
6641
6659
  (binary_expression
6642
- operator: "+"))) @match
6660
+ operator: "+"
6661
+ right: (_) @rhs))) @match
6643
6662
  `,
6644
6663
  ruby: `
6645
6664
  (call
@@ -6662,6 +6681,13 @@ var tsCheck4 = createTreeSitterCheck({
6662
6681
  remediationKey: "remediation.security.sql-injection-surface",
6663
6682
  messageFactory: (captures) => {
6664
6683
  return getCopy("checks.sql-injection-surface.message", captures.method ?? "?");
6684
+ },
6685
+ matchFilter: (captures) => {
6686
+ if (!captures.rhs) return true;
6687
+ const rhs = captures.rhs.trim();
6688
+ if (/^["'`]/.test(rhs)) return false;
6689
+ if (/^\[\]byte\s*\(["']/.test(rhs)) return false;
6690
+ return true;
6665
6691
  }
6666
6692
  });
6667
6693
  var llmCheck = {
@@ -8903,52 +8929,6 @@ var QUERIES6 = {
8903
8929
  arguments: (argument_list
8904
8930
  (_) @data)) @match
8905
8931
  `,
8906
- go: `
8907
- (call_expression
8908
- function: (selector_expression
8909
- operand: (identifier) @obj
8910
- field: (field_identifier) @field
8911
- (#eq? @obj "json")
8912
- (#eq? @field "Unmarshal"))
8913
- arguments: (argument_list
8914
- [
8915
- (identifier) @data
8916
- (call_expression) @data
8917
- (selector_expression) @data
8918
- (index_expression) @data
8919
- (binary_expression) @data
8920
- ]
8921
- (_))) @match
8922
-
8923
- (call_expression
8924
- function: (selector_expression
8925
- field: (field_identifier) @field
8926
- (#eq? @field "Decode"))
8927
- arguments: (argument_list
8928
- [
8929
- (identifier) @data
8930
- (call_expression) @data
8931
- (selector_expression) @data
8932
- (index_expression) @data
8933
- (binary_expression) @data
8934
- ])) @match
8935
-
8936
- (call_expression
8937
- function: (selector_expression
8938
- operand: (identifier) @obj
8939
- field: (field_identifier) @field
8940
- (#eq? @obj "xml")
8941
- (#eq? @field "Unmarshal"))
8942
- arguments: (argument_list
8943
- [
8944
- (identifier) @data
8945
- (call_expression) @data
8946
- (selector_expression) @data
8947
- (index_expression) @data
8948
- (binary_expression) @data
8949
- ]
8950
- (_))) @match
8951
- `,
8952
8932
  java: `
8953
8933
  (method_invocation
8954
8934
  name: (identifier) @method