seaworthycode 1.2.5 → 1.2.7

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
@@ -64,7 +64,7 @@ import { createHash as createHash5, randomBytes } from "crypto";
64
64
  import * as fs8 from "fs";
65
65
  import { join as join11 } from "path";
66
66
  import { createHash as createHash6 } from "crypto";
67
- import { Query as TSQuery2 } from "web-tree-sitter";
67
+ import { Parser as TSParser, Query as TSQuery2 } from "web-tree-sitter";
68
68
  import { readFileSync as readFileSync4, readdirSync, existsSync as existsSync4 } from "fs";
69
69
  import { dirname as dirname3, resolve as pathResolve3 } from "path";
70
70
  import { parse as parseYaml } from "yaml";
@@ -1321,7 +1321,7 @@ var copy = {
1321
1321
  "cli.error.outputInsideTarget": "Cannot write report inside the scanned directory.",
1322
1322
  "errors.parse.binary_file": "File appears to be binary and was skipped.",
1323
1323
  // Warnings
1324
- "warnings.deps.tree_exceeded": "Dependency tree exceeded 500 packages. Results may be incomplete.",
1324
+ "warnings.deps.tree_exceeded": "Dependency tree is large (5000+ packages). CVE scan may take longer than usual.",
1325
1325
  // Debug
1326
1326
  "debug.submodule_skipped": (path) => `Submodule ${path} not initialised, skipping.`,
1327
1327
  "debug.symlink_loop": (path) => `Symlink loop detected at ${path}, skipping.`,
@@ -1497,7 +1497,10 @@ var copy = {
1497
1497
  "remediation.resilience.orm-n-plus-one": "Replace per-row queries inside loops with a single batch query using include/eager loading (Prisma include, Sequelize eager loading, SQLAlchemy joinedload, ActiveRecord includes).",
1498
1498
  // Reporting labels
1499
1499
  "reporting.analysisType.pattern": "pattern",
1500
- "reporting.analysisType.semantic": "semantic"
1500
+ "reporting.analysisType.semantic": "semantic",
1501
+ // Collapsed low/info finding groups
1502
+ "reporting.collapsed.summary": (count, files) => `${count} findings across ${files} ${files === 1 ? "file" : "files"}`,
1503
+ "reporting.collapsed.hint": "Run with --all to list each finding, or --output report.html for full detail."
1501
1504
  };
1502
1505
  function getCopy(key, ...args) {
1503
1506
  const value = copy[key];
@@ -1613,7 +1616,8 @@ var ScanRunner = class {
1613
1616
  files,
1614
1617
  meta,
1615
1618
  taintCache: /* @__PURE__ */ new Map(),
1616
- astCache: /* @__PURE__ */ new Map()
1619
+ astCache: /* @__PURE__ */ new Map(),
1620
+ bashAstCache: /* @__PURE__ */ new Map()
1617
1621
  };
1618
1622
  const results = [];
1619
1623
  const checkResults = /* @__PURE__ */ new Map();
@@ -1646,18 +1650,15 @@ var ScanRunner = class {
1646
1650
  }
1647
1651
  }
1648
1652
  }
