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 +224 -144
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
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
|
-
|
|
2460
|
-
const
|
|
2461
|
-
|
|
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
|
-
|
|
2474
|
-
|
|
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
|
|
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
|
-
|
|
3724
|
+
let ownTree = false;
|
|
3725
|
+
if (file.language === "bash" && bashAstCache) {
|
|
3614
3726
|
const cacheKey = `${file.path}::${file.language}`;
|
|
3615
|
-
let parsePromise =
|
|
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(
|
|
3629
|
-
if (!parsed2)
|
|
3630
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3650
|
-
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
11957
|
+
const ownTree = true;
|
|
11867
11958
|
const cacheKey = `${file.path}::${file.language}`;
|
|
11868
|
-
if (astCache) {
|
|
11869
|
-
|
|
11870
|
-
|
|
11871
|
-
|
|
11872
|
-
|
|
11873
|
-
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
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
|
|
14545
|
-
|
|
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
|
-
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
|
|
14556
|
-
|
|
14557
|
-
|
|
14558
|
-
|
|
14559
|
-
|
|
14560
|
-
|
|
14561
|
-
|
|
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
|
-
|
|
14564
|
-
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
|
|
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
|
{
|