circle-ir 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.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * PromptInjectionSafetyGatePass — cognium-dev #267
3
+ *
4
+ * Drops `prompt_injection` (CWE-1427) sinks whose untrusted content is
5
+ * *delimiter-wrapped* — enclosed in matched boundary markers
6
+ * (`<user_question>…</user_question>`, fences) that keep it as data rather
7
+ * than instructions. Removes the false positive on the delimiter-wrapped
8
+ * "safe mirror" of the OWASP-LLM LLM01 probe pairs while leaving genuine
9
+ * prompt-injection flows untouched.
10
+ *
11
+ * The engine already fires `prompt_injection` when tainted input reaches an
12
+ * LLM-client call (#248); the gap this closes is precision on the wrapped
13
+ * safe-mitigation shape.
14
+ *
15
+ * Scope decision — *role separation alone is NOT treated as a sanitizer*
16
+ * here: `{"role":"user","content":untrusted}` continues to fire, matching
17
+ * #248's must-fire contract (untrusted reaching an LLM prompt is a finding
18
+ * even in the user channel). Only affirmative delimiter-wrapping suppresses.
19
+ *
20
+ * Conservative by construction: a sink is dropped ONLY when EVERY dynamic
21
+ * content placement is delimiter-wrapped and none lands in the instruction
22
+ * channel (system/assistant role, or instruction concat/interpolation). Any
23
+ * ambiguity — unparsed value, standalone value, dynamic+dynamic concat —
24
+ * keeps the sink (no recall loss). Runs after SinkFilterPass and before
25
+ * TaintPropagationPass so flow generators never see a dropped sink.
26
+ *
27
+ * Builder-pattern aware: when the request is constructed in a prior
28
+ * statement (Go/Java `req := ChatCompletionRequest{Messages: …}`) and passed
29
+ * as a bare arg, the gate traces the builder var(s) to see the message
30
+ * structure.
31
+ *
32
+ * Pillar I note: `prompt_injection` / CWE-1427 is a deterministic taint
33
+ * category (#248). This gate is pure structural analysis of the call site —
34
+ * no model invocation, no LLM concepts in its API surface.
35
+ */
36
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
37
+ export interface PromptInjectionSafetyGateResult {
38
+ droppedSafe: number;
39
+ }
40
+ /**
41
+ * A string literal is a *delimiter* (structural boundary marker) rather
42
+ * than *instruction text* when it is a short tag/fence-like token:
43
+ * `<user_question>`, `</user_question>`, `[data]`, ```` ``` ````, `"""`,
44
+ * `---`, `###`, `<<<`. Instruction literals ("Follow policy.") contain free
45
+ * words and do not match.
46
+ */
47
+ export declare function isDelimiterLiteral(lit: string): boolean;
48
+ /**
49
+ * Scan a balanced expression starting at `start`, stopping at a top-level
50
+ * `,` or a closing `) ] }` that closes the enclosing structure. Tracks
51
+ * bracket depth and string state so `}`/`)` inside strings (f-string
52
+ * `{user}`, template `${user}`) or nested calls do not truncate the value.
53
+ */
54
+ export declare function scanValue(code: string, start: number): string;
55
+ /** `+`-concat mixing a dynamic value with a NON-delimiter string literal. */
56
+ export declare function isInstructionConcat(valueExpr: string): boolean;
57
+ /**
58
+ * Classify a prompt-injection call site.
59
+ * 'unsafe' — untrusted in the instruction channel, or mixed into instructions
60
+ * 'safe' — every dynamic content is standalone role-separated or delimiter-wrapped
61
+ * 'unknown' — no confident classification (kept)
62
+ */
63
+ export declare function classifyPromptCall(callCode: string, codeLines: string[]): 'unsafe' | 'safe' | 'unknown';
64
+ /**
65
+ * Classify a prompt-injection sink, first on the call line (Python/JS inline
66
+ * message shape) and — when that is inconclusive — over the builder region
67
+ * for the call's arg vars (Go/Java builder shape).
68
+ */
69
+ export declare function classifyPromptSink(callCode: string, codeLines: string[]): 'unsafe' | 'safe' | 'unknown';
70
+ export declare class PromptInjectionSafetyGatePass implements AnalysisPass<PromptInjectionSafetyGateResult> {
71
+ readonly name = "prompt-injection-safety-gate";
72
+ readonly category: "security";
73
+ run(ctx: PassContext): PromptInjectionSafetyGateResult;
74
+ }
75
+ //# sourceMappingURL=prompt-injection-safety-gate-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-injection-safety-gate-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/prompt-injection-safety-gate-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAI9E,MAAM,WAAW,+BAA+B;IAC9C,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAYvD;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CA2B7D;AAiBD,6EAA6E;AAC7E,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAM9D;AAkFD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EAAE,GAClB,QAAQ,GAAG,MAAM,GAAG,SAAS,CAwB/B;AA8DD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EAAE,GAClB,QAAQ,GAAG,MAAM,GAAG,SAAS,CAM/B;AAED,qBAAa,6BACX,YAAW,YAAY,CAAC,+BAA+B,CAAC;IAExD,QAAQ,CAAC,IAAI,kCAAkC;IAC/C,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,+BAA+B;CA4BvD"}
@@ -0,0 +1,353 @@
1
+ /**
2
+ * PromptInjectionSafetyGatePass — cognium-dev #267
3
+ *
4
+ * Drops `prompt_injection` (CWE-1427) sinks whose untrusted content is
5
+ * *delimiter-wrapped* — enclosed in matched boundary markers
6
+ * (`<user_question>…</user_question>`, fences) that keep it as data rather
7
+ * than instructions. Removes the false positive on the delimiter-wrapped
8
+ * "safe mirror" of the OWASP-LLM LLM01 probe pairs while leaving genuine
9
+ * prompt-injection flows untouched.
10
+ *
11
+ * The engine already fires `prompt_injection` when tainted input reaches an
12
+ * LLM-client call (#248); the gap this closes is precision on the wrapped
13
+ * safe-mitigation shape.
14
+ *
15
+ * Scope decision — *role separation alone is NOT treated as a sanitizer*
16
+ * here: `{"role":"user","content":untrusted}` continues to fire, matching
17
+ * #248's must-fire contract (untrusted reaching an LLM prompt is a finding
18
+ * even in the user channel). Only affirmative delimiter-wrapping suppresses.
19
+ *
20
+ * Conservative by construction: a sink is dropped ONLY when EVERY dynamic
21
+ * content placement is delimiter-wrapped and none lands in the instruction
22
+ * channel (system/assistant role, or instruction concat/interpolation). Any
23
+ * ambiguity — unparsed value, standalone value, dynamic+dynamic concat —
24
+ * keeps the sink (no recall loss). Runs after SinkFilterPass and before
25
+ * TaintPropagationPass so flow generators never see a dropped sink.
26
+ *
27
+ * Builder-pattern aware: when the request is constructed in a prior
28
+ * statement (Go/Java `req := ChatCompletionRequest{Messages: …}`) and passed
29
+ * as a bare arg, the gate traces the builder var(s) to see the message
30
+ * structure.
31
+ *
32
+ * Pillar I note: `prompt_injection` / CWE-1427 is a deterministic taint
33
+ * category (#248). This gate is pure structural analysis of the call site —
34
+ * no model invocation, no LLM concepts in its API surface.
35
+ */
36
+ /**
37
+ * A string literal is a *delimiter* (structural boundary marker) rather
38
+ * than *instruction text* when it is a short tag/fence-like token:
39
+ * `<user_question>`, `</user_question>`, `[data]`, ```` ``` ````, `"""`,
40
+ * `---`, `###`, `<<<`. Instruction literals ("Follow policy.") contain free
41
+ * words and do not match.
42
+ */
43
+ export function isDelimiterLiteral(lit) {
44
+ const s = lit.trim();
45
+ if (s.length === 0)
46
+ return true;
47
+ return (/^<\/?[\w.-]+\s*\/?>$/.test(s) ||
48
+ /^\[\/?[\w.-]+\]$/.test(s) ||
49
+ /^`{1,3}[\w-]*$/.test(s) ||
50
+ /^"""[\w-]*$/.test(s) ||
51
+ /^[#*=_~-]{2,}$/.test(s) ||
52
+ /^<{2,}[\w-]*$/.test(s) ||
53
+ /^[\w-]*>{2,}$/.test(s));
54
+ }
55
+ /**
56
+ * Scan a balanced expression starting at `start`, stopping at a top-level
57
+ * `,` or a closing `) ] }` that closes the enclosing structure. Tracks
58
+ * bracket depth and string state so `}`/`)` inside strings (f-string
59
+ * `{user}`, template `${user}`) or nested calls do not truncate the value.
60
+ */
61
+ export function scanValue(code, start) {
62
+ let depth = 0;
63
+ let str = null;
64
+ let out = '';
65
+ for (let i = start; i < code.length; i++) {
66
+ const c = code[i];
67
+ if (str) {
68
+ out += c;
69
+ if (c === str && code[i - 1] !== '\\')
70
+ str = null;
71
+ continue;
72
+ }
73
+ if (c === '"' || c === "'" || c === '`') {
74
+ str = c;
75
+ out += c;
76
+ continue;
77
+ }
78
+ if (c === '(' || c === '[' || c === '{') {
79
+ depth++;
80
+ out += c;
81
+ continue;
82
+ }
83
+ if (c === ')' || c === ']' || c === '}') {
84
+ if (depth === 0)
85
+ break;
86
+ depth--;
87
+ out += c;
88
+ continue;
89
+ }
90
+ if (c === ',' && depth === 0)
91
+ break;
92
+ out += c;
93
+ }
94
+ return out.trim();
95
+ }
96
+ function stringLiterals(expr) {
97
+ return [...expr.matchAll(/"([^"]*)"|'([^']*)'/g)].map(m => m[1] ?? m[2] ?? '');
98
+ }
99
+ /** Reference to a bare identifier (a dynamic value) once literals removed. */
100
+ function hasIdentifier(expr) {
101
+ return /[A-Za-z_$][\w$]*/.test(expr.replace(/"[^"]*"|'[^']*'|`[^`]*`/g, ' '));
102
+ }
103
+ /** A content value that is entirely a string literal (no dynamic part). */
104
+ function isPureLiteral(valueExpr) {
105
+ const stripped = valueExpr.replace(/"[^"]*"|'[^']*'|`[^`]*`/g, ' ');
106
+ return !/[A-Za-z_$][\w$]*/.test(stripped);
107
+ }
108
+ /** `+`-concat mixing a dynamic value with a NON-delimiter string literal. */
109
+ export function isInstructionConcat(valueExpr) {
110
+ if (!valueExpr.includes('+'))
111
+ return false;
112
+ if (!hasIdentifier(valueExpr))
113
+ return false;
114
+ const lits = stringLiterals(valueExpr);
115
+ if (lits.length === 0)
116
+ return false;
117
+ return !lits.every(isDelimiterLiteral);
118
+ }
119
+ /**
120
+ * f-string / template literal that mixes untrusted interpolation with free
121
+ * instruction text: `f"Follow policy. {user}"` / `` `Follow policy. ${user}` ``.
122
+ * Pure interpolation (`f"{user}"`) is not instruction-mixing.
123
+ */
124
+ function isMixedTemplate(v) {
125
+ const fstr = v.match(/\bf(["'])((?:\\.|(?!\1).)*)\1/);
126
+ if (fstr) {
127
+ const body = fstr[2];
128
+ if (/\{[^}]+\}/.test(body) && /\w/.test(body.replace(/\{[^}]*\}/g, '')))
129
+ return true;
130
+ }
131
+ const tmpl = v.match(/`([^`]*)`/);
132
+ if (tmpl) {
133
+ const body = tmpl[1];
134
+ if (/\$\{[^}]+\}/.test(body) && /\w/.test(body.replace(/\$\{[^}]*\}/g, '')))
135
+ return true;
136
+ }
137
+ return false;
138
+ }
139
+ /**
140
+ * Assignment-operator matcher for `<var> := …` / `<var> = …`, excluding the
141
+ * comparisons `==` / `<=` / `>=` / `!=`. `:=` is matched directly; a plain
142
+ * `=` must not be preceded by `=<>!` nor followed by `=`.
143
+ */
144
+ function assignmentRe(varName, flags = '') {
145
+ return new RegExp(`\\b${varName}\\s*(?::=|(?<![=<>!])=(?!=))\\s*`, flags);
146
+ }
147
+ /** Resolve a bare-identifier content value one hop back to its assignment RHS. */
148
+ function resolveAssignmentRHS(varName, codeLines) {
149
+ const re = assignmentRe(varName);
150
+ for (const line of codeLines) {
151
+ const m = line.match(re);
152
+ if (m && m.index !== undefined) {
153
+ return scanValue(line, m.index + m[0].length);
154
+ }
155
+ }
156
+ return undefined;
157
+ }
158
+ /**
159
+ * Classify a fully-scanned content expression.
160
+ * 'safe' — standalone dynamic value or delimiter-wrapped concat
161
+ * 'unsafe' — instruction concat / instruction-mixing template
162
+ * 'unknown' — dynamic+dynamic concat / unrecognized — kept
163
+ */
164
+ function classifyExpr(v) {
165
+ if (isMixedTemplate(v))
166
+ return 'unsafe';
167
+ if (v.includes('+')) {
168
+ if (isInstructionConcat(v))
169
+ return 'unsafe';
170
+ const lits = stringLiterals(v);
171
+ if (lits.length > 0 && lits.every(isDelimiterLiteral) && hasIdentifier(v))
172
+ return 'safe';
173
+ return 'unknown'; // dynamic+dynamic / complex — never claim safe
174
+ }
175
+ // No '+', no instruction-mixing template: a standalone value placed as
176
+ // the whole content. Whether this is "safe" (role-separation as the
177
+ // mitigation) is a security-semantics choice that conflicts with #248's
178
+ // must-fire contract — left as 'unknown' (kept) pending that decision;
179
+ // only delimiter-wrapping is treated as an affirmative sanitizer.
180
+ return 'unknown';
181
+ }
182
+ /**
183
+ * Classify one dynamic content placement, resolving a bare identifier one
184
+ * hop back so `wrapped = "<q>"+x+"</q>"; content: wrapped` and
185
+ * `bad = "instr "+x; content: bad` are judged on their construction.
186
+ */
187
+ function classifyContentPlacement(val, codeLines) {
188
+ const v = val.trim();
189
+ if (/^[A-Za-z_$][\w$]*$/.test(v)) {
190
+ const rhs = resolveAssignmentRHS(v, codeLines);
191
+ // Unresolved bare identifier (param, source var, or local whose
192
+ // construction we can't see) — standalone/role-separated placement,
193
+ // which is NOT an affirmative sanitizer (kept; see scope note).
194
+ if (rhs === undefined)
195
+ return 'unknown';
196
+ return classifyExpr(rhs);
197
+ }
198
+ return classifyExpr(v);
199
+ }
200
+ /**
201
+ * Classify a prompt-injection call site.
202
+ * 'unsafe' — untrusted in the instruction channel, or mixed into instructions
203
+ * 'safe' — every dynamic content is standalone role-separated or delimiter-wrapped
204
+ * 'unknown' — no confident classification (kept)
205
+ */
206
+ export function classifyPromptCall(callCode, codeLines) {
207
+ const contentRe = /["']?[Cc]ontent["']?\s*[:=]\s*/g;
208
+ let m;
209
+ let sawDynamic = false;
210
+ let allSafe = true;
211
+ while ((m = contentRe.exec(callCode)) !== null) {
212
+ const val = scanValue(callCode, m.index + m[0].length);
213
+ if (val === '' || isPureLiteral(val))
214
+ continue;
215
+ sawDynamic = true;
216
+ // Role context: nearest preceding `role: "..."` in the same object.
217
+ const ctx = callCode.slice(Math.max(0, m.index - 80), m.index);
218
+ const roleM = ctx.match(/["']?[Rr]ole["']?\s*[:=]\s*["'](\w+)["'][^"']*$/);
219
+ const role = roleM ? roleM[1].toLowerCase() : 'user';
220
+ if (role === 'system' || role === 'assistant')
221
+ return 'unsafe';
222
+ const placement = classifyContentPlacement(val, codeLines);
223
+ if (placement === 'unsafe')
224
+ return 'unsafe';
225
+ if (placement !== 'safe')
226
+ allSafe = false;
227
+ }
228
+ if (!sawDynamic)
229
+ return 'unknown';
230
+ return allSafe ? 'safe' : 'unknown';
231
+ }
232
+ /**
233
+ * Scan a statement RHS across lines, stopping at a top-level newline (Go/
234
+ * builder style) or a closing bracket that ends the expression — so a
235
+ * multi-line composite literal `T{ … }` is captured whole. Tracks bracket
236
+ * depth and string state.
237
+ */
238
+ function scanRhsAcrossLines(text, start) {
239
+ let depth = 0;
240
+ let str = null;
241
+ let out = '';
242
+ for (let i = start; i < text.length; i++) {
243
+ const c = text[i];
244
+ if (str) {
245
+ out += c;
246
+ if (c === str && text[i - 1] !== '\\')
247
+ str = null;
248
+ continue;
249
+ }
250
+ if (c === '"' || c === "'" || c === '`') {
251
+ str = c;
252
+ out += c;
253
+ continue;
254
+ }
255
+ if (c === '(' || c === '[' || c === '{') {
256
+ depth++;
257
+ out += c;
258
+ continue;
259
+ }
260
+ if (c === ')' || c === ']' || c === '}') {
261
+ if (depth === 0)
262
+ break;
263
+ depth--;
264
+ out += c;
265
+ continue;
266
+ }
267
+ if (c === '\n' && depth === 0)
268
+ break;
269
+ out += c;
270
+ }
271
+ return out.trim();
272
+ }
273
+ /** Full multi-line RHS of `var := …` / `var = …`. */
274
+ function resolveBuilderRhs(varName, joined) {
275
+ const re = assignmentRe(varName, 'g');
276
+ const m = re.exec(joined);
277
+ if (m)
278
+ return scanRhsAcrossLines(joined, m.index + m[0].length);
279
+ return undefined;
280
+ }
281
+ /**
282
+ * Builder-pattern support (Go/Java): when the request is built in a prior
283
+ * statement (`req := ChatCompletionRequest{Messages: …}`) and passed as a
284
+ * bare arg to the client call, the role/content structure is not on the
285
+ * sink line. Gather the builder text for each identifier arg of the call,
286
+ * recursively inlining referenced builder vars (bounded), so the classifier
287
+ * can see the message structure. Returns the concatenated builder region.
288
+ */
289
+ function collectBuilderRegion(callCode, joined) {
290
+ const parts = [];
291
+ const seen = new Set();
292
+ const stack = [...callCode.matchAll(/[A-Za-z_$][\w$]*/g)].map(x => x[0]);
293
+ let budget = 40;
294
+ while (stack.length > 0 && budget-- > 0) {
295
+ const v = stack.pop();
296
+ if (seen.has(v))
297
+ continue;
298
+ seen.add(v);
299
+ const rhs = resolveBuilderRhs(v, joined);
300
+ if (rhs) {
301
+ parts.push(rhs);
302
+ for (const mm of rhs.matchAll(/[A-Za-z_$][\w$]*/g))
303
+ stack.push(mm[0]);
304
+ }
305
+ }
306
+ return parts.join('\n');
307
+ }
308
+ /**
309
+ * Classify a prompt-injection sink, first on the call line (Python/JS inline
310
+ * message shape) and — when that is inconclusive — over the builder region
311
+ * for the call's arg vars (Go/Java builder shape).
312
+ */
313
+ export function classifyPromptSink(callCode, codeLines) {
314
+ const direct = classifyPromptCall(callCode, codeLines);
315
+ if (direct !== 'unknown')
316
+ return direct;
317
+ const region = collectBuilderRegion(callCode, codeLines.join('\n'));
318
+ if (!region)
319
+ return 'unknown';
320
+ return classifyPromptCall(region, codeLines);
321
+ }
322
+ export class PromptInjectionSafetyGatePass {
323
+ name = 'prompt-injection-safety-gate';
324
+ category = 'security';
325
+ run(ctx) {
326
+ const { graph, code } = ctx;
327
+ const sinks = ctx.hasResult('sink-filter')
328
+ ? ctx.getResult('sink-filter').sinks
329
+ : graph.ir.taint.sinks;
330
+ if (sinks.length === 0)
331
+ return { droppedSafe: 0 };
332
+ const codeLines = code.split('\n');
333
+ let droppedSafe = 0;
334
+ const kept = sinks.filter(sink => {
335
+ if (sink.type !== 'prompt_injection')
336
+ return true;
337
+ const callCode = sink.code ?? codeLines[sink.line - 1] ?? '';
338
+ if (!callCode)
339
+ return true;
340
+ if (classifyPromptSink(callCode, codeLines) === 'safe') {
341
+ droppedSafe++;
342
+ return false;
343
+ }
344
+ return true;
345
+ });
346
+ if (droppedSafe > 0) {
347
+ sinks.length = 0;
348
+ sinks.push(...kept);
349
+ }
350
+ return { droppedSafe };
351
+ }
352
+ }
353
+ //# sourceMappingURL=prompt-injection-safety-gate-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-injection-safety-gate-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/prompt-injection-safety-gate-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * SpeculativePromptParamSourcePass — cognium-dev #267
3
+ *
4
+ * OPT-IN, OFF-BY-DEFAULT. When `AnalyzerOptions.speculativeParamSources` is
5
+ * set, seeds function parameters as untrusted sources within functions that
6
+ * already contain a `prompt_injection` (CWE-1427) sink. This unblocks
7
+ * library / agent / SDK code whose untrusted entry point is a bare
8
+ * parameter (`func Ask(user string, client *LLM)`) — there is no in-file
9
+ * request source to seed taint, so the param → prompt-sink flow never fires
10
+ * under the deterministic default.
11
+ *
12
+ * This is a deliberately speculative, higher-FP heuristic. It is gated
13
+ * behind an explicit flag that defaults OFF, so the precision-sensitive
14
+ * benchmarks (OWASP / Juliet / SecuriBench) — which never set it — are
15
+ * unaffected and the 0%-FPR moat is preserved. Scope is bounded to
16
+ * parameters of functions containing a prompt-construction sink, not every
17
+ * function parameter.
18
+ *
19
+ * Runs after the sink gates (so the prompt_injection sink set is final) and
20
+ * before TaintPropagationPass, appending to the sink-filter source set so
21
+ * the normal flow generators connect param → sink. Pillar-I clean: no model
22
+ * invocation, no LLM identifiers in its surface (`prompt_injection` /
23
+ * CWE-1427 is the established deterministic taint category, #248).
24
+ */
25
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
26
+ import type { TaintFlowInfo, TypeInfo } from '../../types/index.js';
27
+ /**
28
+ * Heuristic: does a string literal read like LLM *prompt / instruction*
29
+ * text rather than an incidental separator or short token? Used to keep the
30
+ * client-less construction detector off ordinary string concatenation
31
+ * (`"/" + name`, `"Hello, " + name`) that structurally looks identical but
32
+ * is not a prompt. Fires on instruction phrasing or a full sentence.
33
+ */
34
+ export declare function looksLikePromptText(lit: string): boolean;
35
+ /**
36
+ * Detect *client-less* prompt-construction flows (cognium-dev #267,
37
+ * speculative). A function that returns or assigns an instruction-literal
38
+ * concatenated with one of its own parameters builds a prompt from
39
+ * untrusted input even without an LLM-client call
40
+ * (`func BuildPrompt(user string) string { return "You are a bot. " + user }`).
41
+ * Emitted directly as `prompt_injection` (CWE-1427) flows — there is no
42
+ * call sink for the flow generators to match. Delimiter-wrapped concats are
43
+ * NOT instruction-concat, so the safe mirror produces no flow.
44
+ *
45
+ * Off-default: only invoked when `speculativeParamSources` is set (the
46
+ * benchmarks never enable it).
47
+ */
48
+ export declare function detectPromptConstructionFlows(codeLines: string[], types: TypeInfo[]): TaintFlowInfo[];
49
+ export interface SpeculativePromptParamSourceResult {
50
+ added: number;
51
+ }
52
+ /**
53
+ * A parameter is a plausible untrusted-text entry point when its type is
54
+ * unknown (untyped languages — Python/JS) or string-ish. Handle/dependency
55
+ * params (a client, context, writer, DB, logger — typically pointer or
56
+ * well-known infrastructure types) are skipped to keep the speculative FP
57
+ * surface off obviously-non-data parameters.
58
+ */
59
+ export declare function looksLikeTextParam(type: string | null | undefined): boolean;
60
+ export declare class SpeculativePromptParamSourcePass implements AnalysisPass<SpeculativePromptParamSourceResult> {
61
+ private readonly enabled;
62
+ readonly name = "speculative-prompt-param-source";
63
+ readonly category: "security";
64
+ constructor(enabled: boolean);
65
+ run(ctx: PassContext): SpeculativePromptParamSourceResult;
66
+ }
67
+ //# sourceMappingURL=speculative-prompt-param-source-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speculative-prompt-param-source-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/speculative-prompt-param-source-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE9E,OAAO,KAAK,EAA0B,aAAa,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAiB5F;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CASxD;AAOD;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EAAE,EACnB,KAAK,EAAE,QAAQ,EAAE,GAChB,aAAa,EAAE,CAqCjB;AAED,MAAM,WAAW,kCAAkC;IACjD,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAU3E;AAED,qBAAa,gCACX,YAAW,YAAY,CAAC,kCAAkC,CAAC;IAK/C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,QAAQ,CAAC,IAAI,qCAAqC;IAClD,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;gBAEX,OAAO,EAAE,OAAO;IAE7C,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,kCAAkC;CAqD1D"}