seaworthycode 1.2.11 → 1.2.13
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 +112 -8
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/wasm/tree-sitter-sql.wasm +0 -0
package/dist/index.js
CHANGED
|
@@ -407,10 +407,11 @@ 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;
|
|
410
411
|
files.push({
|
|
411
412
|
path: fullPath,
|
|
412
413
|
relativePath: relPath,
|
|
413
|
-
content: () => fs.readFile(fullPath, "utf-8"),
|
|
414
|
+
content: () => cachedContent ??= fs.readFile(fullPath, "utf-8"),
|
|
414
415
|
language: inferLanguage(entry.name)
|
|
415
416
|
});
|
|
416
417
|
} catch {
|
|
@@ -427,10 +428,11 @@ async function crawl(options) {
|
|
|
427
428
|
const { isBinary, isGenerated } = await peekFileHead(fullPath);
|
|
428
429
|
if (isBinary || isGenerated) continue;
|
|
429
430
|
if (await shouldSkipMinifiedJs(fullPath, entry.name)) continue;
|
|
431
|
+
let cachedContent;
|
|
430
432
|
files.push({
|
|
431
433
|
path: fullPath,
|
|
432
434
|
relativePath: relPath,
|
|
433
|
-
content: () => fs.readFile(fullPath, "utf-8"),
|
|
435
|
+
content: () => cachedContent ??= fs.readFile(fullPath, "utf-8"),
|
|
434
436
|
language: inferLanguage(entry.name)
|
|
435
437
|
});
|
|
436
438
|
} catch {
|
|
@@ -789,6 +791,7 @@ var copy = {
|
|
|
789
791
|
"report.cta.hasFindings": `Fix these before you ship. ${SITE_URL}/docs`,
|
|
790
792
|
"report.cta.critical": `\u26A0 Critical issues found \u2014 patch before you deploy. ${SITE_URL}/docs`,
|
|
791
793
|
"report.proUpsellNamed": (names, count) => `\u2693 Pro unlocks ${count} more check${count === 1 ? "" : "s"} (${names}). ${SITE_URL}`,
|
|
794
|
+
"report.timedOutChecks": (names, count) => `\u26A0 ${count} check${count === 1 ? "" : "s"} timed out and were skipped (${names}). Use SEAWORTHY_CHECK_TIMEOUT_MS to extend the limit.`,
|
|
792
795
|
"report.preScanNotice": (count) => `\u2693 Pro unlocks ${count} additional checks. Upgrade at ${SITE_URL}`,
|
|
793
796
|
"report.llmPrivacyWarning": "Pro checks will send code context to your LLM provider via your API key. No data is sent to Seaworthy servers.",
|
|
794
797
|
"report.confidenceHigh": "high confidence",
|
|
@@ -1601,6 +1604,16 @@ async function runWithConcurrencyCollect(items, fn, concurrency) {
|
|
|
1601
1604
|
await Promise.all(workers);
|
|
1602
1605
|
return results;
|
|
1603
1606
|
}
|
|
1607
|
+
var DEFAULT_CHECK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1608
|
+
var SLOW_CHECK_THRESHOLD_RATIO = 0.5;
|
|
1609
|
+
function getCheckTimeoutMs() {
|
|
1610
|
+
const env2 = process.env.SEAWORTHY_CHECK_TIMEOUT_MS;
|
|
1611
|
+
if (env2 !== void 0) {
|
|
1612
|
+
const n = parseInt(env2, 10);
|
|
1613
|
+
if (!isNaN(n) && n > 0) return n;
|
|
1614
|
+
}
|
|
1615
|
+
return DEFAULT_CHECK_TIMEOUT_MS;
|
|
1616
|
+
}
|
|
1604
1617
|
var ScanRunner = class {
|
|
1605
1618
|
async run(options) {
|
|
1606
1619
|
const { concurrency = 4 } = options;
|
|
@@ -1628,15 +1641,54 @@ var ScanRunner = class {
|
|
|
1628
1641
|
}
|
|
1629
1642
|
const categoryCompleted = /* @__PURE__ */ new Map();
|
|
1630
1643
|
let completedCount = 0;
|
|
1644
|
+
const timedOutChecks = [];
|
|
1645
|
+
const checkTimeoutMs = getCheckTimeoutMs();
|
|
1631
1646
|
await runWithConcurrency(
|
|
1632
1647
|
checks,
|
|
1633
1648
|
async (check2) => {
|
|
1649
|
+
if (options.debug) {
|
|
1650
|
+
process.stderr.write(`[seaworthy] check.start ${check2.id}
|
|
1651
|
+
`);
|
|
1652
|
+
}
|
|
1653
|
+
const startMs = Date.now();
|
|
1654
|
+
let timedOut = false;
|
|
1655
|
+
let softTimer;
|
|
1656
|
+
let hardTimer;
|
|
1634
1657
|
try {
|
|
1635
|
-
const findings2 = await
|
|
1636
|
-
|
|
1658
|
+
const findings2 = await new Promise((resolve22, reject) => {
|
|
1659
|
+
softTimer = setTimeout(() => {
|
|
1660
|
+
process.stderr.write(
|
|
1661
|
+
`[seaworthy] check.slow ${check2.id} \u2014 still running after ${Math.round(checkTimeoutMs * SLOW_CHECK_THRESHOLD_RATIO / 1e3)}s
|
|
1662
|
+
`
|
|
1663
|
+
);
|
|
1664
|
+
}, checkTimeoutMs * SLOW_CHECK_THRESHOLD_RATIO);
|
|
1665
|
+
hardTimer = setTimeout(() => {
|
|
1666
|
+
timedOut = true;
|
|
1667
|
+
process.stderr.write(
|
|
1668
|
+
`[seaworthy] check.timeout ${check2.id} \u2014 aborting after ${Math.round(checkTimeoutMs / 1e3)}s
|
|
1669
|
+
`
|
|
1670
|
+
);
|
|
1671
|
+
resolve22([]);
|
|
1672
|
+
}, checkTimeoutMs);
|
|
1673
|
+
check2.run(ctx).then(resolve22, reject);
|
|
1674
|
+
});
|
|
1675
|
+
clearTimeout(softTimer);
|
|
1676
|
+
clearTimeout(hardTimer);
|
|
1677
|
+
if (timedOut) {
|
|
1678
|
+
timedOutChecks.push({ id: check2.id, name: check2.name, category: check2.category });
|
|
1679
|
+
checkResults.set(check2.id, []);
|
|
1680
|
+
} else {
|
|
1681
|
+
checkResults.set(check2.id, findings2);
|
|
1682
|
+
}
|
|
1637
1683
|
} catch (err) {
|
|
1684
|
+
clearTimeout(softTimer);
|
|
1685
|
+
clearTimeout(hardTimer);
|
|
1638
1686
|
console.error(`Check "${check2.id}" failed: ${String(err)}`);
|
|
1639
1687
|
}
|
|
1688
|
+
if (options.debug) {
|
|
1689
|
+
process.stderr.write(`[seaworthy] check.done ${check2.id} ${Date.now() - startMs}ms
|
|
1690
|
+
`);
|
|
1691
|
+
}
|
|
1640
1692
|
options.onCheckComplete?.(++completedCount, checks.length, check2.name);
|
|
1641
1693
|
if (options.onCategoryComplete) {
|
|
1642
1694
|
const catDone = (categoryCompleted.get(check2.category) ?? 0) + 1;
|
|
@@ -1685,7 +1737,8 @@ var ScanRunner = class {
|
|
|
1685
1737
|
return {
|
|
1686
1738
|
findings,
|
|
1687
1739
|
scannedFileCount: files.length,
|
|
1688
|
-
skippedChecks
|
|
1740
|
+
skippedChecks,
|
|
1741
|
+
timedOutChecks
|
|
1689
1742
|
};
|
|
1690
1743
|
}
|
|
1691
1744
|
};
|
|
@@ -2486,7 +2539,7 @@ function renderCollapsedGroup(group, showIds) {
|
|
|
2486
2539
|
}
|
|
2487
2540
|
var TerminalReporter = class {
|
|
2488
2541
|
generate(input) {
|
|
2489
|
-
const { findings: rawFindings, targetDir, tier, licensedTo, scannedFileCount, skippedChecks = [], showIds = false, all = false } = input;
|
|
2542
|
+
const { findings: rawFindings, targetDir, tier, licensedTo, scannedFileCount, skippedChecks = [], timedOutChecks = [], showIds = false, all = false } = input;
|
|
2490
2543
|
const findings = dedupeDegradedFindings(rawFindings);
|
|
2491
2544
|
if (findings.length === 0) {
|
|
2492
2545
|
const lines2 = [];
|
|
@@ -2500,6 +2553,10 @@ var TerminalReporter = class {
|
|
|
2500
2553
|
lines2.push("");
|
|
2501
2554
|
lines2.push(text.link(buildProUpsell(skippedChecks)));
|
|
2502
2555
|
}
|
|
2556
|
+
if (timedOutChecks.length > 0) {
|
|
2557
|
+
lines2.push("");
|
|
2558
|
+
lines2.push(text.warning(buildTimedOutNote(timedOutChecks)));
|
|
2559
|
+
}
|
|
2503
2560
|
return lines2.join("\n");
|
|
2504
2561
|
}
|
|
2505
2562
|
const lines = [];
|
|
@@ -2549,9 +2606,18 @@ var TerminalReporter = class {
|
|
|
2549
2606
|
if (skippedChecks.length > 0) {
|
|
2550
2607
|
lines.push(text.link(buildProUpsell(skippedChecks)));
|
|
2551
2608
|
}
|
|
2609
|
+
if (timedOutChecks.length > 0) {
|
|
2610
|
+
lines.push(text.warning(buildTimedOutNote(timedOutChecks)));
|
|
2611
|
+
}
|
|
2552
2612
|
return lines.join("\n");
|
|
2553
2613
|
}
|
|
2554
2614
|
};
|
|
2615
|
+
function buildTimedOutNote(timedOutChecks) {
|
|
2616
|
+
const count = timedOutChecks.length;
|
|
2617
|
+
const names = timedOutChecks.slice(0, 2).map((c) => c.name.toLowerCase()).join(", ");
|
|
2618
|
+
const suffix = count > 2 ? ", \u2026" : "";
|
|
2619
|
+
return getCopy("report.timedOutChecks", names + suffix, count);
|
|
2620
|
+
}
|
|
2555
2621
|
function buildProUpsell(skippedChecks) {
|
|
2556
2622
|
const count = skippedChecks.length;
|
|
2557
2623
|
const names = skippedChecks.slice(0, 2).map((c) => c.name.toLowerCase()).join(", ");
|
|
@@ -5904,19 +5970,49 @@ function classifyError(err) {
|
|
|
5904
5970
|
if (err instanceof Error && err.name === "TimeoutError") return "llm.timeout";
|
|
5905
5971
|
if (err instanceof DOMException && (err.name === "TimeoutError" || err.name === "AbortError")) return "llm.timeout";
|
|
5906
5972
|
if (err instanceof Error && /rate|429|too many/i.test(err.message)) return "llm.rate_limited";
|
|
5973
|
+
if (err instanceof Error && /auth|401|unauthorized|invalid.*key/i.test(err.message)) return "llm.auth_error";
|
|
5907
5974
|
return "llm.unexpected";
|
|
5908
5975
|
}
|
|
5976
|
+
var LLM_TIMEOUT_MS = 9e4;
|
|
5977
|
+
function getMaxFilesPerLlmCheck() {
|
|
5978
|
+
const env2 = process.env.SEAWORTHY_LLM_MAX_FILES;
|
|
5979
|
+
if (env2 !== void 0) {
|
|
5980
|
+
const n = parseInt(env2, 10);
|
|
5981
|
+
if (!isNaN(n) && n > 0) return n;
|
|
5982
|
+
}
|
|
5983
|
+
return 150;
|
|
5984
|
+
}
|
|
5985
|
+
function withLLMTimeout(promise, ms) {
|
|
5986
|
+
return new Promise((resolve22, reject) => {
|
|
5987
|
+
const timerId = setTimeout(() => {
|
|
5988
|
+
const err = new Error("LLM request timed out");
|
|
5989
|
+
err.name = "TimeoutError";
|
|
5990
|
+
reject(err);
|
|
5991
|
+
}, ms);
|
|
5992
|
+
promise.then(
|
|
5993
|
+
(value) => {
|
|
5994
|
+
clearTimeout(timerId);
|
|
5995
|
+
resolve22(value);
|
|
5996
|
+
},
|
|
5997
|
+
(err) => {
|
|
5998
|
+
clearTimeout(timerId);
|
|
5999
|
+
reject(err);
|
|
6000
|
+
}
|
|
6001
|
+
);
|
|
6002
|
+
});
|
|
6003
|
+
}
|
|
5909
6004
|
async function callLLMWithRetry(llm, prompt, checkId) {
|
|
5910
6005
|
let lastError;
|
|
5911
6006
|
const delays = [0, 2e3];
|
|
5912
6007
|
for (let attempt = 0; attempt < delays.length; attempt++) {
|
|
5913
6008
|
try {
|
|
5914
6009
|
if (attempt > 0) await new Promise((r) => setTimeout(r, delays[attempt]));
|
|
5915
|
-
return await llm.complete(prompt);
|
|
6010
|
+
return await withLLMTimeout(llm.complete(prompt), LLM_TIMEOUT_MS);
|
|
5916
6011
|
} catch (err) {
|
|
5917
6012
|
lastError = err;
|
|
5918
6013
|
if (err instanceof LLMResponseError) break;
|
|
5919
6014
|
if (err instanceof Error && /rate|429/i.test(err.message)) break;
|
|
6015
|
+
if (err instanceof Error && /auth|401|unauthorized|invalid.*key/i.test(err.message)) break;
|
|
5920
6016
|
}
|
|
5921
6017
|
}
|
|
5922
6018
|
const code = classifyError(lastError);
|
|
@@ -6127,7 +6223,7 @@ async function runLLMCheck(ctx, template) {
|
|
|
6127
6223
|
return cached;
|
|
6128
6224
|
}
|
|
6129
6225
|
}
|
|
6130
|
-
|
|
6226
|
+
let eligibleFiles = await filterFilesByBudget(proEligibleFiles, template.tokenBudget);
|
|
6131
6227
|
if (eligibleFiles.length === 0) {
|
|
6132
6228
|
let mechFindings = await runMechanicalCheck(ctx, template);
|
|
6133
6229
|
if (template.projectScoped) {
|
|
@@ -6143,6 +6239,13 @@ async function runLLMCheck(ctx, template) {
|
|
|
6143
6239
|
}
|
|
6144
6240
|
return withRemediation;
|
|
6145
6241
|
}
|
|
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
|
+
}
|
|
6146
6249
|
const prompt = `${PROMPT_PREAMBLE}
|
|
6147
6250
|
|
|
6148
6251
|
${await template.buildPrompt(eligibleFiles)}`;
|
|
@@ -16120,6 +16223,7 @@ async function executeScan(args, ctx) {
|
|
|
16120
16223
|
licensedTo: ctx.licensedTo ?? "free tier",
|
|
16121
16224
|
scannedFileCount: result.scannedFileCount,
|
|
16122
16225
|
skippedChecks: result.skippedChecks,
|
|
16226
|
+
timedOutChecks: result.timedOutChecks,
|
|
16123
16227
|
showIds: args.showIds ?? args.debug,
|
|
16124
16228
|
all: args.all
|
|
16125
16229
|
};
|