cognium-dev 3.193.0 → 3.194.0
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/cli.js +416 -3
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -9350,6 +9350,7 @@ var GO_KEYWORDS = new Set([
|
|
|
9350
9350
|
function buildGoDFG(tree) {
|
|
9351
9351
|
const defs = [];
|
|
9352
9352
|
const uses = [];
|
|
9353
|
+
const extraChains = [];
|
|
9353
9354
|
let defIdCounter = 1;
|
|
9354
9355
|
let useIdCounter = 1;
|
|
9355
9356
|
const scopeStack = [new Map];
|
|
@@ -9371,7 +9372,7 @@ function buildGoDFG(tree) {
|
|
|
9371
9372
|
}
|
|
9372
9373
|
const body2 = func2.childForFieldName("body");
|
|
9373
9374
|
if (body2) {
|
|
9374
|
-
processGoBlock(body2, defs, uses, scopeStack, { defId: defIdCounter, useId: useIdCounter });
|
|
9375
|
+
processGoBlock(body2, defs, uses, scopeStack, { defId: defIdCounter, useId: useIdCounter }, extraChains);
|
|
9375
9376
|
defIdCounter = defs.length + 1;
|
|
9376
9377
|
useIdCounter = uses.length + 1;
|
|
9377
9378
|
}
|
|
@@ -9387,6 +9388,15 @@ function buildGoDFG(tree) {
|
|
|
9387
9388
|
}
|
|
9388
9389
|
}
|
|
9389
9390
|
const chains = computeChains(defs, uses);
|
|
9391
|
+
const seen = new Set(chains.map((c) => `${c.from_def}|${c.to_def}|${c.via}`));
|
|
9392
|
+
for (const ch of extraChains) {
|
|
9393
|
+
const key = `${ch.from_def}|${ch.to_def}|${ch.via}`;
|
|
9394
|
+
if (!seen.has(key)) {
|
|
9395
|
+
seen.add(key);
|
|
9396
|
+
chains.push(ch);
|
|
9397
|
+
}
|
|
9398
|
+
}
|
|
9399
|
+
chains.sort((a, b) => a.from_def - b.from_def || a.to_def - b.to_def);
|
|
9390
9400
|
return { defs, uses, chains };
|
|
9391
9401
|
}
|
|
9392
9402
|
function extractGoParamDefs(params, defs, _startId, scopeStack) {
|
|
@@ -9415,28 +9425,51 @@ function extractGoParamDefs(params, defs, _startId, scopeStack) {
|
|
|
9415
9425
|
}
|
|
9416
9426
|
}
|
|
9417
9427
|
}
|
|
9418
|
-
function processGoBlock(node, defs, uses, scopeStack, counters) {
|
|
9428
|
+
function processGoBlock(node, defs, uses, scopeStack, counters, extraChains = []) {
|
|
9429
|
+
const linkMultiLineRhs = (right, useStart, useEnd, defStart, defEnd) => {
|
|
9430
|
+
if (right.endPosition.row <= right.startPosition.row)
|
|
9431
|
+
return;
|
|
9432
|
+
for (let ui = useStart;ui < useEnd; ui++) {
|
|
9433
|
+
const u = uses[ui];
|
|
9434
|
+
if (!u || u.def_id === null)
|
|
9435
|
+
continue;
|
|
9436
|
+
for (let di = defStart;di < defEnd; di++) {
|
|
9437
|
+
const d = defs[di];
|
|
9438
|
+
if (d && d.kind === "local" && d.id !== u.def_id) {
|
|
9439
|
+
extraChains.push({ from_def: u.def_id, to_def: d.id, via: u.variable });
|
|
9440
|
+
}
|
|
9441
|
+
}
|
|
9442
|
+
}
|
|
9443
|
+
};
|
|
9419
9444
|
walkTree(node, (child) => {
|
|
9420
9445
|
if (child.type === "short_var_declaration") {
|
|
9421
9446
|
const left = child.childForFieldName("left");
|
|
9422
9447
|
const right = child.childForFieldName("right");
|
|
9448
|
+
const usesBefore = uses.length;
|
|
9423
9449
|
if (right) {
|
|
9424
9450
|
extractGoUses(right, uses, scopeStack);
|
|
9425
9451
|
}
|
|
9452
|
+
const defsBefore = defs.length;
|
|
9426
9453
|
if (left) {
|
|
9427
9454
|
extractGoLhsDefs(left, defs, scopeStack, child.startPosition.row + 1);
|
|
9428
9455
|
}
|
|
9456
|
+
if (right)
|
|
9457
|
+
linkMultiLineRhs(right, usesBefore, uses.length, defsBefore, defs.length);
|
|
9429
9458
|
} else if (child.type === "var_declaration") {
|
|
9430
9459
|
processGoVarDecl(child, defs, scopeStack, counters);
|
|
9431
9460
|
} else if (child.type === "assignment_statement") {
|
|
9432
9461
|
const left = child.childForFieldName("left");
|
|
9433
9462
|
const right = child.childForFieldName("right");
|
|
9463
|
+
const usesBefore = uses.length;
|
|
9434
9464
|
if (right) {
|
|
9435
9465
|
extractGoUses(right, uses, scopeStack);
|
|
9436
9466
|
}
|
|
9467
|
+
const defsBefore = defs.length;
|
|
9437
9468
|
if (left) {
|
|
9438
9469
|
extractGoLhsDefs(left, defs, scopeStack, child.startPosition.row + 1);
|
|
9439
9470
|
}
|
|
9471
|
+
if (right)
|
|
9472
|
+
linkMultiLineRhs(right, usesBefore, uses.length, defsBefore, defs.length);
|
|
9440
9473
|
} else if (child.type === "for_statement") {
|
|
9441
9474
|
const rangeClause = findChildByTypeGo(child, "range_clause");
|
|
9442
9475
|
if (rangeClause) {
|
|
@@ -33426,6 +33459,375 @@ class DeserializationSafetyGatePass {
|
|
|
33426
33459
|
}
|
|
33427
33460
|
}
|
|
33428
33461
|
|
|
33462
|
+
// ../circle-ir/dist/analysis/passes/prompt-injection-safety-gate-pass.js
|
|
33463
|
+
function isDelimiterLiteral(lit) {
|
|
33464
|
+
const s = lit.trim();
|
|
33465
|
+
if (s.length === 0)
|
|
33466
|
+
return true;
|
|
33467
|
+
return /^<\/?[\w.-]+\s*\/?>$/.test(s) || /^\[\/?[\w.-]+\]$/.test(s) || /^`{1,3}[\w-]*$/.test(s) || /^"""[\w-]*$/.test(s) || /^[#*=_~-]{2,}$/.test(s) || /^<{2,}[\w-]*$/.test(s) || /^[\w-]*>{2,}$/.test(s);
|
|
33468
|
+
}
|
|
33469
|
+
function scanValue(code, start2) {
|
|
33470
|
+
let depth = 0;
|
|
33471
|
+
let str = null;
|
|
33472
|
+
let out2 = "";
|
|
33473
|
+
for (let i2 = start2;i2 < code.length; i2++) {
|
|
33474
|
+
const c = code[i2];
|
|
33475
|
+
if (str) {
|
|
33476
|
+
out2 += c;
|
|
33477
|
+
if (c === str && code[i2 - 1] !== "\\")
|
|
33478
|
+
str = null;
|
|
33479
|
+
continue;
|
|
33480
|
+
}
|
|
33481
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
33482
|
+
str = c;
|
|
33483
|
+
out2 += c;
|
|
33484
|
+
continue;
|
|
33485
|
+
}
|
|
33486
|
+
if (c === "(" || c === "[" || c === "{") {
|
|
33487
|
+
depth++;
|
|
33488
|
+
out2 += c;
|
|
33489
|
+
continue;
|
|
33490
|
+
}
|
|
33491
|
+
if (c === ")" || c === "]" || c === "}") {
|
|
33492
|
+
if (depth === 0)
|
|
33493
|
+
break;
|
|
33494
|
+
depth--;
|
|
33495
|
+
out2 += c;
|
|
33496
|
+
continue;
|
|
33497
|
+
}
|
|
33498
|
+
if (c === "," && depth === 0)
|
|
33499
|
+
break;
|
|
33500
|
+
out2 += c;
|
|
33501
|
+
}
|
|
33502
|
+
return out2.trim();
|
|
33503
|
+
}
|
|
33504
|
+
function stringLiterals(expr) {
|
|
33505
|
+
return [...expr.matchAll(/"([^"]*)"|'([^']*)'/g)].map((m) => m[1] ?? m[2] ?? "");
|
|
33506
|
+
}
|
|
33507
|
+
function hasIdentifier(expr) {
|
|
33508
|
+
return /[A-Za-z_$][\w$]*/.test(expr.replace(/"[^"]*"|'[^']*'|`[^`]*`/g, " "));
|
|
33509
|
+
}
|
|
33510
|
+
function isPureLiteral(valueExpr) {
|
|
33511
|
+
const stripped = valueExpr.replace(/"[^"]*"|'[^']*'|`[^`]*`/g, " ");
|
|
33512
|
+
return !/[A-Za-z_$][\w$]*/.test(stripped);
|
|
33513
|
+
}
|
|
33514
|
+
function isInstructionConcat(valueExpr) {
|
|
33515
|
+
if (!valueExpr.includes("+"))
|
|
33516
|
+
return false;
|
|
33517
|
+
if (!hasIdentifier(valueExpr))
|
|
33518
|
+
return false;
|
|
33519
|
+
const lits = stringLiterals(valueExpr);
|
|
33520
|
+
if (lits.length === 0)
|
|
33521
|
+
return false;
|
|
33522
|
+
return !lits.every(isDelimiterLiteral);
|
|
33523
|
+
}
|
|
33524
|
+
function isMixedTemplate(v) {
|
|
33525
|
+
const fstr = v.match(/\bf(["'])((?:\\.|(?!\1).)*)\1/);
|
|
33526
|
+
if (fstr) {
|
|
33527
|
+
const body2 = fstr[2];
|
|
33528
|
+
if (/\{[^}]+\}/.test(body2) && /\w/.test(body2.replace(/\{[^}]*\}/g, "")))
|
|
33529
|
+
return true;
|
|
33530
|
+
}
|
|
33531
|
+
const tmpl = v.match(/`([^`]*)`/);
|
|
33532
|
+
if (tmpl) {
|
|
33533
|
+
const body2 = tmpl[1];
|
|
33534
|
+
if (/\$\{[^}]+\}/.test(body2) && /\w/.test(body2.replace(/\$\{[^}]*\}/g, "")))
|
|
33535
|
+
return true;
|
|
33536
|
+
}
|
|
33537
|
+
return false;
|
|
33538
|
+
}
|
|
33539
|
+
function assignmentRe(varName, flags2 = "") {
|
|
33540
|
+
return new RegExp(`\\b${varName}\\s*(?::=|(?<![=<>!])=(?!=))\\s*`, flags2);
|
|
33541
|
+
}
|
|
33542
|
+
function resolveAssignmentRHS(varName, codeLines) {
|
|
33543
|
+
const re = assignmentRe(varName);
|
|
33544
|
+
for (const line of codeLines) {
|
|
33545
|
+
const m = line.match(re);
|
|
33546
|
+
if (m && m.index !== undefined) {
|
|
33547
|
+
return scanValue(line, m.index + m[0].length);
|
|
33548
|
+
}
|
|
33549
|
+
}
|
|
33550
|
+
return;
|
|
33551
|
+
}
|
|
33552
|
+
function classifyExpr(v) {
|
|
33553
|
+
if (isMixedTemplate(v))
|
|
33554
|
+
return "unsafe";
|
|
33555
|
+
if (v.includes("+")) {
|
|
33556
|
+
if (isInstructionConcat(v))
|
|
33557
|
+
return "unsafe";
|
|
33558
|
+
const lits = stringLiterals(v);
|
|
33559
|
+
if (lits.length > 0 && lits.every(isDelimiterLiteral) && hasIdentifier(v))
|
|
33560
|
+
return "safe";
|
|
33561
|
+
return "unknown";
|
|
33562
|
+
}
|
|
33563
|
+
return "unknown";
|
|
33564
|
+
}
|
|
33565
|
+
function classifyContentPlacement(val, codeLines) {
|
|
33566
|
+
const v = val.trim();
|
|
33567
|
+
if (/^[A-Za-z_$][\w$]*$/.test(v)) {
|
|
33568
|
+
const rhs = resolveAssignmentRHS(v, codeLines);
|
|
33569
|
+
if (rhs === undefined)
|
|
33570
|
+
return "unknown";
|
|
33571
|
+
return classifyExpr(rhs);
|
|
33572
|
+
}
|
|
33573
|
+
return classifyExpr(v);
|
|
33574
|
+
}
|
|
33575
|
+
function classifyPromptCall(callCode, codeLines) {
|
|
33576
|
+
const contentRe = /["']?[Cc]ontent["']?\s*[:=]\s*/g;
|
|
33577
|
+
let m;
|
|
33578
|
+
let sawDynamic = false;
|
|
33579
|
+
let allSafe = true;
|
|
33580
|
+
while ((m = contentRe.exec(callCode)) !== null) {
|
|
33581
|
+
const val = scanValue(callCode, m.index + m[0].length);
|
|
33582
|
+
if (val === "" || isPureLiteral(val))
|
|
33583
|
+
continue;
|
|
33584
|
+
sawDynamic = true;
|
|
33585
|
+
const ctx = callCode.slice(Math.max(0, m.index - 80), m.index);
|
|
33586
|
+
const roleM = ctx.match(/["']?[Rr]ole["']?\s*[:=]\s*["'](\w+)["'][^"']*$/);
|
|
33587
|
+
const role = roleM ? roleM[1].toLowerCase() : "user";
|
|
33588
|
+
if (role === "system" || role === "assistant")
|
|
33589
|
+
return "unsafe";
|
|
33590
|
+
const placement = classifyContentPlacement(val, codeLines);
|
|
33591
|
+
if (placement === "unsafe")
|
|
33592
|
+
return "unsafe";
|
|
33593
|
+
if (placement !== "safe")
|
|
33594
|
+
allSafe = false;
|
|
33595
|
+
}
|
|
33596
|
+
if (!sawDynamic)
|
|
33597
|
+
return "unknown";
|
|
33598
|
+
return allSafe ? "safe" : "unknown";
|
|
33599
|
+
}
|
|
33600
|
+
function scanRhsAcrossLines(text, start2) {
|
|
33601
|
+
let depth = 0;
|
|
33602
|
+
let str = null;
|
|
33603
|
+
let out2 = "";
|
|
33604
|
+
for (let i2 = start2;i2 < text.length; i2++) {
|
|
33605
|
+
const c = text[i2];
|
|
33606
|
+
if (str) {
|
|
33607
|
+
out2 += c;
|
|
33608
|
+
if (c === str && text[i2 - 1] !== "\\")
|
|
33609
|
+
str = null;
|
|
33610
|
+
continue;
|
|
33611
|
+
}
|
|
33612
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
33613
|
+
str = c;
|
|
33614
|
+
out2 += c;
|
|
33615
|
+
continue;
|
|
33616
|
+
}
|
|
33617
|
+
if (c === "(" || c === "[" || c === "{") {
|
|
33618
|
+
depth++;
|
|
33619
|
+
out2 += c;
|
|
33620
|
+
continue;
|
|
33621
|
+
}
|
|
33622
|
+
if (c === ")" || c === "]" || c === "}") {
|
|
33623
|
+
if (depth === 0)
|
|
33624
|
+
break;
|
|
33625
|
+
depth--;
|
|
33626
|
+
out2 += c;
|
|
33627
|
+
continue;
|
|
33628
|
+
}
|
|
33629
|
+
if (c === `
|
|
33630
|
+
` && depth === 0)
|
|
33631
|
+
break;
|
|
33632
|
+
out2 += c;
|
|
33633
|
+
}
|
|
33634
|
+
return out2.trim();
|
|
33635
|
+
}
|
|
33636
|
+
function resolveBuilderRhs(varName, joined) {
|
|
33637
|
+
const re = assignmentRe(varName, "g");
|
|
33638
|
+
const m = re.exec(joined);
|
|
33639
|
+
if (m)
|
|
33640
|
+
return scanRhsAcrossLines(joined, m.index + m[0].length);
|
|
33641
|
+
return;
|
|
33642
|
+
}
|
|
33643
|
+
function collectBuilderRegion(callCode, joined) {
|
|
33644
|
+
const parts2 = [];
|
|
33645
|
+
const seen = new Set;
|
|
33646
|
+
const stack = [...callCode.matchAll(/[A-Za-z_$][\w$]*/g)].map((x) => x[0]);
|
|
33647
|
+
let budget = 40;
|
|
33648
|
+
while (stack.length > 0 && budget-- > 0) {
|
|
33649
|
+
const v = stack.pop();
|
|
33650
|
+
if (seen.has(v))
|
|
33651
|
+
continue;
|
|
33652
|
+
seen.add(v);
|
|
33653
|
+
const rhs = resolveBuilderRhs(v, joined);
|
|
33654
|
+
if (rhs) {
|
|
33655
|
+
parts2.push(rhs);
|
|
33656
|
+
for (const mm of rhs.matchAll(/[A-Za-z_$][\w$]*/g))
|
|
33657
|
+
stack.push(mm[0]);
|
|
33658
|
+
}
|
|
33659
|
+
}
|
|
33660
|
+
return parts2.join(`
|
|
33661
|
+
`);
|
|
33662
|
+
}
|
|
33663
|
+
function classifyPromptSink(callCode, codeLines) {
|
|
33664
|
+
const direct = classifyPromptCall(callCode, codeLines);
|
|
33665
|
+
if (direct !== "unknown")
|
|
33666
|
+
return direct;
|
|
33667
|
+
const region = collectBuilderRegion(callCode, codeLines.join(`
|
|
33668
|
+
`));
|
|
33669
|
+
if (!region)
|
|
33670
|
+
return "unknown";
|
|
33671
|
+
return classifyPromptCall(region, codeLines);
|
|
33672
|
+
}
|
|
33673
|
+
|
|
33674
|
+
class PromptInjectionSafetyGatePass {
|
|
33675
|
+
name = "prompt-injection-safety-gate";
|
|
33676
|
+
category = "security";
|
|
33677
|
+
run(ctx) {
|
|
33678
|
+
const { graph, code } = ctx;
|
|
33679
|
+
const sinks = ctx.hasResult("sink-filter") ? ctx.getResult("sink-filter").sinks : graph.ir.taint.sinks;
|
|
33680
|
+
if (sinks.length === 0)
|
|
33681
|
+
return { droppedSafe: 0 };
|
|
33682
|
+
const codeLines = code.split(`
|
|
33683
|
+
`);
|
|
33684
|
+
let droppedSafe = 0;
|
|
33685
|
+
const kept = sinks.filter((sink) => {
|
|
33686
|
+
if (sink.type !== "prompt_injection")
|
|
33687
|
+
return true;
|
|
33688
|
+
const callCode = sink.code ?? codeLines[sink.line - 1] ?? "";
|
|
33689
|
+
if (!callCode)
|
|
33690
|
+
return true;
|
|
33691
|
+
if (classifyPromptSink(callCode, codeLines) === "safe") {
|
|
33692
|
+
droppedSafe++;
|
|
33693
|
+
return false;
|
|
33694
|
+
}
|
|
33695
|
+
return true;
|
|
33696
|
+
});
|
|
33697
|
+
if (droppedSafe > 0) {
|
|
33698
|
+
sinks.length = 0;
|
|
33699
|
+
sinks.push(...kept);
|
|
33700
|
+
}
|
|
33701
|
+
return { droppedSafe };
|
|
33702
|
+
}
|
|
33703
|
+
}
|
|
33704
|
+
|
|
33705
|
+
// ../circle-ir/dist/analysis/passes/speculative-prompt-param-source-pass.js
|
|
33706
|
+
function returnOrAssignRhs(text) {
|
|
33707
|
+
const t = text.trim();
|
|
33708
|
+
const ret = t.match(/^return\s+(.+?);?$/);
|
|
33709
|
+
if (ret)
|
|
33710
|
+
return ret[1];
|
|
33711
|
+
const asg = t.match(/^[A-Za-z_$][\w$.]*\s*(?::=|(?<![=<>!])=(?!=))\s*(.+?);?$/);
|
|
33712
|
+
if (asg)
|
|
33713
|
+
return asg[1];
|
|
33714
|
+
return;
|
|
33715
|
+
}
|
|
33716
|
+
function looksLikePromptText(lit) {
|
|
33717
|
+
const s = lit.trim();
|
|
33718
|
+
if (/\b(you are|you're|assistant|system prompt|follow (the )?(policy|instructions|rules)|act as|your task|instructions?\s*:|ignore (all )?previous|do not reveal|respond (to|with|as)|answer the (question|user)|you (must|should|will)|helpful (ai|assistant))\b/i.test(s)) {
|
|
33719
|
+
return true;
|
|
33720
|
+
}
|
|
33721
|
+
const words = s.split(/\s+/).filter(Boolean);
|
|
33722
|
+
return words.length >= 4 && s.length >= 20;
|
|
33723
|
+
}
|
|
33724
|
+
function literalsOf(expr) {
|
|
33725
|
+
return [...expr.matchAll(/"([^"]*)"|'([^']*)'|`([^`]*)`/g)].map((m) => m[1] ?? m[2] ?? m[3] ?? "");
|
|
33726
|
+
}
|
|
33727
|
+
function detectPromptConstructionFlows(codeLines, types) {
|
|
33728
|
+
const flows = [];
|
|
33729
|
+
const seen = new Set;
|
|
33730
|
+
for (const type of types) {
|
|
33731
|
+
for (const method of type.methods) {
|
|
33732
|
+
const params = (method.parameters ?? []).map((p) => ({ name: p.name, line: p.line ?? method.start_line })).filter((p) => p.name && p.name !== "_");
|
|
33733
|
+
if (params.length === 0)
|
|
33734
|
+
continue;
|
|
33735
|
+
for (let line = method.start_line;line <= method.end_line; line++) {
|
|
33736
|
+
const text = codeLines[line - 1] ?? "";
|
|
33737
|
+
const rhs = returnOrAssignRhs(text);
|
|
33738
|
+
if (!rhs || !isInstructionConcat(rhs))
|
|
33739
|
+
continue;
|
|
33740
|
+
if (!literalsOf(rhs).some(looksLikePromptText))
|
|
33741
|
+
continue;
|
|
33742
|
+
const param = params.find((p) => new RegExp(`\\b${p.name}\\b`).test(rhs));
|
|
33743
|
+
if (!param)
|
|
33744
|
+
continue;
|
|
33745
|
+
const key = `${param.line}|${line}`;
|
|
33746
|
+
if (seen.has(key))
|
|
33747
|
+
continue;
|
|
33748
|
+
seen.add(key);
|
|
33749
|
+
flows.push({
|
|
33750
|
+
source_line: param.line,
|
|
33751
|
+
sink_line: line,
|
|
33752
|
+
source_type: "http_body",
|
|
33753
|
+
sink_type: "prompt_injection",
|
|
33754
|
+
path: [
|
|
33755
|
+
{ variable: param.name, line: param.line, type: "source" },
|
|
33756
|
+
{ variable: param.name, line, type: "sink" }
|
|
33757
|
+
],
|
|
33758
|
+
confidence: 1,
|
|
33759
|
+
sanitized: false
|
|
33760
|
+
});
|
|
33761
|
+
}
|
|
33762
|
+
}
|
|
33763
|
+
}
|
|
33764
|
+
return flows;
|
|
33765
|
+
}
|
|
33766
|
+
function looksLikeTextParam(type) {
|
|
33767
|
+
if (!type)
|
|
33768
|
+
return true;
|
|
33769
|
+
const t = type.trim();
|
|
33770
|
+
if (t.startsWith("*") || t.startsWith("&"))
|
|
33771
|
+
return false;
|
|
33772
|
+
if (/\b(Client|Context|Writer|Reader|Conn|DB|Logger|Handler|Server|Request|Response|Pool|Session|Engine|Service|Repository|Config)\b/.test(t)) {
|
|
33773
|
+
return false;
|
|
33774
|
+
}
|
|
33775
|
+
return /(?:^|\b)(string|str|String|text|any|object|interface\{\}|\[\]byte|\[\]string|List\[str\]|Optional\[str\])(?:\b|$)/i.test(t) || /^[A-Za-z_$][\w$<>[\], |]*$/.test(t);
|
|
33776
|
+
}
|
|
33777
|
+
|
|
33778
|
+
class SpeculativePromptParamSourcePass {
|
|
33779
|
+
enabled;
|
|
33780
|
+
name = "speculative-prompt-param-source";
|
|
33781
|
+
category = "security";
|
|
33782
|
+
constructor(enabled) {
|
|
33783
|
+
this.enabled = enabled;
|
|
33784
|
+
}
|
|
33785
|
+
run(ctx) {
|
|
33786
|
+
if (!this.enabled)
|
|
33787
|
+
return { added: 0 };
|
|
33788
|
+
if (!ctx.hasResult("sink-filter"))
|
|
33789
|
+
return { added: 0 };
|
|
33790
|
+
const sinkFilter = ctx.getResult("sink-filter");
|
|
33791
|
+
const { types } = ctx.graph.ir;
|
|
33792
|
+
const promptSinks = sinkFilter.sinks.filter((s) => s.type === "prompt_injection");
|
|
33793
|
+
if (promptSinks.length === 0)
|
|
33794
|
+
return { added: 0 };
|
|
33795
|
+
const existing = new Set(sinkFilter.sources.filter((s) => typeof s.variable === "string").map((s) => `${s.variable}:${s.line}`));
|
|
33796
|
+
const added = [];
|
|
33797
|
+
for (const type of types) {
|
|
33798
|
+
for (const method of type.methods) {
|
|
33799
|
+
const hasPromptSink = promptSinks.some((s) => s.line >= method.start_line && s.line <= method.end_line);
|
|
33800
|
+
if (!hasPromptSink)
|
|
33801
|
+
continue;
|
|
33802
|
+
for (const param of method.parameters) {
|
|
33803
|
+
if (!param.name || param.name === "_")
|
|
33804
|
+
continue;
|
|
33805
|
+
if (!looksLikeTextParam(param.type))
|
|
33806
|
+
continue;
|
|
33807
|
+
const line = param.line ?? method.start_line;
|
|
33808
|
+
const key = `${param.name}:${line}`;
|
|
33809
|
+
if (existing.has(key))
|
|
33810
|
+
continue;
|
|
33811
|
+
existing.add(key);
|
|
33812
|
+
added.push({
|
|
33813
|
+
type: "http_body",
|
|
33814
|
+
location: `speculative untrusted parameter '${param.name}' in ${method.name}`,
|
|
33815
|
+
severity: "high",
|
|
33816
|
+
line,
|
|
33817
|
+
confidence: 1,
|
|
33818
|
+
variable: param.name,
|
|
33819
|
+
in_method: method.name
|
|
33820
|
+
});
|
|
33821
|
+
}
|
|
33822
|
+
}
|
|
33823
|
+
}
|
|
33824
|
+
if (added.length > 0) {
|
|
33825
|
+
sinkFilter.sources.push(...added);
|
|
33826
|
+
}
|
|
33827
|
+
return { added: added.length };
|
|
33828
|
+
}
|
|
33829
|
+
}
|
|
33830
|
+
|
|
33429
33831
|
// ../circle-ir/dist/analysis/passes/cli-main-reflection-suppress-pass.js
|
|
33430
33832
|
var REFLECTION_SINK_METHODS = new Set([
|
|
33431
33833
|
"forName",
|
|
@@ -45134,6 +45536,9 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
45134
45536
|
pipeline.add(new SinkSemanticsPass);
|
|
45135
45537
|
if (!disabledPasses.has("deserialization-safety-gate"))
|
|
45136
45538
|
pipeline.add(new DeserializationSafetyGatePass(options.dependencyContext));
|
|
45539
|
+
if (!disabledPasses.has("prompt-injection-safety-gate"))
|
|
45540
|
+
pipeline.add(new PromptInjectionSafetyGatePass);
|
|
45541
|
+
pipeline.add(new SpeculativePromptParamSourcePass(options.speculativeParamSources === true));
|
|
45137
45542
|
if (!disabledPasses.has("cli-main-reflection-suppress"))
|
|
45138
45543
|
pipeline.add(new CliMainReflectionSuppressPass);
|
|
45139
45544
|
if (!disabledPasses.has("library-profile-sink-gate"))
|
|
@@ -45268,6 +45673,14 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
45268
45673
|
flows: interProc.additionalFlows,
|
|
45269
45674
|
interprocedural: interProc.interprocedural
|
|
45270
45675
|
};
|
|
45676
|
+
if (options.speculativeParamSources === true) {
|
|
45677
|
+
const existingFlowKeys = new Set((taint.flows ?? []).map((f) => `${f.source_line}|${f.sink_line}|${f.sink_type}`));
|
|
45678
|
+
const constructionFlows = detectPromptConstructionFlows(code.split(`
|
|
45679
|
+
`), types).filter((f) => !existingFlowKeys.has(`${f.source_line}|${f.sink_line}|${f.sink_type}`));
|
|
45680
|
+
if (constructionFlows.length > 0) {
|
|
45681
|
+
taint.flows = [...taint.flows ?? [], ...constructionFlows];
|
|
45682
|
+
}
|
|
45683
|
+
}
|
|
45271
45684
|
if (taint.flows && taint.flows.length > 0 && taint.sinks.length > 0) {
|
|
45272
45685
|
const sinkTagsByKey = new Map;
|
|
45273
45686
|
for (const s of taint.sinks) {
|
|
@@ -46079,7 +46492,7 @@ var colors = {
|
|
|
46079
46492
|
};
|
|
46080
46493
|
|
|
46081
46494
|
// src/version.ts
|
|
46082
|
-
var version = "3.
|
|
46495
|
+
var version = "3.194.0";
|
|
46083
46496
|
|
|
46084
46497
|
// src/formatters.ts
|
|
46085
46498
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.194.0",
|
|
4
4
|
"description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@cognium/project-profile-detect": "^1.1.0",
|
|
69
|
-
"circle-ir": "^3.
|
|
69
|
+
"circle-ir": "^3.194.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|