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.
- package/dist/analysis/passes/language-sources-pass.d.ts +26 -0
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +317 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/browser/circle-ir.js +237 -0
- package/package.json +1 -1
|
@@ -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":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAwB,WAAW,EAAO,MAAM,sBAAsB,CAAC;AAC3H,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAqB9E,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0C/B,CAAC;AAiCF,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,eAAe,EAAE,SAAS,EAAE,CAAC;IAC7B;;;;OAIG;IACH,oBAAoB,EAAE,cAAc,EAAE,CAAC;IACvC;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAMD,qBAAa,mBAAoB,YAAW,YAAY,CAAC,qBAAqB,CAAC;IAC7E,QAAQ,CAAC,IAAI,sBAAsB;IACnC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,qBAAqB;
|
|
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
|