circle-ir 3.134.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.
@@ -388,4 +388,41 @@ export declare function findJavaMongoNosqlInjectionFindings(code: string, file:
388
388
  * a value transitively derived from Flask/Django/FastAPI request input.
389
389
  */
390
390
  export declare function findPythonMongoengineWhereNosqlInjectionFindings(code: string, file: string): SastFinding[];
391
+ /**
392
+ * Sprint 85 detector (#189) — Java SSRF via `new URL(<tainted>)` →
393
+ * `.openStream()` / `.openConnection()` / `.getContent()` receiver chains
394
+ * that the configured `URL.openStream` / `URL.openConnection` sinks
395
+ * recognize but the cross-statement flow construction misses (URL value
396
+ * passes through an intermediate local variable). Also fires when the
397
+ * tainted URL is gated only by a weak allowlist — a scheme-only check
398
+ * such as `url.startsWith("https://")` is NOT a sanitizer because the
399
+ * host is still attacker-controlled.
400
+ */
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[];
391
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
@@ -375,6 +391,12 @@ export class LanguageSourcesPass {
375
391
  for (const finding of findJavaMongoNosqlInjectionFindings(code, graph.ir.meta.file)) {
376
392
  ctx.addFinding(finding);
377
393
  }
394
+ // Sprint 85 (#189): Java ssrf — `new URL(<tainted>)` →
395
+ // `.openStream()` / `.openConnection()` / `.getContent()` receiver
396
+ // chains (basic_fetch + weak_allowlist variants).
397
+ for (const finding of findJavaUrlOpenStreamSsrfFindings(code, graph.ir.meta.file)) {
398
+ ctx.addFinding(finding);
399
+ }
378
400
  }
379
401
  // Sprint 70 (#151): cross-language env-secret → external-network exfiltration.
380
402
  // Pattern findings only — no taint flow required (composed-flow shape that
@@ -6017,4 +6039,413 @@ export function findPythonMongoengineWhereNosqlInjectionFindings(code, file) {
6017
6039
  }
6018
6040
  return findings;
6019
6041
  }
6042
+ /**
6043
+ * Sprint 85 detector (#189) — Java SSRF via `new URL(<tainted>)` →
6044
+ * `.openStream()` / `.openConnection()` / `.getContent()` receiver chains
6045
+ * that the configured `URL.openStream` / `URL.openConnection` sinks
6046
+ * recognize but the cross-statement flow construction misses (URL value
6047
+ * passes through an intermediate local variable). Also fires when the
6048
+ * tainted URL is gated only by a weak allowlist — a scheme-only check
6049
+ * such as `url.startsWith("https://")` is NOT a sanitizer because the
6050
+ * host is still attacker-controlled.
6051
+ */
6052
+ export function findJavaUrlOpenStreamSsrfFindings(code, file) {
6053
+ const findings = [];
6054
+ if (typeof code !== 'string' || code.length === 0)
6055
+ return findings;
6056
+ // Gate: require URL construction + a fetch-style method call.
6057
+ if (!/\bnew\s+URL\s*\(/.test(code) &&
6058
+ !/\bURI\s*\.\s*create\s*\(/.test(code)) {
6059
+ return findings;
6060
+ }
6061
+ if (!/\.\s*(?:openStream|openConnection|getContent)\s*\(/.test(code)) {
6062
+ return findings;
6063
+ }
6064
+ const lines = code.split('\n');
6065
+ const reqExtractRe = /\b\w+\s*\.\s*(?:getParameter|getParameterValues|getHeader|getHeaders|getCookies|getReader|getQueryString|getRequestURI|getInputStream|getPart|getParts)\s*\(/;
6066
+ // Whole-file 3-pass taint propagation. Tracks straightforward
6067
+ // `Type name = expr;` / `name = expr;` assignments. Notably
6068
+ // includes the `new URL(<tainted>)` wrapper — substring match on
6069
+ // the rhs is sufficient because no built-in URL constructor erases
6070
+ // taint.
6071
+ const taintedVars = new Set();
6072
+ for (let pass = 0; pass < 3; pass++) {
6073
+ const before = taintedVars.size;
6074
+ for (let i = 0; i < lines.length; i++) {
6075
+ const t = lines[i].trim();
6076
+ const a = t.match(/^(?:final\s+)?(?:[\w<>?,\[\]]+\s+)?(\w+)\s*=\s*(.+?);?$/);
6077
+ if (!a)
6078
+ continue;
6079
+ const lhs = a[1];
6080
+ const rhs = a[2];
6081
+ if (taintedVars.has(lhs))
6082
+ continue;
6083
+ if (reqExtractRe.test(rhs)) {
6084
+ taintedVars.add(lhs);
6085
+ continue;
6086
+ }
6087
+ for (const v of taintedVars) {
6088
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
6089
+ taintedVars.add(lhs);
6090
+ break;
6091
+ }
6092
+ }
6093
+ }
6094
+ if (taintedVars.size === before)
6095
+ break;
6096
+ }
6097
+ if (taintedVars.size === 0)
6098
+ return findings;
6099
+ const sinkRe = /\.\s*(openStream|openConnection|getContent)\s*\(/;
6100
+ const seen = new Set();
6101
+ for (let i = 0; i < lines.length; i++) {
6102
+ const line = lines[i];
6103
+ const m = line.match(sinkRe);
6104
+ if (!m)
6105
+ continue;
6106
+ const op = m[1];
6107
+ const sinkIdx = line.indexOf(`.${op}`);
6108
+ if (sinkIdx < 0)
6109
+ continue;
6110
+ // Identify the receiver span: everything to the left of the `.op(`
6111
+ // call. The receiver may be a chained call (e.g.
6112
+ // `new URL(url).openStream()`) — treat the entire pre-call span as
6113
+ // the analysis target and ask whether any tainted var appears in it.
6114
+ const head = line.substring(0, sinkIdx + 1);
6115
+ let tainted = reqExtractRe.test(head);
6116
+ if (!tainted) {
6117
+ for (const v of taintedVars) {
6118
+ if (new RegExp(`\\b${v}\\b`).test(head)) {
6119
+ tainted = true;
6120
+ break;
6121
+ }
6122
+ }
6123
+ }
6124
+ if (!tainted)
6125
+ continue;
6126
+ const key = `${i + 1}:${op}`;
6127
+ if (seen.has(key))
6128
+ continue;
6129
+ seen.add(key);
6130
+ findings.push({
6131
+ id: `ssrf-${file}-${i + 1}-java-url-${op.toLowerCase()}`,
6132
+ pass: 'language-sources',
6133
+ category: 'security',
6134
+ rule_id: 'ssrf',
6135
+ cwe: 'CWE-918',
6136
+ severity: 'critical',
6137
+ level: 'error',
6138
+ message: `SSRF: Java \`URL.${op}()\` invoked on a URL derived from servlet ` +
6139
+ 'request input. Even when an `if (url.startsWith("https://"))` ' +
6140
+ 'guard is present, the host portion remains attacker-controlled — ' +
6141
+ 'use a strict host allowlist (parse the URL, check `getHost()`) ' +
6142
+ 'before issuing the request.',
6143
+ file,
6144
+ line: i + 1,
6145
+ snippet: line.trim(),
6146
+ });
6147
+ }
6148
+ return findings;
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
+ }
6020
6451
  //# sourceMappingURL=language-sources-pass.js.map