circle-ir 3.133.0 → 3.135.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.
@@ -360,4 +360,43 @@ export declare function findPythonInteractiveInterpreterCodeInjectionFindings(co
360
360
  * `web::Json<T>` / `HttpRequest` param, or `axum::body::Bytes`/`String` etc.
361
361
  */
362
362
  export declare function findRustEvalCrateCodeInjectionFindings(code: string, file: string): SastFinding[];
363
+ /**
364
+ * Sprint 84 detector A (#189) — Go MongoDB driver nosql_injection.
365
+ * The Go MongoDB driver call shape `coll.FindOne(ctx, bson.M{"k": <taint>})`
366
+ * (and siblings: Find / InsertOne / InsertMany / UpdateOne / UpdateMany /
367
+ * DeleteOne / DeleteMany / FindOneAndUpdate / FindOneAndDelete /
368
+ * FindOneAndReplace / Aggregate) is not modeled by configured sinks (those
369
+ * cover Node.js Mongo only). Fires when the filter argument (after `ctx`)
370
+ * references a value transitively derived from `*http.Request` extractors
371
+ * (URL.Query().Get / FormValue / PostFormValue / Header.Get / Cookie).
372
+ */
373
+ export declare function findGoMongoNosqlInjectionFindings(code: string, file: string): SastFinding[];
374
+ /**
375
+ * Sprint 84 detector B (#189) — Java Mongo driver nosql_injection.
376
+ * Mongo Java driver shape `users.find(eq("k", <taint>))` (and siblings)
377
+ * is not modeled by configured sinks (those cover Node.js Mongo only).
378
+ * Fires when the receiver call payload references a value transitively
379
+ * derived from servlet request extractors (request.getParameter /
380
+ * getHeader / getCookies / getReader / getQueryString / getPart).
381
+ */
382
+ export declare function findJavaMongoNosqlInjectionFindings(code: string, file: string): SastFinding[];
383
+ /**
384
+ * Sprint 84 detector C (#189) — Python mongoengine `$where` JS-string injection.
385
+ * The shape `User.objects(__raw__={'$where': "this.x == '" + n + "'"})`
386
+ * (and aliases via `$where` key with string concat / f-string) bypasses
387
+ * the configured nosql sinks. Fires when the `$where` value references
388
+ * a value transitively derived from Flask/Django/FastAPI request input.
389
+ */
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[];
363
402
  //# 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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"}
@@ -223,6 +223,11 @@ export class LanguageSourcesPass {
223
223
  for (const finding of findGoPluginOpenCodeInjectionFindings(code, graph.ir.meta.file)) {
224
224
  ctx.addFinding(finding);
225
225
  }
226
+ // Sprint 84 (#189): Go nosql_injection — *mongo.Collection FindOne / Find
227
+ // / Insert / Update / Delete / Aggregate with bson.M{...tainted}.
228
+ for (const finding of findGoMongoNosqlInjectionFindings(code, graph.ir.meta.file)) {
229
+ ctx.addFinding(finding);
230
+ }
226
231
  }
227
232
  // -- Python: safe-handler sanitizer detectors (cognium-dev #114 Sprint 31) --
228
233
  if (language === 'python') {
@@ -261,6 +266,11 @@ export class LanguageSourcesPass {
261
266
  for (const finding of findPythonInteractiveInterpreterCodeInjectionFindings(code, graph.ir.meta.file)) {
262
267
  ctx.addFinding(finding);
263
268
  }
269
+ // Sprint 84 (#189): Python nosql_injection — mongoengine `__raw__={"$where":
270
+ // <tainted-string-concat>}` JS-string injection through MongoDB $where.
271
+ for (const finding of findPythonMongoengineWhereNosqlInjectionFindings(code, graph.ir.meta.file)) {
272
+ ctx.addFinding(finding);
273
+ }
264
274
  }
265
275
  // -- Rust: safe-handler sanitizer detectors (cognium-dev #115 Sprint 31) --
266
276
  if (language === 'rust') {
@@ -360,6 +370,17 @@ export class LanguageSourcesPass {
360
370
  for (const finding of findJavaResponseWriterXssFindings(code, graph.ir.meta.file)) {
361
371
  ctx.addFinding(finding);
362
372
  }
373
+ // Sprint 84 (#189): Java nosql_injection — MongoCollection find / insert /
374
+ // update / delete / aggregate with Filters.eq("k", <tainted servlet input>).
375
+ for (const finding of findJavaMongoNosqlInjectionFindings(code, graph.ir.meta.file)) {
376
+ ctx.addFinding(finding);
377
+ }
378
+ // Sprint 85 (#189): Java ssrf — `new URL(<tainted>)` →
379
+ // `.openStream()` / `.openConnection()` / `.getContent()` receiver
380
+ // chains (basic_fetch + weak_allowlist variants).
381
+ for (const finding of findJavaUrlOpenStreamSsrfFindings(code, graph.ir.meta.file)) {
382
+ ctx.addFinding(finding);
383
+ }
363
384
  }
364
385
  // Sprint 70 (#151): cross-language env-secret → external-network exfiltration.
365
386
  // Pattern findings only — no taint flow required (composed-flow shape that
@@ -5701,4 +5722,413 @@ export function findRustEvalCrateCodeInjectionFindings(code, file) {
5701
5722
  }
5702
5723
  return findings;
5703
5724
  }
5725
+ /**
5726
+ * Sprint 84 detector A (#189) — Go MongoDB driver nosql_injection.
5727
+ * The Go MongoDB driver call shape `coll.FindOne(ctx, bson.M{"k": <taint>})`
5728
+ * (and siblings: Find / InsertOne / InsertMany / UpdateOne / UpdateMany /
5729
+ * DeleteOne / DeleteMany / FindOneAndUpdate / FindOneAndDelete /
5730
+ * FindOneAndReplace / Aggregate) is not modeled by configured sinks (those
5731
+ * cover Node.js Mongo only). Fires when the filter argument (after `ctx`)
5732
+ * references a value transitively derived from `*http.Request` extractors
5733
+ * (URL.Query().Get / FormValue / PostFormValue / Header.Get / Cookie).
5734
+ */
5735
+ export function findGoMongoNosqlInjectionFindings(code, file) {
5736
+ const findings = [];
5737
+ if (typeof code !== 'string' || code.length === 0)
5738
+ return findings;
5739
+ // Gate: require some hint of the mongo-driver / bson namespace usage.
5740
+ if (!/(\bbson\s*\.\s*[MDAE]\b|mongo-driver|\*\s*mongo\.Collection)/.test(code)) {
5741
+ return findings;
5742
+ }
5743
+ const lines = code.split('\n');
5744
+ const reqExtractRe = /\b\w+\s*\.\s*(?:FormValue|PostFormValue|URL\s*\.\s*Query\s*\(\s*\)\s*\.\s*Get|Header\s*\.\s*Get|Cookie)\s*\(/;
5745
+ const httpReqParamRe = /\*\s*http\.Request\b/;
5746
+ const opsAlt = '(?:FindOne|Find|InsertOne|InsertMany|UpdateOne|UpdateMany|DeleteOne|DeleteMany|FindOneAndUpdate|FindOneAndDelete|FindOneAndReplace|Aggregate)';
5747
+ // Match call-site head only; balanced parens for args are extracted below.
5748
+ const callHeadRe = new RegExp(`\\.\\s*(${opsAlt})\\s*\\(`);
5749
+ function extractBalanced(line, openIdx) {
5750
+ let depth = 0;
5751
+ for (let k = openIdx; k < line.length; k++) {
5752
+ const ch = line[k];
5753
+ if (ch === '(')
5754
+ depth++;
5755
+ else if (ch === ')') {
5756
+ depth--;
5757
+ if (depth === 0)
5758
+ return line.substring(openIdx + 1, k);
5759
+ }
5760
+ }
5761
+ return null;
5762
+ }
5763
+ const funcs = [];
5764
+ let cur = null;
5765
+ for (let i = 0; i < lines.length; i++) {
5766
+ const t = lines[i].trim();
5767
+ if (/^func\b/.test(t)) {
5768
+ if (cur) {
5769
+ cur.end = i - 1;
5770
+ funcs.push(cur);
5771
+ }
5772
+ cur = { start: i, end: lines.length - 1 };
5773
+ }
5774
+ }
5775
+ if (cur)
5776
+ funcs.push(cur);
5777
+ for (const fn of funcs) {
5778
+ const header = lines[fn.start];
5779
+ if (!httpReqParamRe.test(header))
5780
+ continue;
5781
+ const taintedVars = new Set();
5782
+ for (let pass = 0; pass < 3; pass++) {
5783
+ const before = taintedVars.size;
5784
+ for (let i = fn.start; i <= fn.end; i++) {
5785
+ const trimmed = lines[i].trim();
5786
+ const assignMatch = trimmed.match(/^(\w+)\s*(?::=|=)\s*(.+?)(?:\s*\/\/.*)?$/);
5787
+ if (!assignMatch)
5788
+ continue;
5789
+ const lhs = assignMatch[1];
5790
+ const rhs = assignMatch[2];
5791
+ if (taintedVars.has(lhs))
5792
+ continue;
5793
+ if (reqExtractRe.test(rhs)) {
5794
+ taintedVars.add(lhs);
5795
+ continue;
5796
+ }
5797
+ for (const v of taintedVars) {
5798
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
5799
+ taintedVars.add(lhs);
5800
+ break;
5801
+ }
5802
+ }
5803
+ }
5804
+ if (taintedVars.size === before)
5805
+ break;
5806
+ }
5807
+ if (taintedVars.size === 0)
5808
+ continue;
5809
+ for (let i = fn.start; i <= fn.end; i++) {
5810
+ const line = lines[i];
5811
+ const m = callHeadRe.exec(line);
5812
+ if (!m)
5813
+ continue;
5814
+ const op = m[1];
5815
+ const openIdx = m.index + m[0].length - 1;
5816
+ const args = extractBalanced(line, openIdx);
5817
+ if (args === null || args.length === 0)
5818
+ continue;
5819
+ let tainted = reqExtractRe.test(args);
5820
+ if (!tainted) {
5821
+ for (const v of taintedVars) {
5822
+ if (new RegExp(`\\b${v}\\b`).test(args)) {
5823
+ tainted = true;
5824
+ break;
5825
+ }
5826
+ }
5827
+ }
5828
+ if (!tainted)
5829
+ continue;
5830
+ findings.push({
5831
+ id: `nosql_injection-${file}-${i + 1}-go-mongo-${op.toLowerCase()}`,
5832
+ pass: 'language-sources',
5833
+ category: 'security',
5834
+ rule_id: 'nosql_injection',
5835
+ cwe: 'CWE-943',
5836
+ severity: 'critical',
5837
+ level: 'error',
5838
+ message: `NoSQL injection: Go MongoDB driver \`${op}(...)\` called with a ` +
5839
+ 'filter derived from *http.Request input. Untrusted values inside ' +
5840
+ 'a `bson.M` / `bson.D` filter can be operator objects (e.g. ' +
5841
+ '`{"$ne": null}`) and bypass authentication/intent. Validate or ' +
5842
+ 'coerce the value to a primitive before building the filter.',
5843
+ file,
5844
+ line: i + 1,
5845
+ snippet: line.trim(),
5846
+ });
5847
+ }
5848
+ }
5849
+ return findings;
5850
+ }
5851
+ /**
5852
+ * Sprint 84 detector B (#189) — Java Mongo driver nosql_injection.
5853
+ * Mongo Java driver shape `users.find(eq("k", <taint>))` (and siblings)
5854
+ * is not modeled by configured sinks (those cover Node.js Mongo only).
5855
+ * Fires when the receiver call payload references a value transitively
5856
+ * derived from servlet request extractors (request.getParameter /
5857
+ * getHeader / getCookies / getReader / getQueryString / getPart).
5858
+ */
5859
+ export function findJavaMongoNosqlInjectionFindings(code, file) {
5860
+ const findings = [];
5861
+ if (typeof code !== 'string' || code.length === 0)
5862
+ return findings;
5863
+ // Gate: require some hint of MongoCollection / Filters / Document use.
5864
+ if (!/(MongoCollection\b|com\.mongodb\b|\bFilters\b|\bnew\s+Document\s*\()/.test(code)) {
5865
+ return findings;
5866
+ }
5867
+ const lines = code.split('\n');
5868
+ const reqExtractRe = /\b\w+\s*\.\s*(?:getParameter|getParameterValues|getHeader|getHeaders|getCookies|getReader|getQueryString|getRequestURI|getInputStream|getPart|getParts)\s*\(/;
5869
+ const opsAlt = '(?:find|findOne|findOneAndUpdate|findOneAndDelete|findOneAndReplace|insertOne|insertMany|updateOne|updateMany|deleteOne|deleteMany|replaceOne|aggregate|countDocuments|distinct)';
5870
+ const callRe = new RegExp(`\\.\\s*(${opsAlt})\\s*\\(([\\s\\S]*?)\\)`);
5871
+ // Whole-file 3-pass taint propagation across simple `Type name = expr;`
5872
+ // and `name = expr;` assignments. Lightweight; suitable for the fixture
5873
+ // set without paying for full Java scope tracking.
5874
+ const taintedVars = new Set();
5875
+ for (let pass = 0; pass < 3; pass++) {
5876
+ const before = taintedVars.size;
5877
+ for (let i = 0; i < lines.length; i++) {
5878
+ const t = lines[i].trim();
5879
+ const a = t.match(/^(?:final\s+)?(?:[\w<>?,\[\]]+\s+)?(\w+)\s*=\s*(.+?);?$/);
5880
+ if (!a)
5881
+ continue;
5882
+ const lhs = a[1];
5883
+ const rhs = a[2];
5884
+ if (taintedVars.has(lhs))
5885
+ continue;
5886
+ if (reqExtractRe.test(rhs)) {
5887
+ taintedVars.add(lhs);
5888
+ continue;
5889
+ }
5890
+ for (const v of taintedVars) {
5891
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
5892
+ taintedVars.add(lhs);
5893
+ break;
5894
+ }
5895
+ }
5896
+ }
5897
+ if (taintedVars.size === before)
5898
+ break;
5899
+ }
5900
+ if (taintedVars.size === 0)
5901
+ return findings;
5902
+ for (let i = 0; i < lines.length; i++) {
5903
+ const line = lines[i];
5904
+ const m = line.match(callRe);
5905
+ if (!m)
5906
+ continue;
5907
+ const op = m[1];
5908
+ const args = m[2];
5909
+ if (args.trim().length === 0)
5910
+ continue;
5911
+ let tainted = reqExtractRe.test(args);
5912
+ if (!tainted) {
5913
+ for (const v of taintedVars) {
5914
+ if (new RegExp(`\\b${v}\\b`).test(args)) {
5915
+ tainted = true;
5916
+ break;
5917
+ }
5918
+ }
5919
+ }
5920
+ if (!tainted)
5921
+ continue;
5922
+ findings.push({
5923
+ id: `nosql_injection-${file}-${i + 1}-java-mongo-${op.toLowerCase()}`,
5924
+ pass: 'language-sources',
5925
+ category: 'security',
5926
+ rule_id: 'nosql_injection',
5927
+ cwe: 'CWE-943',
5928
+ severity: 'critical',
5929
+ level: 'error',
5930
+ message: `NoSQL injection: Java Mongo driver \`${op}(...)\` called with a ` +
5931
+ 'filter derived from servlet request input. Untrusted values can ' +
5932
+ 'reach BSON operator positions and bypass intent. Validate the ' +
5933
+ 'input type before constructing the filter (e.g. require String).',
5934
+ file,
5935
+ line: i + 1,
5936
+ snippet: line.trim(),
5937
+ });
5938
+ }
5939
+ return findings;
5940
+ }
5941
+ /**
5942
+ * Sprint 84 detector C (#189) — Python mongoengine `$where` JS-string injection.
5943
+ * The shape `User.objects(__raw__={'$where': "this.x == '" + n + "'"})`
5944
+ * (and aliases via `$where` key with string concat / f-string) bypasses
5945
+ * the configured nosql sinks. Fires when the `$where` value references
5946
+ * a value transitively derived from Flask/Django/FastAPI request input.
5947
+ */
5948
+ export function findPythonMongoengineWhereNosqlInjectionFindings(code, file) {
5949
+ const findings = [];
5950
+ if (typeof code !== 'string' || code.length === 0)
5951
+ return findings;
5952
+ // Gate: require literal '$where' to appear in the file.
5953
+ if (!/['"]\$where['"]/.test(code))
5954
+ return findings;
5955
+ const lines = code.split('\n');
5956
+ const reqExtractRe = /\b(?:request\.(?:args|form|values|json|files|cookies|headers|data)\b|flask\.request\b)/;
5957
+ const taintedVars = new Set();
5958
+ for (let pass = 0; pass < 3; pass++) {
5959
+ const before = taintedVars.size;
5960
+ for (let i = 0; i < lines.length; i++) {
5961
+ const t = lines[i].trim();
5962
+ if (t.startsWith('#'))
5963
+ continue;
5964
+ const a = t.match(/^(\w+)\s*=\s*(.+?)$/);
5965
+ if (!a)
5966
+ continue;
5967
+ const lhs = a[1];
5968
+ const rhs = a[2];
5969
+ if (taintedVars.has(lhs))
5970
+ continue;
5971
+ if (reqExtractRe.test(rhs)) {
5972
+ taintedVars.add(lhs);
5973
+ continue;
5974
+ }
5975
+ for (const v of taintedVars) {
5976
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
5977
+ taintedVars.add(lhs);
5978
+ break;
5979
+ }
5980
+ }
5981
+ }
5982
+ if (taintedVars.size === before)
5983
+ break;
5984
+ }
5985
+ const whereRe = /['"]\$where['"]\s*:\s*([^,}\n]+)/;
5986
+ for (let i = 0; i < lines.length; i++) {
5987
+ const line = lines[i];
5988
+ const m = line.match(whereRe);
5989
+ if (!m)
5990
+ continue;
5991
+ const valueExpr = m[1].trim();
5992
+ // Pure string literal (no concat, no f-string interp): safe.
5993
+ if (/^"[^"]*"$/.test(valueExpr) || /^'[^']*'$/.test(valueExpr))
5994
+ continue;
5995
+ let tainted = reqExtractRe.test(valueExpr);
5996
+ if (!tainted) {
5997
+ for (const v of taintedVars) {
5998
+ if (new RegExp(`\\b${v}\\b`).test(valueExpr)) {
5999
+ tainted = true;
6000
+ break;
6001
+ }
6002
+ }
6003
+ }
6004
+ if (!tainted)
6005
+ continue;
6006
+ findings.push({
6007
+ id: `nosql_injection-${file}-${i + 1}-py-mongoengine-where`,
6008
+ pass: 'language-sources',
6009
+ category: 'security',
6010
+ rule_id: 'nosql_injection',
6011
+ cwe: 'CWE-943',
6012
+ severity: 'critical',
6013
+ level: 'error',
6014
+ message: 'NoSQL injection: mongoengine `__raw__={"$where": ...}` payload ' +
6015
+ 'derived from HTTP request input. The `$where` operator evaluates ' +
6016
+ 'JavaScript on the server; tainted string concatenation lets an ' +
6017
+ 'attacker inject arbitrary JS. Replace `$where` with field-based ' +
6018
+ 'operators or validate the input.',
6019
+ file,
6020
+ line: i + 1,
6021
+ snippet: line.trim(),
6022
+ });
6023
+ }
6024
+ return findings;
6025
+ }
6026
+ /**
6027
+ * Sprint 85 detector (#189) — Java SSRF via `new URL(<tainted>)` →
6028
+ * `.openStream()` / `.openConnection()` / `.getContent()` receiver chains
6029
+ * that the configured `URL.openStream` / `URL.openConnection` sinks
6030
+ * recognize but the cross-statement flow construction misses (URL value
6031
+ * passes through an intermediate local variable). Also fires when the
6032
+ * tainted URL is gated only by a weak allowlist — a scheme-only check
6033
+ * such as `url.startsWith("https://")` is NOT a sanitizer because the
6034
+ * host is still attacker-controlled.
6035
+ */
6036
+ export function findJavaUrlOpenStreamSsrfFindings(code, file) {
6037
+ const findings = [];
6038
+ if (typeof code !== 'string' || code.length === 0)
6039
+ return findings;
6040
+ // Gate: require URL construction + a fetch-style method call.
6041
+ if (!/\bnew\s+URL\s*\(/.test(code) &&
6042
+ !/\bURI\s*\.\s*create\s*\(/.test(code)) {
6043
+ return findings;
6044
+ }
6045
+ if (!/\.\s*(?:openStream|openConnection|getContent)\s*\(/.test(code)) {
6046
+ return findings;
6047
+ }
6048
+ const lines = code.split('\n');
6049
+ const reqExtractRe = /\b\w+\s*\.\s*(?:getParameter|getParameterValues|getHeader|getHeaders|getCookies|getReader|getQueryString|getRequestURI|getInputStream|getPart|getParts)\s*\(/;
6050
+ // Whole-file 3-pass taint propagation. Tracks straightforward
6051
+ // `Type name = expr;` / `name = expr;` assignments. Notably
6052
+ // includes the `new URL(<tainted>)` wrapper — substring match on
6053
+ // the rhs is sufficient because no built-in URL constructor erases
6054
+ // taint.
6055
+ const taintedVars = new Set();
6056
+ for (let pass = 0; pass < 3; pass++) {
6057
+ const before = taintedVars.size;
6058
+ for (let i = 0; i < lines.length; i++) {
6059
+ const t = lines[i].trim();
6060
+ const a = t.match(/^(?:final\s+)?(?:[\w<>?,\[\]]+\s+)?(\w+)\s*=\s*(.+?);?$/);
6061
+ if (!a)
6062
+ continue;
6063
+ const lhs = a[1];
6064
+ const rhs = a[2];
6065
+ if (taintedVars.has(lhs))
6066
+ continue;
6067
+ if (reqExtractRe.test(rhs)) {
6068
+ taintedVars.add(lhs);
6069
+ continue;
6070
+ }
6071
+ for (const v of taintedVars) {
6072
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
6073
+ taintedVars.add(lhs);
6074
+ break;
6075
+ }
6076
+ }
6077
+ }
6078
+ if (taintedVars.size === before)
6079
+ break;
6080
+ }
6081
+ if (taintedVars.size === 0)
6082
+ return findings;
6083
+ const sinkRe = /\.\s*(openStream|openConnection|getContent)\s*\(/;
6084
+ const seen = new Set();
6085
+ for (let i = 0; i < lines.length; i++) {
6086
+ const line = lines[i];
6087
+ const m = line.match(sinkRe);
6088
+ if (!m)
6089
+ continue;
6090
+ const op = m[1];
6091
+ const sinkIdx = line.indexOf(`.${op}`);
6092
+ if (sinkIdx < 0)
6093
+ continue;
6094
+ // Identify the receiver span: everything to the left of the `.op(`
6095
+ // call. The receiver may be a chained call (e.g.
6096
+ // `new URL(url).openStream()`) — treat the entire pre-call span as
6097
+ // the analysis target and ask whether any tainted var appears in it.
6098
+ const head = line.substring(0, sinkIdx + 1);
6099
+ let tainted = reqExtractRe.test(head);
6100
+ if (!tainted) {
6101
+ for (const v of taintedVars) {
6102
+ if (new RegExp(`\\b${v}\\b`).test(head)) {
6103
+ tainted = true;
6104
+ break;
6105
+ }
6106
+ }
6107
+ }
6108
+ if (!tainted)
6109
+ continue;
6110
+ const key = `${i + 1}:${op}`;
6111
+ if (seen.has(key))
6112
+ continue;
6113
+ seen.add(key);
6114
+ findings.push({
6115
+ id: `ssrf-${file}-${i + 1}-java-url-${op.toLowerCase()}`,
6116
+ pass: 'language-sources',
6117
+ category: 'security',
6118
+ rule_id: 'ssrf',
6119
+ cwe: 'CWE-918',
6120
+ severity: 'critical',
6121
+ level: 'error',
6122
+ message: `SSRF: Java \`URL.${op}()\` invoked on a URL derived from servlet ` +
6123
+ 'request input. Even when an `if (url.startsWith("https://"))` ' +
6124
+ 'guard is present, the host portion remains attacker-controlled — ' +
6125
+ 'use a strict host allowlist (parse the URL, check `getHost()`) ' +
6126
+ 'before issuing the request.',
6127
+ file,
6128
+ line: i + 1,
6129
+ snippet: line.trim(),
6130
+ });
6131
+ }
6132
+ return findings;
6133
+ }
5704
6134
  //# sourceMappingURL=language-sources-pass.js.map