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 +65 -85
- 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,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: () =>
|
|
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: () =>
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
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
|
-
|
|
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.
|
|
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 = [
|
|
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: "+"
|
|
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: "+"
|
|
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: "+"
|
|
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: "+"
|
|
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: "+"
|
|
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: "+"
|
|
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: "+"
|
|
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: "+"
|
|
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
|