circle-ir 3.135.0 → 3.136.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.
@@ -399,4 +399,30 @@ export declare function findPythonMongoengineWhereNosqlInjectionFindings(code: s
399
399
  * host is still attacker-controlled.
400
400
  */
401
401
  export declare function findJavaUrlOpenStreamSsrfFindings(code: string, file: string): SastFinding[];
402
+ /**
403
+ * Sprint 86 detector A (#189) — Python uncontrolled format-string.
404
+ *
405
+ * Two shapes:
406
+ * 1. `<tainted> % args` — old-style percent formatting
407
+ * 2. `<tainted>.format(args)` — `str.format` API
408
+ *
409
+ * When the format string itself comes from HTTP request input, an
410
+ * attacker can probe arbitrary attributes/items via `str.format`
411
+ * (`{0.__class__.__init__.__globals__}`) or crash the process via
412
+ * malformed `%` specifiers. CWE-134.
413
+ */
414
+ export declare function findPythonTaintedFormatStringFindings(code: string, file: string): SastFinding[];
415
+ /**
416
+ * Sprint 86 detector B (#189) — JavaScript uncontrolled format-string
417
+ * via Node `util.format(<tainted>, ...args)`. `util.format` honours
418
+ * `%s`/`%d`/`%j`/`%O` specifiers; a tainted format string can fingerprint
419
+ * argument types and is the documented sink for CWE-134 in Node.
420
+ */
421
+ export declare function findJsUtilFormatFormatStringFindings(code: string, file: string): SastFinding[];
422
+ /**
423
+ * Sprint 86 detector C (#189) — Python HTTP header CRLF injection via
424
+ * Flask/Werkzeug `response.headers['X-Custom'] = <tainted concat>` or
425
+ * `response.headers.add(...)` / `response.headers.set(...)`. CWE-113.
426
+ */
427
+ export declare function findPythonHeaderCrlfInjectionFindings(code: string, file: string): SastFinding[];
402
428
  //# sourceMappingURL=language-sources-pass.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"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"}
@@ -271,6 +271,17 @@ export class LanguageSourcesPass {
271
271
  for (const finding of findPythonMongoengineWhereNosqlInjectionFindings(code, graph.ir.meta.file)) {
272
272
  ctx.addFinding(finding);
273
273
  }
274
+ // Sprint 86 (#189): Python format_string — `<tainted> % args` and
275
+ // `<tainted>.format(args)` where the format template comes from
276
+ // an HTTP request extractor.
277
+ for (const finding of findPythonTaintedFormatStringFindings(code, graph.ir.meta.file)) {
278
+ ctx.addFinding(finding);
279
+ }
280
+ // Sprint 86 (#189): Python crlf / header injection —
281
+ // `response.headers['X-Custom'] = <tainted>` (Flask/Werkzeug).
282
+ for (const finding of findPythonHeaderCrlfInjectionFindings(code, graph.ir.meta.file)) {
283
+ ctx.addFinding(finding);
284
+ }
274
285
  }
275
286
  // -- Rust: safe-handler sanitizer detectors (cognium-dev #115 Sprint 31) --
276
287
  if (language === 'rust') {
@@ -347,6 +358,11 @@ export class LanguageSourcesPass {
347
358
  for (const finding of findJsIndirectEvalCodeInjectionFindings(code, graph.ir.meta.file)) {
348
359
  ctx.addFinding(finding);
349
360
  }
361
+ // Sprint 86 (#189): JS format_string — `util.format(<tainted>, ...)`
362
+ // where the format template comes from an HTTP request extractor.
363
+ for (const finding of findJsUtilFormatFormatStringFindings(code, graph.ir.meta.file)) {
364
+ ctx.addFinding(finding);
365
+ }
350
366
  }
351
367
  // -- Java: Sprint 73 (#216 Pattern A) — Jackson readValue / Gson
352
368
  // fromJson recognized as ETE terminator (does not affect
@@ -6131,4 +6147,305 @@ export function findJavaUrlOpenStreamSsrfFindings(code, file) {
6131
6147
  }
6132
6148
  return findings;
6133
6149
  }
6150
+ /**
6151
+ * Sprint 86 detector A (#189) — Python uncontrolled format-string.
6152
+ *
6153
+ * Two shapes:
6154
+ * 1. `<tainted> % args` — old-style percent formatting
6155
+ * 2. `<tainted>.format(args)` — `str.format` API
6156
+ *
6157
+ * When the format string itself comes from HTTP request input, an
6158
+ * attacker can probe arbitrary attributes/items via `str.format`
6159
+ * (`{0.__class__.__init__.__globals__}`) or crash the process via
6160
+ * malformed `%` specifiers. CWE-134.
6161
+ */
6162
+ export function findPythonTaintedFormatStringFindings(code, file) {
6163
+ const findings = [];
6164
+ if (typeof code !== 'string' || code.length === 0)
6165
+ return findings;
6166
+ // Gate: the file must use a Flask/Django/FastAPI request extractor.
6167
+ if (!/\b(?:request\.(?:args|form|values|json|files|cookies|headers|data)|flask\.request)\b/.test(code)) {
6168
+ return findings;
6169
+ }
6170
+ const lines = code.split('\n');
6171
+ const reqExtractRe = /\b(?:request\.(?:args|form|values|json|files|cookies|headers|data)\b|flask\.request\b)/;
6172
+ // 3-pass whole-file taint propagation across simple `name = expr`
6173
+ // assignments.
6174
+ const taintedVars = new Set();
6175
+ for (let pass = 0; pass < 3; pass++) {
6176
+ const before = taintedVars.size;
6177
+ for (let i = 0; i < lines.length; i++) {
6178
+ const t = lines[i].trim();
6179
+ if (t.startsWith('#'))
6180
+ continue;
6181
+ const a = t.match(/^(\w+)\s*=\s*(.+?)$/);
6182
+ if (!a)
6183
+ continue;
6184
+ const lhs = a[1];
6185
+ const rhs = a[2];
6186
+ if (taintedVars.has(lhs))
6187
+ continue;
6188
+ if (reqExtractRe.test(rhs)) {
6189
+ taintedVars.add(lhs);
6190
+ continue;
6191
+ }
6192
+ for (const v of taintedVars) {
6193
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
6194
+ taintedVars.add(lhs);
6195
+ break;
6196
+ }
6197
+ }
6198
+ }
6199
+ if (taintedVars.size === before)
6200
+ break;
6201
+ }
6202
+ if (taintedVars.size === 0)
6203
+ return findings;
6204
+ // Percent format: `<name> % <rhs>` where `<name>` is tainted and not a
6205
+ // string literal. Avoid false-fire on `2 % x` etc. by gating on
6206
+ // taintedVar appearing as the LHS of `%`.
6207
+ const percentRe = /\b(\w+)\s*%\s*[\(\[\{"'\w]/;
6208
+ // str.format(...): `<name>.format(`
6209
+ const dotFormatRe = /\b(\w+)\s*\.\s*format\s*\(/;
6210
+ const seen = new Set();
6211
+ for (let i = 0; i < lines.length; i++) {
6212
+ const line = lines[i];
6213
+ const t = line.trim();
6214
+ if (t.startsWith('#'))
6215
+ continue;
6216
+ // Percent format
6217
+ const pm = line.match(percentRe);
6218
+ if (pm && taintedVars.has(pm[1])) {
6219
+ // Avoid string literal LHS (already a literal would have been
6220
+ // assigned as a literal, but we double-check the var was tainted —
6221
+ // which the check above asserts).
6222
+ const key = `${i + 1}:percent`;
6223
+ if (!seen.has(key)) {
6224
+ seen.add(key);
6225
+ findings.push({
6226
+ id: `format_string-${file}-${i + 1}-py-percent`,
6227
+ pass: 'language-sources',
6228
+ category: 'security',
6229
+ rule_id: 'format_string',
6230
+ cwe: 'CWE-134',
6231
+ severity: 'high',
6232
+ level: 'error',
6233
+ message: 'Format-string injection: Python `<tainted> % args` uses an ' +
6234
+ 'HTTP-request-controlled format string. Malformed specifiers can ' +
6235
+ 'crash the handler (TypeError) and `%(name)s` access can reveal ' +
6236
+ 'arbitrary mapping keys. Use a literal format string and pass ' +
6237
+ 'untrusted values as arguments only.',
6238
+ file,
6239
+ line: i + 1,
6240
+ snippet: line.trim(),
6241
+ });
6242
+ }
6243
+ }
6244
+ // str.format
6245
+ const dm = line.match(dotFormatRe);
6246
+ if (dm && taintedVars.has(dm[1])) {
6247
+ const key = `${i + 1}:dotformat`;
6248
+ if (!seen.has(key)) {
6249
+ seen.add(key);
6250
+ findings.push({
6251
+ id: `format_string-${file}-${i + 1}-py-strformat`,
6252
+ pass: 'language-sources',
6253
+ category: 'security',
6254
+ rule_id: 'format_string',
6255
+ cwe: 'CWE-134',
6256
+ severity: 'high',
6257
+ level: 'error',
6258
+ message: 'Format-string injection: Python `<tainted>.format(...)` lets ' +
6259
+ 'the attacker control the format template. Field-access shapes ' +
6260
+ 'such as `{0.__class__.__init__.__globals__}` can leak module ' +
6261
+ 'globals (e.g. secret keys). Use a literal template and pass ' +
6262
+ 'untrusted values as positional/keyword arguments only.',
6263
+ file,
6264
+ line: i + 1,
6265
+ snippet: line.trim(),
6266
+ });
6267
+ }
6268
+ }
6269
+ }
6270
+ return findings;
6271
+ }
6272
+ /**
6273
+ * Sprint 86 detector B (#189) — JavaScript uncontrolled format-string
6274
+ * via Node `util.format(<tainted>, ...args)`. `util.format` honours
6275
+ * `%s`/`%d`/`%j`/`%O` specifiers; a tainted format string can fingerprint
6276
+ * argument types and is the documented sink for CWE-134 in Node.
6277
+ */
6278
+ export function findJsUtilFormatFormatStringFindings(code, file) {
6279
+ const findings = [];
6280
+ if (typeof code !== 'string' || code.length === 0)
6281
+ return findings;
6282
+ if (!/\butil\s*\.\s*format\s*\(/.test(code))
6283
+ return findings;
6284
+ if (!/\breq\s*\.\s*(?:query|body|params|headers|cookies)\b/.test(code)) {
6285
+ return findings;
6286
+ }
6287
+ const lines = code.split('\n');
6288
+ const reqExtractRe = /\breq\s*\.\s*(?:query|body|params|headers|cookies)\b/;
6289
+ const taintedVars = new Set();
6290
+ for (let pass = 0; pass < 3; pass++) {
6291
+ const before = taintedVars.size;
6292
+ for (let i = 0; i < lines.length; i++) {
6293
+ const t = lines[i].trim();
6294
+ if (t.startsWith('//'))
6295
+ continue;
6296
+ const a = t.match(/^(?:const|let|var)\s+(\w+)\s*=\s*(.+?);?$/);
6297
+ if (!a)
6298
+ continue;
6299
+ const lhs = a[1];
6300
+ const rhs = a[2];
6301
+ if (taintedVars.has(lhs))
6302
+ continue;
6303
+ if (reqExtractRe.test(rhs)) {
6304
+ taintedVars.add(lhs);
6305
+ continue;
6306
+ }
6307
+ for (const v of taintedVars) {
6308
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
6309
+ taintedVars.add(lhs);
6310
+ break;
6311
+ }
6312
+ }
6313
+ }
6314
+ if (taintedVars.size === before)
6315
+ break;
6316
+ }
6317
+ if (taintedVars.size === 0)
6318
+ return findings;
6319
+ // util.format(<firstArg>, ...) — check whether <firstArg> is tainted.
6320
+ const callRe = /\butil\s*\.\s*format\s*\(\s*([\w.]+)\s*[,)]/;
6321
+ for (let i = 0; i < lines.length; i++) {
6322
+ const line = lines[i];
6323
+ const m = line.match(callRe);
6324
+ if (!m)
6325
+ continue;
6326
+ const arg = m[1];
6327
+ // First token of dotted path is the var name.
6328
+ const root = arg.split('.')[0];
6329
+ if (!taintedVars.has(root) && !reqExtractRe.test(arg))
6330
+ continue;
6331
+ findings.push({
6332
+ id: `format_string-${file}-${i + 1}-js-util-format`,
6333
+ pass: 'language-sources',
6334
+ category: 'security',
6335
+ rule_id: 'format_string',
6336
+ cwe: 'CWE-134',
6337
+ severity: 'medium',
6338
+ level: 'error',
6339
+ message: 'Format-string injection: Node `util.format(<tainted>, ...)` uses ' +
6340
+ 'an HTTP-request-controlled format string. The attacker can ' +
6341
+ 'manipulate `%s`/`%j`/`%O` specifiers to alter the rendered ' +
6342
+ 'output. Pass user input as a subsequent argument with a literal ' +
6343
+ 'format string.',
6344
+ file,
6345
+ line: i + 1,
6346
+ snippet: line.trim(),
6347
+ });
6348
+ }
6349
+ return findings;
6350
+ }
6351
+ /**
6352
+ * Sprint 86 detector C (#189) — Python HTTP header CRLF injection via
6353
+ * Flask/Werkzeug `response.headers['X-Custom'] = <tainted concat>` or
6354
+ * `response.headers.add(...)` / `response.headers.set(...)`. CWE-113.
6355
+ */
6356
+ export function findPythonHeaderCrlfInjectionFindings(code, file) {
6357
+ const findings = [];
6358
+ if (typeof code !== 'string' || code.length === 0)
6359
+ return findings;
6360
+ if (!/\b\w+\s*\.\s*headers\b/.test(code))
6361
+ return findings;
6362
+ if (!/\b(?:request\.(?:args|form|values|json|files|cookies|headers|data)|flask\.request)\b/.test(code)) {
6363
+ return findings;
6364
+ }
6365
+ const lines = code.split('\n');
6366
+ const reqExtractRe = /\b(?:request\.(?:args|form|values|json|files|cookies|headers|data)\b|flask\.request\b)/;
6367
+ const taintedVars = new Set();
6368
+ for (let pass = 0; pass < 3; pass++) {
6369
+ const before = taintedVars.size;
6370
+ for (let i = 0; i < lines.length; i++) {
6371
+ const t = lines[i].trim();
6372
+ if (t.startsWith('#'))
6373
+ continue;
6374
+ const a = t.match(/^(\w+)\s*=\s*(.+?)$/);
6375
+ if (!a)
6376
+ continue;
6377
+ const lhs = a[1];
6378
+ const rhs = a[2];
6379
+ if (taintedVars.has(lhs))
6380
+ continue;
6381
+ if (reqExtractRe.test(rhs)) {
6382
+ taintedVars.add(lhs);
6383
+ continue;
6384
+ }
6385
+ for (const v of taintedVars) {
6386
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
6387
+ taintedVars.add(lhs);
6388
+ break;
6389
+ }
6390
+ }
6391
+ }
6392
+ if (taintedVars.size === before)
6393
+ break;
6394
+ }
6395
+ if (taintedVars.size === 0)
6396
+ return findings;
6397
+ // Patterns:
6398
+ // resp.headers['X-Custom'] = <expr>
6399
+ // resp.headers.add('X-Custom', <expr>)
6400
+ // resp.headers.set('X-Custom', <expr>)
6401
+ // resp.headers.update({'X-Custom': <expr>})
6402
+ const subscriptRe = /\b\w+\s*\.\s*headers\s*\[\s*['"][^'"]+['"]\s*\]\s*=\s*(.+)$/;
6403
+ const methodRe = /\b\w+\s*\.\s*headers\s*\.\s*(?:add|set|setdefault|append)\s*\(\s*['"][^'"]+['"]\s*,\s*(.+?)\)/;
6404
+ for (let i = 0; i < lines.length; i++) {
6405
+ const line = lines[i];
6406
+ const t = line.trim();
6407
+ if (t.startsWith('#'))
6408
+ continue;
6409
+ let expr = null;
6410
+ const sm = line.match(subscriptRe);
6411
+ if (sm)
6412
+ expr = sm[1];
6413
+ else {
6414
+ const mm = line.match(methodRe);
6415
+ if (mm)
6416
+ expr = mm[1];
6417
+ }
6418
+ if (!expr)
6419
+ continue;
6420
+ let tainted = reqExtractRe.test(expr);
6421
+ if (!tainted) {
6422
+ for (const v of taintedVars) {
6423
+ if (new RegExp(`\\b${v}\\b`).test(expr)) {
6424
+ tainted = true;
6425
+ break;
6426
+ }
6427
+ }
6428
+ }
6429
+ if (!tainted)
6430
+ continue;
6431
+ findings.push({
6432
+ id: `crlf-${file}-${i + 1}-py-headers`,
6433
+ pass: 'language-sources',
6434
+ category: 'security',
6435
+ rule_id: 'crlf',
6436
+ cwe: 'CWE-113',
6437
+ severity: 'medium',
6438
+ level: 'error',
6439
+ message: 'CRLF / header injection: Python Flask/Werkzeug ' +
6440
+ '`response.headers[...] = <tainted>` lets an attacker inject a ' +
6441
+ '`\\r\\n` sequence and forge additional headers or split the ' +
6442
+ 'response body. Validate the value (reject control characters) or ' +
6443
+ 'use a fixed allowlist.',
6444
+ file,
6445
+ line: i + 1,
6446
+ snippet: line.trim(),
6447
+ });
6448
+ }
6449
+ return findings;
6450
+ }
6134
6451
  //# sourceMappingURL=language-sources-pass.js.map