@kernlang/review 2.0.0 → 3.1.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.
Files changed (118) hide show
  1. package/dist/concept-rules/boundary-mutation.d.ts +13 -0
  2. package/dist/concept-rules/boundary-mutation.js +40 -0
  3. package/dist/concept-rules/boundary-mutation.js.map +1 -0
  4. package/dist/concept-rules/ignored-error.d.ts +13 -0
  5. package/dist/concept-rules/ignored-error.js +40 -0
  6. package/dist/concept-rules/ignored-error.js.map +1 -0
  7. package/dist/concept-rules/illegal-dependency.d.ts +13 -0
  8. package/dist/concept-rules/illegal-dependency.js +49 -0
  9. package/dist/concept-rules/illegal-dependency.js.map +1 -0
  10. package/dist/concept-rules/index.d.ts +15 -0
  11. package/dist/concept-rules/index.js +27 -0
  12. package/dist/concept-rules/index.js.map +1 -0
  13. package/dist/concept-rules/unguarded-effect.d.ts +13 -0
  14. package/dist/concept-rules/unguarded-effect.js +58 -0
  15. package/dist/concept-rules/unguarded-effect.js.map +1 -0
  16. package/dist/concept-rules/unrecovered-effect.d.ts +13 -0
  17. package/dist/concept-rules/unrecovered-effect.js +61 -0
  18. package/dist/concept-rules/unrecovered-effect.js.map +1 -0
  19. package/dist/confidence.d.ts +92 -0
  20. package/dist/confidence.js +263 -0
  21. package/dist/confidence.js.map +1 -0
  22. package/dist/differ.js +4 -2
  23. package/dist/differ.js.map +1 -1
  24. package/dist/external-tools.js +7 -3
  25. package/dist/external-tools.js.map +1 -1
  26. package/dist/file-role.d.ts +10 -0
  27. package/dist/file-role.js +80 -0
  28. package/dist/file-role.js.map +1 -0
  29. package/dist/graph.d.ts +11 -0
  30. package/dist/graph.js +152 -0
  31. package/dist/graph.js.map +1 -0
  32. package/dist/index.d.ts +46 -3
  33. package/dist/index.js +313 -27
  34. package/dist/index.js.map +1 -1
  35. package/dist/inferrer.js +123 -25
  36. package/dist/inferrer.js.map +1 -1
  37. package/dist/kern-lint.d.ts +18 -0
  38. package/dist/kern-lint.js +24 -0
  39. package/dist/kern-lint.js.map +1 -0
  40. package/dist/llm-bridge.d.ts +42 -0
  41. package/dist/llm-bridge.js +176 -0
  42. package/dist/llm-bridge.js.map +1 -0
  43. package/dist/llm-review.d.ts +8 -1
  44. package/dist/llm-review.js +20 -7
  45. package/dist/llm-review.js.map +1 -1
  46. package/dist/mappers/ts-concepts.d.ts +9 -0
  47. package/dist/mappers/ts-concepts.js +518 -0
  48. package/dist/mappers/ts-concepts.js.map +1 -0
  49. package/dist/quality-rules.d.ts +3 -3
  50. package/dist/quality-rules.js +3 -11
  51. package/dist/quality-rules.js.map +1 -1
  52. package/dist/reporter.d.ts +19 -3
  53. package/dist/reporter.js +232 -20
  54. package/dist/reporter.js.map +1 -1
  55. package/dist/rules/base.js +167 -15
  56. package/dist/rules/base.js.map +1 -1
  57. package/dist/rules/confidence.d.ts +37 -0
  58. package/dist/rules/confidence.js +159 -0
  59. package/dist/rules/confidence.js.map +1 -0
  60. package/dist/rules/dead-logic.d.ts +13 -0
  61. package/dist/rules/dead-logic.js +393 -0
  62. package/dist/rules/dead-logic.js.map +1 -0
  63. package/dist/rules/express.js +69 -2
  64. package/dist/rules/express.js.map +1 -1
  65. package/dist/rules/ground-layer.d.ts +23 -0
  66. package/dist/rules/ground-layer.js +132 -0
  67. package/dist/rules/ground-layer.js.map +1 -0
  68. package/dist/rules/index.d.ts +1 -1
  69. package/dist/rules/index.js +8 -2
  70. package/dist/rules/index.js.map +1 -1
  71. package/dist/rules/kern-source.d.ts +16 -0
  72. package/dist/rules/kern-source.js +726 -0
  73. package/dist/rules/kern-source.js.map +1 -0
  74. package/dist/rules/nextjs.js +38 -10
  75. package/dist/rules/nextjs.js.map +1 -1
  76. package/dist/rules/null-safety.d.ts +12 -0
  77. package/dist/rules/null-safety.js +123 -0
  78. package/dist/rules/null-safety.js.map +1 -0
  79. package/dist/rules/react.js +64 -1
  80. package/dist/rules/react.js.map +1 -1
  81. package/dist/rules/security-v2.d.ts +12 -0
  82. package/dist/rules/security-v2.js +415 -0
  83. package/dist/rules/security-v2.js.map +1 -0
  84. package/dist/rules/security-v3.d.ts +12 -0
  85. package/dist/rules/security-v3.js +397 -0
  86. package/dist/rules/security-v3.js.map +1 -0
  87. package/dist/rules/security-v4.d.ts +22 -0
  88. package/dist/rules/security-v4.js +688 -0
  89. package/dist/rules/security-v4.js.map +1 -0
  90. package/dist/rules/security.d.ts +12 -0
  91. package/dist/rules/security.js +286 -0
  92. package/dist/rules/security.js.map +1 -0
  93. package/dist/rules/utils.d.ts +7 -0
  94. package/dist/rules/utils.js +21 -0
  95. package/dist/rules/utils.js.map +1 -0
  96. package/dist/rules/vue.js +1 -1
  97. package/dist/rules/vue.js.map +1 -1
  98. package/dist/spec-checker.d.ts +83 -0
  99. package/dist/spec-checker.js +405 -0
  100. package/dist/spec-checker.js.map +1 -0
  101. package/dist/suppression/apply-suppression.d.ts +17 -0
  102. package/dist/suppression/apply-suppression.js +94 -0
  103. package/dist/suppression/apply-suppression.js.map +1 -0
  104. package/dist/suppression/index.d.ts +6 -0
  105. package/dist/suppression/index.js +6 -0
  106. package/dist/suppression/index.js.map +1 -0
  107. package/dist/suppression/parse-directives.d.ts +25 -0
  108. package/dist/suppression/parse-directives.js +161 -0
  109. package/dist/suppression/parse-directives.js.map +1 -0
  110. package/dist/suppression/types.d.ts +32 -0
  111. package/dist/suppression/types.js +5 -0
  112. package/dist/suppression/types.js.map +1 -0
  113. package/dist/taint.d.ts +115 -0
  114. package/dist/taint.js +1052 -0
  115. package/dist/taint.js.map +1 -0
  116. package/dist/types.d.ts +71 -0
  117. package/dist/types.js.map +1 -1
  118. package/package.json +7 -4
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Security v3 rules — OWASP gap closure.
3
+ *
4
+ * Covers: Regex DoS, missing input validation, prototype pollution,
5
+ * information exposure (stack traces in responses).
6
+ *
7
+ * All AST-based. Always active regardless of target.
8
+ */
9
+ import { SyntaxKind } from 'ts-morph';
10
+ import { createFingerprint } from '../types.js';
11
+ function span(file, line, col = 1) {
12
+ return { file, startLine: line, startCol: col, endLine: line, endCol: col };
13
+ }
14
+ function finding(ruleId, severity, category, message, file, line, extra) {
15
+ return {
16
+ source: 'kern',
17
+ ruleId,
18
+ severity,
19
+ category,
20
+ message,
21
+ primarySpan: span(file, line),
22
+ fingerprint: createFingerprint(ruleId, line, 1),
23
+ ...extra,
24
+ };
25
+ }
26
+ // ── Rule S9: regex-dos ────────────────────────────────────────────────
27
+ // Regex literals with nested quantifiers or unbounded repetition that
28
+ // can cause catastrophic backtracking (ReDoS).
29
+ //
30
+ // Detects: (a+)+, (a|a)+, (.*a){n}, nested quantifiers in groups
31
+ // CWE-1333
32
+ /**
33
+ * Check if a regex pattern contains ReDoS-vulnerable constructs.
34
+ * Looks for: nested quantifiers, overlapping alternation with quantifiers,
35
+ * and ambiguous repetition patterns.
36
+ */
37
+ function isReDoSVulnerable(pattern) {
38
+ // Nested quantifiers: (x+)+ , (x*)* , (x+)* , (x*)+, (x{n,})+ etc.
39
+ // These cause exponential backtracking
40
+ if (/\([^)]*[+*]\)[+*{]/.test(pattern)) {
41
+ return 'nested quantifier — causes exponential backtracking';
42
+ }
43
+ // Quantified group containing alternation with overlap: (a|a)+, (a|ab)+
44
+ // Simplified: group with | and outer quantifier
45
+ if (/\([^)]*\|[^)]*\)[+*]{1,2}/.test(pattern)) {
46
+ // Check for character overlap in alternation branches
47
+ const groupMatch = pattern.match(/\(([^)]*\|[^)]*)\)[+*]/);
48
+ if (groupMatch) {
49
+ const branches = groupMatch[1].split('|');
50
+ if (branches.length >= 2) {
51
+ const first = branches[0].replace(/[+*?{}\\[\]()]/g, '');
52
+ const second = branches[1].replace(/[+*?{}\\[\]()]/g, '');
53
+ // If branches share starting characters, it's ambiguous
54
+ if (first.length > 0 && second.length > 0 && first[0] === second[0]) {
55
+ return 'overlapping alternation with quantifier — ambiguous matching causes backtracking';
56
+ }
57
+ }
58
+ }
59
+ }
60
+ // Quantified group with .* or .+ inside: (.*something)+ or (.+x){2,}
61
+ if (/\([^)]*\.\*[^)]*\)[+*{]/.test(pattern) || /\([^)]*\.\+[^)]*\)[+*{]/.test(pattern)) {
62
+ return '.* or .+ inside quantified group — unbounded matching causes backtracking';
63
+ }
64
+ // Adjacent overlapping quantifiers without separator: \s*\s*, \d+\d+
65
+ if (/\\[dswDSW][+*]\\[dswDSW][+*]/.test(pattern)) {
66
+ return 'adjacent overlapping quantifiers — ambiguous boundary causes backtracking';
67
+ }
68
+ return null;
69
+ }
70
+ function regexDos(ctx) {
71
+ const findings = [];
72
+ // Check regex literals: /pattern/flags
73
+ for (const node of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.RegularExpressionLiteral)) {
74
+ const text = node.getText();
75
+ // Extract pattern (between first / and last /)
76
+ const lastSlash = text.lastIndexOf('/');
77
+ if (lastSlash <= 0)
78
+ continue;
79
+ const pattern = text.slice(1, lastSlash);
80
+ const vulnerability = isReDoSVulnerable(pattern);
81
+ if (vulnerability) {
82
+ findings.push(finding('regex-dos', 'warning', 'bug', `Regex vulnerable to ReDoS: ${vulnerability}`, ctx.filePath, node.getStartLineNumber(), { suggestion: 'Rewrite regex to avoid nested quantifiers, or use a linear-time regex engine (RE2)' }));
83
+ }
84
+ }
85
+ // Check new RegExp('pattern') constructors
86
+ for (const newExpr of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.NewExpression)) {
87
+ if (newExpr.getExpression().getText() !== 'RegExp')
88
+ continue;
89
+ const args = newExpr.getArguments();
90
+ if (args.length === 0)
91
+ continue;
92
+ const firstArg = args[0];
93
+ if (firstArg.getKind() !== SyntaxKind.StringLiteral)
94
+ continue;
95
+ const pattern = firstArg.getLiteralValue();
96
+ const vulnerability = isReDoSVulnerable(pattern);
97
+ if (vulnerability) {
98
+ findings.push(finding('regex-dos', 'warning', 'bug', `RegExp constructor vulnerable to ReDoS: ${vulnerability}`, ctx.filePath, newExpr.getStartLineNumber(), { suggestion: 'Rewrite regex to avoid nested quantifiers, or use a linear-time regex engine (RE2)' }));
99
+ }
100
+ }
101
+ return findings;
102
+ }
103
+ // ── Rule S10: missing-input-validation ────────────────────────────────
104
+ // HTTP handler params (req.body, req.query, req.params) used directly
105
+ // in logic without validation (no schema.parse, no if-check, no
106
+ // parseInt/Number, no sanitize call).
107
+ // CWE-20
108
+ const USER_INPUT_PATTERNS = /req\.(body|query|params|headers)\b/;
109
+ const VALIDATION_PATTERNS = /\.parse\(|\.validate\(|\.safeParse\(|parseInt\(|Number\(|Boolean\(|sanitize|validator\.|zod\.|yup\.|joi\.|ajv\.|superstruct|valibot/;
110
+ function missingInputValidation(ctx) {
111
+ const findings = [];
112
+ // Find Express-style route handlers: (req, res) => { ... } or function(req, res) { ... }
113
+ for (const fn of [
114
+ ...ctx.sourceFile.getDescendantsOfKind(SyntaxKind.ArrowFunction),
115
+ ...ctx.sourceFile.getDescendantsOfKind(SyntaxKind.FunctionExpression),
116
+ ]) {
117
+ const params = fn.getParameters();
118
+ if (params.length < 2)
119
+ continue;
120
+ // Heuristic: first param named req/request, second named res/response
121
+ const firstName = params[0].getName();
122
+ const secondName = params[1].getName();
123
+ if (!/^req(uest)?$/.test(firstName) || !/^res(ponse)?$/.test(secondName))
124
+ continue;
125
+ const body = fn.getBody();
126
+ if (!body)
127
+ continue;
128
+ const bodyText = body.getText();
129
+ // Check if handler uses user input
130
+ if (!USER_INPUT_PATTERNS.test(bodyText))
131
+ continue;
132
+ // Check if any validation is present in the handler
133
+ if (VALIDATION_PATTERNS.test(bodyText))
134
+ continue;
135
+ // Extract which input sources are used
136
+ const sources = [];
137
+ const bodyMatch = bodyText.match(/req\.body/);
138
+ const queryMatch = bodyText.match(/req\.query/);
139
+ const paramsMatch = bodyText.match(/req\.params/);
140
+ if (bodyMatch)
141
+ sources.push('req.body');
142
+ if (queryMatch)
143
+ sources.push('req.query');
144
+ if (paramsMatch)
145
+ sources.push('req.params');
146
+ findings.push(finding('missing-input-validation', 'warning', 'bug', `HTTP handler uses ${sources.join(', ')} without input validation`, ctx.filePath, fn.getStartLineNumber(), { suggestion: 'Validate input with zod, joi, or manual checks before using in business logic' }));
147
+ }
148
+ return findings;
149
+ }
150
+ // ── Rule S11: prototype-pollution ─────────────────────────────────────
151
+ // Object.assign() or spread from unvalidated external input.
152
+ // Detects: Object.assign(target, req.body), { ...req.body }, merge(target, userInput)
153
+ // CWE-1321
154
+ const MERGE_FUNCTIONS = new Set(['merge', 'defaults', 'defaultsDeep', 'extend', 'assign']);
155
+ function prototypePollution(ctx) {
156
+ const findings = [];
157
+ // Object.assign(target, untrustedSource)
158
+ for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
159
+ const callee = call.getExpression();
160
+ // Object.assign(...)
161
+ if (callee.getKind() === SyntaxKind.PropertyAccessExpression) {
162
+ const pa = callee;
163
+ if (pa.getExpression().getText() === 'Object' && pa.getName() === 'assign') {
164
+ const args = call.getArguments();
165
+ // Check if any argument after the first is user input
166
+ for (let i = 1; i < args.length; i++) {
167
+ const text = args[i].getText();
168
+ if (USER_INPUT_PATTERNS.test(text) || /\bJSON\.parse\b/.test(text)) {
169
+ findings.push(finding('prototype-pollution', 'error', 'bug', `Object.assign() with user input (${text.substring(0, 40)}) — prototype pollution risk`, ctx.filePath, call.getStartLineNumber(), { suggestion: 'Use a safe merge utility, or validate/strip __proto__ and constructor keys first' }));
170
+ break;
171
+ }
172
+ }
173
+ }
174
+ }
175
+ // Deep merge utilities: merge(target, userInput), _.defaults(target, userInput)
176
+ if (callee.getKind() === SyntaxKind.Identifier) {
177
+ const funcName = callee.getText();
178
+ if (MERGE_FUNCTIONS.has(funcName)) {
179
+ const args = call.getArguments();
180
+ for (const arg of args) {
181
+ const text = arg.getText();
182
+ if (USER_INPUT_PATTERNS.test(text) || /\bJSON\.parse\b/.test(text)) {
183
+ findings.push(finding('prototype-pollution', 'warning', 'bug', `${funcName}() with user input — potential prototype pollution`, ctx.filePath, call.getStartLineNumber(), { suggestion: 'Validate input keys or use Object.create(null) as target' }));
184
+ break;
185
+ }
186
+ }
187
+ }
188
+ }
189
+ // lodash-style: _.merge, _.defaults, _.defaultsDeep, _.extend
190
+ if (callee.getKind() === SyntaxKind.PropertyAccessExpression) {
191
+ const pa = callee;
192
+ const objText = pa.getExpression().getText();
193
+ if ((objText === '_' || objText === 'lodash') && MERGE_FUNCTIONS.has(pa.getName())) {
194
+ const args = call.getArguments();
195
+ for (const arg of args) {
196
+ const text = arg.getText();
197
+ if (USER_INPUT_PATTERNS.test(text) || /\bJSON\.parse\b/.test(text)) {
198
+ findings.push(finding('prototype-pollution', 'warning', 'bug', `${objText}.${pa.getName()}() with user input — potential prototype pollution`, ctx.filePath, call.getStartLineNumber(), { suggestion: 'Use a prototype-safe merge, or strip __proto__/constructor from input' }));
199
+ break;
200
+ }
201
+ }
202
+ }
203
+ }
204
+ }
205
+ // Spread from user input in object literal: { ...req.body }
206
+ for (const spread of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.SpreadAssignment)) {
207
+ const expr = spread.getExpression();
208
+ const text = expr.getText();
209
+ if (USER_INPUT_PATTERNS.test(text) || /\bJSON\.parse\b/.test(text)) {
210
+ findings.push(finding('prototype-pollution', 'warning', 'bug', `Spread from user input (${text.substring(0, 40)}) — prototype pollution risk if input contains __proto__`, ctx.filePath, spread.getStartLineNumber(), { suggestion: 'Destructure only known fields, or strip __proto__ and constructor keys' }));
211
+ }
212
+ }
213
+ return findings;
214
+ }
215
+ // ── Rule S12: information-exposure ────────────────────────────────────
216
+ // Error responses that include stack traces, internal paths, or
217
+ // unfiltered error objects sent to clients.
218
+ // CWE-209
219
+ function informationExposure(ctx) {
220
+ const findings = [];
221
+ for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
222
+ const callee = call.getExpression();
223
+ if (callee.getKind() !== SyntaxKind.PropertyAccessExpression)
224
+ continue;
225
+ const pa = callee;
226
+ const methodName = pa.getName();
227
+ // res.json(), res.send(), res.status().json()
228
+ const isResponseMethod = methodName === 'json' || methodName === 'send';
229
+ if (!isResponseMethod)
230
+ continue;
231
+ // Walk up to check if this is on a response object (res.json or res.status(N).json)
232
+ let objText = pa.getExpression().getText();
233
+ // Handle chained: res.status(500).json(...)
234
+ if (objText.includes('.status(')) {
235
+ objText = objText.split('.')[0];
236
+ }
237
+ if (!/^res(ponse)?$/.test(objText))
238
+ continue;
239
+ const args = call.getArguments();
240
+ if (args.length === 0)
241
+ continue;
242
+ const argText = args[0].getText();
243
+ // Pattern 1: Sending raw error object — res.json(err), res.json({ error: err })
244
+ // err.stack, err.message with stack included, error.stack
245
+ if (/\.stack\b/.test(argText)) {
246
+ findings.push(finding('information-exposure', 'error', 'bug', 'Stack trace sent in response — exposes internal paths and code structure', ctx.filePath, call.getStartLineNumber(), { suggestion: 'Send a generic error message to clients; log the stack server-side' }));
247
+ continue;
248
+ }
249
+ // Pattern 2: Sending raw error in catch block — res.json(err) or res.send(err)
250
+ // Check if we're inside a catch clause
251
+ let ancestor = call.getParent();
252
+ let inCatch = false;
253
+ let catchVarName = '';
254
+ while (ancestor) {
255
+ if (ancestor.getKind() === SyntaxKind.CatchClause) {
256
+ inCatch = true;
257
+ const clause = ancestor;
258
+ const varDecl = clause.getVariableDeclaration();
259
+ if (varDecl)
260
+ catchVarName = varDecl.getName();
261
+ break;
262
+ }
263
+ ancestor = ancestor.getParent();
264
+ }
265
+ if (inCatch && catchVarName) {
266
+ // Sending the raw error variable: res.json(err), res.json({ error: err })
267
+ const errRef = new RegExp(`\\b${catchVarName}\\b`);
268
+ if (errRef.test(argText)) {
269
+ // Check it's not just err.message (which is often safe)
270
+ const onlyMessage = new RegExp(`^\\{?\\s*\\w+:\\s*${catchVarName}\\.message\\s*\\}?$`);
271
+ if (!onlyMessage.test(argText.trim())) {
272
+ findings.push(finding('information-exposure', 'warning', 'bug', `Raw error object '${catchVarName}' sent in response — may expose stack traces and internal details`, ctx.filePath, call.getStartLineNumber(), { suggestion: `Send only ${catchVarName}.message or a generic error string` }));
273
+ }
274
+ }
275
+ }
276
+ // Pattern 3: process.env or __dirname in response
277
+ if (/process\.env\b/.test(argText) || /\b__dirname\b/.test(argText) || /\b__filename\b/.test(argText)) {
278
+ findings.push(finding('information-exposure', 'warning', 'bug', 'Internal path or environment variable sent in response', ctx.filePath, call.getStartLineNumber(), { suggestion: 'Never expose process.env, __dirname, or __filename to clients' }));
279
+ }
280
+ }
281
+ return findings;
282
+ }
283
+ // ── Rule S13: prompt-injection ─────────────────────────────────────────
284
+ // User input embedded into LLM prompts without sanitization.
285
+ // Detects: template literals or string concatenation that include user input
286
+ // flowing to LLM API calls (generateContent, chat.completions, etc.)
287
+ // CWE-77 (Injection), OWASP LLM01
288
+ /** Known LLM API call patterns */
289
+ const LLM_API_PATTERNS = /\bgenerateContent\b|\bchat\.completions\b|\bcreate\b.*\bmodel\b|\bgenerate\b|\bsendMessage\b|\bcomplete\b/;
290
+ /** Common prompt builder function names */
291
+ const PROMPT_BUILDER_PATTERNS = /\bbuildPrompt\b|\bgeneratePrompt\b|\bsystemPrompt\b|\buserPrompt\b|\bcreatePrompt\b/;
292
+ /** Sanitization function patterns */
293
+ const PROMPT_SANITIZER_PATTERNS = /\bsanitizeForPrompt\b|\bescapePrompt\b|\bcleanPrompt\b|\bsanitize\w*\(/;
294
+ function promptInjection(ctx) {
295
+ const findings = [];
296
+ // Check template literals that embed user input and flow to LLM calls
297
+ for (const template of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.TemplateExpression)) {
298
+ const text = template.getText();
299
+ // Does this template contain user input references?
300
+ const hasUserInput = USER_INPUT_PATTERNS.test(text) ||
301
+ /\b(question|userInput|userMessage|message|input|query|prompt|instruction|caption)\b/.test(text);
302
+ if (!hasUserInput)
303
+ continue;
304
+ // Is there a sanitizer wrapping the user input in this template?
305
+ if (PROMPT_SANITIZER_PATTERNS.test(text))
306
+ continue;
307
+ // Check if this template is used in an LLM context
308
+ // 1. Inside a function whose name suggests prompt building
309
+ let parent = template.getParent();
310
+ let inPromptContext = false;
311
+ while (parent) {
312
+ if (parent.getKind() === SyntaxKind.FunctionDeclaration ||
313
+ parent.getKind() === SyntaxKind.ArrowFunction ||
314
+ parent.getKind() === SyntaxKind.MethodDeclaration) {
315
+ const parentText = parent.getText().substring(0, 200);
316
+ if (PROMPT_BUILDER_PATTERNS.test(parentText) || LLM_API_PATTERNS.test(parentText)) {
317
+ inPromptContext = true;
318
+ }
319
+ // Check function name
320
+ if (parent.getKind() === SyntaxKind.FunctionDeclaration) {
321
+ const fnName = parent.getName() || '';
322
+ if (/prompt|build.*prompt|generate.*prompt|system.*prompt/i.test(fnName)) {
323
+ inPromptContext = true;
324
+ }
325
+ }
326
+ break;
327
+ }
328
+ parent = parent.getParent();
329
+ }
330
+ // 2. Variable assigned to a name like "systemPrompt", "userPrompt"
331
+ const assignParent = template.getParent();
332
+ if (assignParent?.getKind() === SyntaxKind.VariableDeclaration) {
333
+ const varName = assignParent.getName();
334
+ if (/prompt|system|instruction/i.test(varName)) {
335
+ inPromptContext = true;
336
+ }
337
+ }
338
+ // Also check if template is assigned via `const x = ...`
339
+ if (assignParent?.getKind() === SyntaxKind.BinaryExpression) {
340
+ const leftText = assignParent.getLeft().getText();
341
+ if (/prompt|system|instruction/i.test(leftText)) {
342
+ inPromptContext = true;
343
+ }
344
+ }
345
+ if (!inPromptContext)
346
+ continue;
347
+ // Find which user input variable is unsanitized
348
+ const spans = template.getTemplateSpans();
349
+ for (const span of spans) {
350
+ const exprText = span.getExpression().getText();
351
+ // Skip if this specific expression is wrapped in sanitize
352
+ if (PROMPT_SANITIZER_PATTERNS.test(exprText))
353
+ continue;
354
+ // Skip simple property access on known safe objects
355
+ if (/^(intent|mixContext|analysisResults)\b/.test(exprText))
356
+ continue;
357
+ // Check if this is a user-controlled value
358
+ const isUserControlled = USER_INPUT_PATTERNS.test(exprText) ||
359
+ /^(question|userInput|userMessage|message|input|caption|instruction)\b/.test(exprText);
360
+ if (isUserControlled) {
361
+ findings.push(finding('prompt-injection', 'warning', 'bug', `User input '${exprText.substring(0, 50)}' embedded in LLM prompt without sanitization — prompt injection risk`, ctx.filePath, template.getStartLineNumber(), { suggestion: 'Wrap user input with sanitizeForPrompt() or equivalent before embedding in prompts' }));
362
+ break; // One finding per template
363
+ }
364
+ }
365
+ }
366
+ // Check string concatenation in prompt context: "You are... " + userInput
367
+ for (const bin of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.BinaryExpression)) {
368
+ if (bin.getOperatorToken().getKind() !== SyntaxKind.PlusToken)
369
+ continue;
370
+ const rightText = bin.getRight().getText();
371
+ const leftText = bin.getLeft().getText();
372
+ const fullText = bin.getText();
373
+ // Is this string concat involving user input?
374
+ const hasUserInput = USER_INPUT_PATTERNS.test(rightText) ||
375
+ /^(question|userInput|message|input|caption|instruction)\b/.test(rightText);
376
+ if (!hasUserInput)
377
+ continue;
378
+ // Is the left side a prompt-like string?
379
+ const isPromptConcat = /prompt|instruction|system|you are|analyze|review/i.test(leftText);
380
+ if (!isPromptConcat)
381
+ continue;
382
+ // Is it sanitized?
383
+ if (PROMPT_SANITIZER_PATTERNS.test(fullText))
384
+ continue;
385
+ findings.push(finding('prompt-injection', 'warning', 'bug', `User input concatenated into LLM prompt without sanitization — prompt injection risk`, ctx.filePath, bin.getStartLineNumber(), { suggestion: 'Use sanitizeForPrompt() on user input before concatenating into prompts' }));
386
+ }
387
+ return findings;
388
+ }
389
+ // ── Exported Security v3 Rules ────────────────────────────────────────
390
+ export const securityV3Rules = [
391
+ regexDos,
392
+ missingInputValidation,
393
+ prototypePollution,
394
+ informationExposure,
395
+ promptInjection,
396
+ ];
397
+ //# sourceMappingURL=security-v3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-v3.js","sourceRoot":"","sources":["../../src/rules/security-v3.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Security v4 rules — LLM prompt injection attack surface.
3
+ *
4
+ * Covers 10 attack vectors beyond basic prompt injection (v3):
5
+ * S14: indirect-prompt-injection — DB-stored data flows to LLM prompt
6
+ * S15: llm-output-execution — LLM output passed to eval/exec
7
+ * S16: system-prompt-leakage — system prompt exposed in responses
8
+ * S17: rag-poisoning — retrieval results flow unsanitized to prompt
9
+ * S18: tool-calling-manipulation — user input controls tool names/schemas
10
+ * S19: encoding-bypass — decoded content enters prompt unsanitized
11
+ * S20: delimiter-injection — user input with delimiters in prompt context
12
+ * S21: unsanitized-history — chat history pushed without sanitization
13
+ * S22: json-output-manipulation — JSON.parse on LLM output without schema
14
+ * S23: missing-output-validation — LLM output used without validation
15
+ *
16
+ * All AST-based. Always active regardless of target.
17
+ * OWASP LLM01-LLM09
18
+ */
19
+ import type { ReviewFinding, RuleContext } from '../types.js';
20
+ declare function indirectPromptInjection(ctx: RuleContext): ReviewFinding[];
21
+ export declare const securityV4Rules: (typeof indirectPromptInjection)[];
22
+ export {};