1649
- if (ctx.astCache) {
1650
- for (const promise of ctx.astCache.values()) {
1651
- try {
1652
- const { tree } = await promise;
1653
- if (tree) {
1654
- tree.delete();
1655
- }
1656
- } catch {
1657
- }
1653
+ ctx.astCache?.clear();
1654
+ for (const [, promise] of ctx.bashAstCache ?? []) {
1655
+ try {
1656
+ const parsed = await promise;
1657
+ parsed.tree.delete();
1658
+ } catch {
1658
1659
  }
1659
- ctx.astCache.clear();
1660
1660
  }
1661
+ ctx.bashAstCache?.clear();
1661
1662
  let findings = sortFindings(
1662
1663
  dedupeDegradedFindings(dedupeCrossCheck(dedupeFindings(results)))
1663
1664
  );
@@ -2427,6 +2428,7 @@ var text = {
2427
2428
  success: (s) => source_default.hex(tokens.success)(s)
2428
2429
  };
2429
2430
  var SEVERITY_ORDER2 = ["critical", "high", "medium", "low", "info"];
2431
+ var COLLAPSIBLE_SEVERITIES = /* @__PURE__ */ new Set(["low", "info"]);
2430
2432
  function checkNameOf(checkId) {
2431
2433
  const short = checkId.replace(/^[^.]+\./, "");
2432
2434
  try {
@@ -2435,9 +2437,49 @@ function checkNameOf(checkId) {
2435
2437
  return short;
2436
2438
  }
2437
2439
  }
2440
+ function analysisTypeSuffix(f) {
2441
+ const rawAnalysisType = f.properties?.analysisType;
2442
+ const analysisTypeValue = rawAnalysisType === "pattern" || rawAnalysisType === "semantic" ? rawAnalysisType : void 0;
2443
+ return analysisTypeValue ? text.meta(` [${getCopy(`reporting.analysisType.${analysisTypeValue}`)}]`) : "";
2444
+ }
2445
+ function renderFinding(f, showIds) {
2446
+ const severityLabel = textForSeverity(f.severity);
2447
+ const baselineLabel = f.baselineState === "new" ? text.success(" [NEW]") : "";
2448
+ const loc = f.file ? text.meta(` (${f.file}:${f.line ?? "-"})`) : "";
2449
+ const confidenceLabel = f.confidence && f.confidence !== "high" ? text.meta(` [${f.confidence} confidence]`) : "";
2450
+ const analysisType = analysisTypeSuffix(f);
2451
+ const name = checkNameOf(f.checkId);
2452
+ const idSuffix = showIds ? text.meta(` \xB7 ${f.checkId}`) : "";
2453
+ const lines = [];
2454
+ lines.push(`${severityLabel}${baselineLabel} ${name}${idSuffix}${analysisType} \u2014 ${f.message}${loc}${confidenceLabel}`);
2455
+ if (f.remediation) {
2456
+ lines.push(` ${text.meta(`\u2192 ${f.remediation}`)}`);
2457
+ }
2458
+ if (f.properties?.suggestion) {
2459
+ lines.push(` ${text.meta(`\u{1F4A1} ${f.properties.suggestion}`)}`);
2460
+ }
2461
+ return lines;
2462
+ }
2463
+ function renderCollapsedGroup(group, showIds) {
2464
+ const first = group[0];
2465
+ const severityLabel = textForSeverity(first.severity);
2466
+ const analysisType = analysisTypeSuffix(first);
2467
+ const name = checkNameOf(first.checkId);
2468
+ const idSuffix = showIds ? text.meta(` \xB7 ${first.checkId}`) : "";
2469
+ const total = group.length;
2470
+ const fileCount = new Set(group.map((f) => f.file).filter(Boolean)).size;
2471
+ const countPhrase = getCopy("reporting.collapsed.summary", total, fileCount);
2472
+ const lines = [];
2473
+ lines.push(`${severityLabel} ${name}${idSuffix}${analysisType} \u2014 ${countPhrase}`);
2474
+ if (first.remediation) {
2475
+ lines.push(` ${text.meta(`\u2192 ${first.remediation}`)}`);
2476
+ }
2477
+ lines.push(` ${text.meta(`\u2192 ${getCopy("reporting.collapsed.hint")}`)}`);
2478
+ return lines;
2479
+ }
2438
2480
  var TerminalReporter = class {
2439
2481
  generate(input) {
2440
- const { findings: rawFindings, targetDir, tier, licensedTo, scannedFileCount, skippedChecks = [], showIds = false } = input;
2482
+ const { findings: rawFindings, targetDir, tier, licensedTo, scannedFileCount, skippedChecks = [], showIds = false, all = false } = input;
2441
2483
  const findings = dedupeDegradedFindings(rawFindings);
2442
2484
  if (findings.length === 0) {
2443
2485
  const lines2 = [];
@@ -2456,22 +2498,30 @@ var TerminalReporter = class {
2456
2498
  const lines = [];
2457
2499
  lines.push(text.heading(getCopy("report.header", targetDir, findings.length)));
2458
2500
  lines.push("");
2459
- for (const f of findings) {
2460
- const severityLabel = textForSeverity(f.severity);
2461
- const baselineLabel = f.baselineState === "new" ? text.success(" [NEW]") : "";
2462
- const loc = f.file ? text.meta(` (${f.file}:${f.line ?? "-"})`) : "";
2463
- const confidenceLabel = f.confidence && f.confidence !== "high" ? text.meta(` [${f.confidence} confidence]`) : "";
2464
- const rawAnalysisType = f.properties?.analysisType;
2465
- const analysisTypeValue = rawAnalysisType === "pattern" || rawAnalysisType === "semantic" ? rawAnalysisType : void 0;
2466
- const analysisType = analysisTypeValue ? text.meta(` [${getCopy(`reporting.analysisType.${analysisTypeValue}`)}]`) : "";
2467
- const name = checkNameOf(f.checkId);
2468
- const idSuffix = showIds ? text.meta(` \xB7 ${f.checkId}`) : "";
2469
- lines.push(`${severityLabel}${baselineLabel} ${name}${idSuffix}${analysisType} \u2014 ${f.message}${loc}${confidenceLabel}`);
2470
- if (f.remediation) {
2471
- lines.push(` ${text.meta(`\u2192 ${f.remediation}`)}`);
2501
+ if (all) {
2502
+ for (const f of findings) {
2503
+ lines.push(...renderFinding(f, showIds));
2472
2504
  }
2473
- if (f.properties?.suggestion) {
2474
- lines.push(` ${text.meta(`\u{1F4A1} ${f.properties.suggestion}`)}`);
2505
+ } else {
2506
+ const collapsibleGroups = /* @__PURE__ */ new Map();
2507
+ for (const f of findings) {
2508
+ if (COLLAPSIBLE_SEVERITIES.has(f.severity)) {
2509
+ const group = collapsibleGroups.get(f.checkId);
2510
+ if (group) {
2511
+ group.push(f);
2512
+ } else {
2513
+ collapsibleGroups.set(f.checkId, [f]);
2514
+ }
2515
+ } else {
2516
+ lines.push(...renderFinding(f, showIds));
2517
+ }
2518
+ }
2519
+ for (const group of collapsibleGroups.values()) {
2520
+ if (group.length === 1) {
2521
+ lines.push(...renderFinding(group[0], showIds));
2522
+ } else {
2523
+ lines.push(...renderCollapsedGroup(group, showIds));
2524
+ }
2475
2525
  }
2476
2526
  }
2477
2527
  lines.push("");
@@ -3567,6 +3617,7 @@ async function loadLanguage(langKey) {
3567
3617
  languageCache.set(langKey, promise);
3568
3618
  return promise;
3569
3619
  }
3620
+ var MAX_PARSE_BYTES = 256 * 1024;
3570
3621
  function getOrCreateParser(lang, langKey) {
3571
3622
  if (langKey === "bash") {
3572
3623
  const parser2 = new Parser();
@@ -3581,7 +3632,56 @@ function getOrCreateParser(lang, langKey) {
3581
3632
  }
3582
3633
  return parser;
3583
3634
  }
3584
- async function queryFile(file, queryString, astCache) {
3635
+ async function resolveContent(file, contentCache) {
3636
+ if (contentCache) {
3637
+ const cacheKey = `${file.path}::${file.language}`;
3638
+ let contentPromise = contentCache.get(cacheKey);
3639
+ if (!contentPromise) {
3640
+ contentPromise = file.content().catch(() => {
3641
+ throw new Error("read_failed");
3642
+ });
3643
+ contentCache.set(cacheKey, contentPromise);
3644
+ }
3645
+ return contentPromise;
3646
+ }
3647
+ return file.content();
3648
+ }
3649
+ async function parseFile(file, contentCache) {
3650
+ if (!isLanguageSupported(file.language)) return null;
3651
+ const wasmFilename = LANGUAGE_WASM_MAP[file.language];
3652
+ if (!wasmFilename) return null;
3653
+ try {
3654
+ await ensureParserInit();
3655
+ } catch {
3656
+ return null;
3657
+ }
3658
+ let lang;
3659
+ try {
3660
+ lang = await loadLanguage(file.language);
3661
+ } catch {
3662
+ return null;
3663
+ }
3664
+ if (languageVerified.get(file.language) === false) return null;
3665
+ let source;
3666
+ try {
3667
+ source = await resolveContent(file, contentCache);
3668
+ } catch {
3669
+ return null;
3670
+ }
3671
+ if (source.length === 0 || source.length > MAX_PARSE_BYTES) return null;
3672
+ const freshParser = new Parser();
3673
+ freshParser.setLanguage(lang);
3674
+ try {
3675
+ const tree = freshParser.parse(source);
3676
+ freshParser.delete();
3677
+ if (!tree) return null;
3678
+ return { tree, source };
3679
+ } catch {
3680
+ freshParser.delete();
3681
+ return null;
3682
+ }
3683
+ }
3684
+ async function queryFile(file, queryString, astCache, bashAstCache) {
3585
3685
  if (!isLanguageSupported(file.language)) {
3586
3686
  return { matches: [], degraded: true };
3587
3687
  }
@@ -3609,29 +3709,30 @@ async function queryFile(file, queryString, astCache) {
3609
3709
  return { matches: [], degraded: true, error: `languageVerified is false for ${file.language}` };
3610
3710
  }
3611
3711
  let source;
3712
+ try {
3713
+ source = await resolveContent(file, astCache);
3714
+ } catch {
3715
+ return { matches: [], degraded: true };
3716
+ }
3717
+ if (source.length === 0) {
3718
+ return { matches: [], degraded: false };
3719
+ }
3720
+ if (source.length > MAX_PARSE_BYTES) {
3721
+ return { matches: [], degraded: true };
3722
+ }
3612
3723
  let tree;
3613
- if (astCache) {
3724
+ let ownTree = false;
3725
+ if (file.language === "bash" && bashAstCache) {
3614
3726
  const cacheKey = `${file.path}::${file.language}`;
3615
- let parsePromise = astCache.get(cacheKey);
3727
+ let parsePromise = bashAstCache.get(cacheKey);
3616
3728
  if (!parsePromise) {
3617
3729
  parsePromise = (async () => {
3618
- let s;
3619
- try {
3620
- s = await file.content();
3621
- } catch {
3622
- throw new Error("read_failed");
3623
- }
3624
- if (s.length === 0) {
3625
- return { tree: null, source: s };
3626
- }
3627
3730
  const parser = getOrCreateParser(lang, file.language);
3628
- const parsed2 = parser.parse(s);
3629
- if (!parsed2) {
3630
- throw new Error("parse_failed");
3631
- }
3632
- return { tree: parsed2, source: s };
3731
+ const parsed2 = parser.parse(source);
3732
+ if (!parsed2) throw new Error("parse_failed");
3733
+ return { tree: parsed2, source };
3633
3734
  })();
3634
- astCache.set(cacheKey, parsePromise);
3735
+ bashAstCache.set(cacheKey, parsePromise);
3635
3736
  }
3636
3737
  let parsed;
3637
3738
  try {
@@ -3639,28 +3740,20 @@ async function queryFile(file, queryString, astCache) {
3639
3740
  } catch {
3640
3741
  return { matches: [], degraded: true };
3641
3742
  }
3642
- if (parsed.source.length === 0) {
3643
- return { matches: [], degraded: false };
3644
- }
3645
- source = parsed.source;
3646
3743
  tree = parsed.tree;
3647
3744
  } else {
3745
+ ownTree = true;
3746
+ const freshParser = new Parser();
3747
+ freshParser.setLanguage(lang);
3648
3748
  try {
3649
- source = await file.content();
3650
- } catch {
3651
- return { matches: [], degraded: true };
3652
- }
3653
- if (source.length === 0) {
3654
- return { matches: [], degraded: false };
3655
- }
3656
- try {
3657
- const parser = getOrCreateParser(lang, file.language);
3658
- const parsed = parser.parse(source);
3749
+ const parsed = freshParser.parse(source);
3750
+ freshParser.delete();
3659
3751
  if (!parsed) {
3660
3752
  return { matches: [], degraded: true };
3661
3753
  }
3662
3754
  tree = parsed;
3663
3755
  } catch {
3756
+ freshParser.delete();
3664
3757
  return { matches: [], degraded: true };
3665
3758
  }
3666
3759
  }
@@ -3672,7 +3765,7 @@ async function queryFile(file, queryString, astCache) {
3672
3765
  query = new TSQuery(lang, queryString);
3673
3766
  compiledQueryCache.set(queryCacheKey, query);
3674
3767
  } catch (err) {
3675
- tree.delete();
3768
+ if (ownTree) tree.delete();
3676
3769
  console.error(
3677
3770
  `[seaworthy] Invalid query for ${file.language}:`,
3678
3771
  err.message
@@ -3722,9 +3815,7 @@ async function queryFile(file, queryString, astCache) {
3722
3815
  }
3723
3816
  return true;
3724
3817
  });
3725
- if (!astCache) {
3726
- tree.delete();
3727
- }
3818
+ if (ownTree) tree.delete();
3728
3819
  return { matches, degraded: false };
3729
3820
  }
3730
3821
  function setAnalysisType(finding, analysisType = "pattern") {
@@ -3778,7 +3869,7 @@ function createTreeSitterCheck(options) {
3778
3869
  async (file) => {
3779
3870
  const query = queries[file.language];
3780
3871
  try {
3781
- const result = await queryFile(file, query, ctx.astCache);
3872
+ const result = await queryFile(file, query, ctx.astCache, ctx.bashAstCache);
3782
3873
  if (result.degraded) {
3783
3874
  if (!degradedLanguages.has(file.language)) {
3784
3875
  degradedLanguages.add(file.language);
@@ -4747,7 +4838,7 @@ registry.register({
4747
4838
  return findings;
4748
4839
  }
4749
4840
  });
4750
- var MAX_DEPS = 500;
4841
+ var MAX_DEPS = 5e3;
4751
4842
  async function countDepsFromPackageLock(targetDir) {
4752
4843
  try {
4753
4844
  const raw = await fs5.readFile(join8(targetDir, "package-lock.json"), "utf-8");
@@ -11863,36 +11954,21 @@ function runAnalyse(file, table, fileContent, astCache) {
11863
11954
  }
11864
11955
  if (content.length === 0) return { flows: [], degraded: false };
11865
11956
  let tree;
11866
- let ownTree = false;
11957
+ const ownTree = true;
11867
11958
  const cacheKey = `${file.path}::${file.language}`;
11868
- if (astCache) {
11869
- let cached = astCache.get(cacheKey);
11870
- if (!cached) {
11871
- cached = (async () => {
11872
- const parser = getOrCreateParser(lang, file.language);
11873
- const parsed = parser.parse(content);
11874
- if (!parsed) throw new Error("parse failed");
11875
- return { tree: parsed, source: content };
11876
- })();
11877
- astCache.set(cacheKey, cached);
11878
- ownTree = false;
11879
- }
11880
- try {
11881
- const r = await cached;
11882
- tree = r.tree;
11883
- } catch {
11884
- return { flows: [], degraded: true };
11885
- }
11886
- } else {
11887
- try {
11888
- const parser = getOrCreateParser(lang, file.language);
11889
- const parsed = parser.parse(content);
11890
- if (!parsed) return { flows: [], degraded: true };
11891
- tree = parsed;
11892
- ownTree = true;
11893
- } catch {
11894
- return { flows: [], degraded: true };
11895
- }
11959
+ if (astCache && !astCache.has(cacheKey)) {
11960
+ astCache.set(cacheKey, Promise.resolve(content));
11961
+ }
11962
+ const freshParser = new TSParser();
11963
+ freshParser.setLanguage(lang);
11964
+ try {
11965
+ const parsed = freshParser.parse(content);
11966
+ freshParser.delete();
11967
+ if (!parsed) return { flows: [], degraded: true };
11968
+ tree = parsed;
11969
+ } catch {
11970
+ freshParser.delete();
11971
+ return { flows: [], degraded: true };
11896
11972
  }
11897
11973
  const entries = collectEntries(table);
11898
11974
  const taggedByNode = /* @__PURE__ */ new Map();
@@ -12826,7 +12902,7 @@ var check = {
12826
12902
  const _exampleLine = 1;
12827
12903
  for (const file of eligible) {
12828
12904
  const query = QUERIES9[file.language];
12829
- const result = await queryFile(file, query, ctx.astCache);
12905
+ const result = await queryFile(file, query, ctx.astCache, ctx.bashAstCache);
12830
12906
  if (result.degraded) {
12831
12907
  if (!degradedLanguages.has(file.language)) {
12832
12908
  degradedLanguages.add(file.language);
@@ -13829,7 +13905,7 @@ var sqlRlsStaticCheck = {
13829
13905
  const dialect = classifySqlDialect(content);
13830
13906
  if (dialect !== "postgres" && dialect !== "supabase") continue;
13831
13907
  const tableQuery = "(create_table (object_reference name: (identifier) @table_name))";
13832
- const tableResult = await queryFile(file, tableQuery, ctx.astCache);
13908
+ const tableResult = await queryFile(file, tableQuery, ctx.astCache, ctx.bashAstCache);
13833
13909
  if (tableResult.degraded) continue;
13834
13910
  const tables = [];
13835
13911
  for (const match of tableResult.matches) {
@@ -13839,7 +13915,7 @@ var sqlRlsStaticCheck = {
13839
13915
  const columnQuery = `(create_table
13840
13916
  (object_reference name: (identifier) @t_name (#eq? @t_name "${tableNameRaw}"))
13841
13917
  (column_definitions (column_definition name: (identifier) @col_name)))`;
13842
- const columnResult = await queryFile(file, columnQuery, ctx.astCache);
13918
+ const columnResult = await queryFile(file, columnQuery, ctx.astCache, ctx.bashAstCache);
13843
13919
  const columns = columnResult.matches.map((m) => normalizeIdentifier(m.captures.col_name ?? ""));
13844
13920
  const tenantColumns = ["tenant_id", "org_id", "workspace_id", "account_id", "user_id"];
13845
13921
  const isTenantTable = columns.some((c) => tenantColumns.includes(c));
@@ -14288,7 +14364,7 @@ var unprotectedSensitiveSqlColumnsCheck = {
14288
14364
  (identifier) @col_type
14289
14365
  (keyword_text) @col_type
14290
14366
  ])`;
14291
- const result = await queryFile(file, query, ctx.astCache);
14367
+ const result = await queryFile(file, query, ctx.astCache, ctx.bashAstCache);
14292
14368
  if (result.degraded) continue;
14293
14369
  for (const match of result.matches) {
14294
14370
  const colNameRaw = match.captures.col_name;
@@ -14387,7 +14463,7 @@ registry.register({
14387
14463
  continue;
14388
14464
  }
14389
14465
  const lines = code.split("\n");
14390
- const queryResult = await queryFile(file, "(unsafe_block) @unsafe", ctx.astCache);
14466
+ const queryResult = await queryFile(file, "(unsafe_block) @unsafe", ctx.astCache, ctx.bashAstCache);
14391
14467
  if (queryResult.degraded) continue;
14392
14468
  for (let i = 0; i < queryResult.matches.length; i++) {
14393
14469
  const match = queryResult.matches[i];
@@ -14539,50 +14615,51 @@ registry.register({
14539
14615
  }
14540
14616
  const isWebFile = /axum|actix/i.test(code);
14541
14617
  if (!isWebFile) continue;
14542
- const queryResult = await queryFile(file, RUST_QUERY, ctx.astCache);
14618
+ const queryResult = await queryFile(file, RUST_QUERY, ctx.astCache, ctx.bashAstCache);
14543
14619
  if (queryResult.degraded) continue;
14544
- const cacheKey = `${file.path}::${file.language}`;
14545
- const parsePromise = ctx.astCache?.get(cacheKey);
14546
- if (!parsePromise) continue;
14547
- const parsed = await parsePromise;
14548
- if (!parsed || !parsed.tree) continue;
14620
+ const parsed = await parseFile(file, ctx.astCache);
14621
+ if (!parsed) continue;
14549
14622
  const parentMap = /* @__PURE__ */ new Map();
14550
14623
  walk2(parsed.tree.rootNode);
14551
- for (let i = 0; i < queryResult.matches.length; i++) {
14552
- const match = queryResult.matches[i];
14553
- if (!match) continue;
14554
- const node = findNodeAt2(parsed.tree.rootNode, match.startLine, match.startColumn ?? 0);
14555
- if (!node) continue;
14556
- let curr = node;
14557
- let enclosingFunction = null;
14558
- while (curr) {
14559
- if (curr.type === "function_item") {
14560
- enclosingFunction = curr;
14561
- break;
14624
+ try {
14625
+ for (let i = 0; i < queryResult.matches.length; i++) {
14626
+ const match = queryResult.matches[i];
14627
+ if (!match) continue;
14628
+ const node = findNodeAt2(parsed.tree.rootNode, match.startLine, match.startColumn ?? 0);
14629
+ if (!node) continue;
14630
+ let curr = node;
14631
+ let enclosingFunction = null;
14632
+ while (curr) {
14633
+ if (curr.type === "function_item") {
14634
+ enclosingFunction = curr;
14635
+ break;
14636
+ }
14637
+ const next = parentMap.get(curr.id);
14638
+ if (!next) break;
14639
+ curr = next;
14562
14640
  }
14563
- const next = parentMap.get(curr.id);
14564
- if (!next) break;
14565
- curr = next;
14566
- }
14567
- if (enclosingFunction) {
14568
- const isAsync = hasAsyncModifier(enclosingFunction);
14569
- if (isAsync) {
14570
- const finding = {
14571
- id: `${CHECK_ID56}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
14572
- checkId: CHECK_ID56,
14573
- category: "resilience",
14574
- severity: "high",
14575
- message: getCopy("checks.rust-handler-panics.message", match.nodeText),
14576
- file: file.relativePath,
14577
- line: match.startLine,
14578
- column: match.startColumn,
14579
- confidence: "high",
14580
- remediation
14581
- };
14582
- setAnalysisType(finding, "pattern");
14583
- findings.push(finding);
14641
+ if (enclosingFunction) {
14642
+ const isAsync = hasAsyncModifier(enclosingFunction);
14643
+ if (isAsync) {
14644
+ const finding = {
14645
+ id: `${CHECK_ID56}:${file.relativePath}:${match.startLine}:${match.startColumn ?? 0}:${i}`,
14646
+ checkId: CHECK_ID56,
14647
+ category: "resilience",
14648
+ severity: "high",
14649
+ message: getCopy("checks.rust-handler-panics.message", match.nodeText),
14650
+ file: file.relativePath,
14651
+ line: match.startLine,
14652
+ column: match.startColumn,
14653
+ confidence: "high",
14654
+ remediation
14655
+ };
14656
+ setAnalysisType(finding, "pattern");
14657
+ findings.push(finding);
14658
+ }
14584
14659
  }
14585
14660
  }
14661
+ } finally {
14662
+ parsed.tree.delete();
14586
14663
  }
14587
14664
  }
14588
14665
  return findings;
@@ -16006,7 +16083,8 @@ async function executeScan(args, ctx) {
16006
16083
  licensedTo: ctx.licensedTo ?? "free tier",
16007
16084
  scannedFileCount: result.scannedFileCount,
16008
16085
  skippedChecks: result.skippedChecks,
16009
- showIds: args.showIds ?? args.debug
16086
+ showIds: args.showIds ?? args.debug,
16087
+ all: args.all
16010
16088
  };
16011
16089
  let primaryOutput;
16012
16090
  switch (args.format) {
@@ -16351,7 +16429,8 @@ async function runCli(options) {
16351
16429
  debug: options.debug,
16352
16430
  baseline: options.baseline,
16353
16431
  minConfidence: options.minConfidence,
16354
- showIds: options.showIds
16432
+ showIds: options.showIds,
16433
+ all: options.all
16355
16434
  };
16356
16435
  const updateStart = Date.now();
16357
16436
  const updatePromise = checkUpdate().catch(() => void 0);
@@ -16528,12 +16607,12 @@ var require5 = createRequire4(import.meta.url);
16528
16607
  var { version } = require5("../package.json");
16529
16608
  var cli = cac("seaworthy");
16530
16609
  cli.version(version);
16531
- 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)").action(async (targetDir, options) => {
16610
+ 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) => {
16532
16611
  const maxFileSize = options.maxFileSize ? Number(options.maxFileSize) : void 0;
16533
16612
  const tier = options.tier;
16534
16613
  const baseline = options.baseline === "save" ? "save" : options.baseline;
16535
16614
  const minConfidence = options.minConfidence;
16536
- 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 });
16615
+ 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 });
16537
16616
  if (result.updateMessage) {
16538
16617
  console.error(result.updateMessage);
16539
16618
  }
@@ -16602,6 +16681,7 @@ cli.help(() => {
16602
16681
  --baseline [ref] Compare against baseline file or save (--baseline save)
16603
16682
  --min-confidence <level> Hide findings below confidence (high, medium, low)
16604
16683
  --show-ids Show check IDs alongside names
16684
+ --all List every finding; do not collapse low-severity findings
16605
16685
  --debug Enable debug logging`
16606
16686
  },
16607
16687
  {