circle-ir 3.198.0 → 3.200.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/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +45 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/browser/circle-ir.js +116 -0
- package/dist/core/circle-ir-core.cjs +116 -0
- package/dist/core/circle-ir-core.js +116 -0
- package/dist/core/extractors/types.js +97 -0
- package/dist/core/extractors/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -4380,6 +4380,10 @@ function extractJavaScriptTypes(tree, cache) {
|
|
|
4380
4380
|
for (const cls of classes) {
|
|
4381
4381
|
types.push(extractJSClassInfo(cls));
|
|
4382
4382
|
}
|
|
4383
|
+
const interfaces = getNodesFromCache(tree.rootNode, "interface_declaration", cache);
|
|
4384
|
+
for (const iface of interfaces) {
|
|
4385
|
+
types.push(extractTSInterfaceInfo(iface));
|
|
4386
|
+
}
|
|
4383
4387
|
const functions = getNodesFromCache(tree.rootNode, "function_declaration", cache);
|
|
4384
4388
|
if (functions.length > 0) {
|
|
4385
4389
|
const moduleFunctions = [];
|
|
@@ -4715,6 +4719,73 @@ function extractSelfAssignments(body2, fields, fieldNames) {
|
|
|
4715
4719
|
}
|
|
4716
4720
|
}
|
|
4717
4721
|
}
|
|
4722
|
+
function extractTSInterfaceInfo(node) {
|
|
4723
|
+
const name2 = getIdentifier(node, "name") ?? "Anonymous";
|
|
4724
|
+
const parents = [];
|
|
4725
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
4726
|
+
const child = node.child(i2);
|
|
4727
|
+
if (!child || child.type !== "extends_type_clause") continue;
|
|
4728
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
4729
|
+
const gc = child.child(j);
|
|
4730
|
+
if (!gc) continue;
|
|
4731
|
+
if (gc.type === "type_identifier" || gc.type === "generic_type" || gc.type === "nested_type_identifier") {
|
|
4732
|
+
parents.push(getNodeText(gc));
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
const body2 = node.childForFieldName("body");
|
|
4737
|
+
const methods = [];
|
|
4738
|
+
const fields = [];
|
|
4739
|
+
if (body2) {
|
|
4740
|
+
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4741
|
+
const member = body2.child(i2);
|
|
4742
|
+
if (!member) continue;
|
|
4743
|
+
if (member.type === "method_signature") {
|
|
4744
|
+
const methodName = getIdentifier(member, "name") ?? getNodeText(member.child(0) ?? member);
|
|
4745
|
+
const params = member.childForFieldName("parameters");
|
|
4746
|
+
const returnType = member.childForFieldName("return_type");
|
|
4747
|
+
methods.push({
|
|
4748
|
+
name: methodName,
|
|
4749
|
+
return_type: returnType ? getNodeText(returnType).replace(/^:\s*/, "") : null,
|
|
4750
|
+
parameters: params ? extractJSParameters(params) : [],
|
|
4751
|
+
annotations: [],
|
|
4752
|
+
// An interface member has no body and therefore no modifiers in the
|
|
4753
|
+
// class sense; `readonly` / `?` live on property signatures.
|
|
4754
|
+
modifiers: [],
|
|
4755
|
+
start_line: member.startPosition.row + 1,
|
|
4756
|
+
end_line: member.endPosition.row + 1
|
|
4757
|
+
});
|
|
4758
|
+
continue;
|
|
4759
|
+
}
|
|
4760
|
+
if (member.type === "property_signature") {
|
|
4761
|
+
const fieldName = getIdentifier(member, "name") ?? getNodeText(member.child(0) ?? member);
|
|
4762
|
+
const typeNode = member.childForFieldName("type");
|
|
4763
|
+
const modifiers = [];
|
|
4764
|
+
const text = getNodeText(member);
|
|
4765
|
+
if (/^\s*readonly\b/.test(text)) modifiers.push("readonly");
|
|
4766
|
+
if (/\?\s*:/.test(text)) modifiers.push("optional");
|
|
4767
|
+
fields.push({
|
|
4768
|
+
name: fieldName,
|
|
4769
|
+
type: typeNode ? getNodeText(typeNode).replace(/^:\s*/, "") : null,
|
|
4770
|
+
modifiers,
|
|
4771
|
+
annotations: []
|
|
4772
|
+
});
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
return {
|
|
4777
|
+
name: name2,
|
|
4778
|
+
kind: "interface",
|
|
4779
|
+
package: null,
|
|
4780
|
+
extends: parents[0] ?? null,
|
|
4781
|
+
implements: parents.slice(1),
|
|
4782
|
+
annotations: [],
|
|
4783
|
+
methods,
|
|
4784
|
+
fields,
|
|
4785
|
+
start_line: node.startPosition.row + 1,
|
|
4786
|
+
end_line: node.endPosition.row + 1
|
|
4787
|
+
};
|
|
4788
|
+
}
|
|
4718
4789
|
function extractJSClassInfo(node) {
|
|
4719
4790
|
const name2 = getIdentifier(node, "name") ?? "Anonymous";
|
|
4720
4791
|
let extendsType = null;
|
|
@@ -11011,6 +11082,26 @@ var DEFAULT_SOURCES = [
|
|
|
11011
11082
|
{ annotation: "FormParam", type: "http_param", severity: "high", param_tainted: true },
|
|
11012
11083
|
{ annotation: "PathParam", type: "http_path", severity: "medium", param_tainted: true },
|
|
11013
11084
|
{ annotation: "HeaderParam", type: "http_header", severity: "high", param_tainted: true },
|
|
11085
|
+
// JAX-RS params the block was missing (jakarta.ws.rs). Java-scoped — the
|
|
11086
|
+
// JS decorator ecosystem has its own distinct names and should not match.
|
|
11087
|
+
{ annotation: "CookieParam", type: "http_cookie", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11088
|
+
{ annotation: "MatrixParam", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11089
|
+
{ annotation: "BeanParam", type: "http_body", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11090
|
+
// Micronaut (io.micronaut.http.annotation). @Body / @Header / @CookieValue /
|
|
11091
|
+
// @PathVariable reuse Spring/JAX-RS names already covered above; these three
|
|
11092
|
+
// are Micronaut-specific. Java-scoped.
|
|
11093
|
+
{ annotation: "QueryValue", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11094
|
+
{ annotation: "Part", type: "http_body", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11095
|
+
{ annotation: "RequestBean", type: "http_body", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11096
|
+
// Quarkus / RESTEasy Reactive (org.jboss.resteasy.reactive) — the `@Rest*`
|
|
11097
|
+
// shortcut annotations that infer the binding name from the parameter name.
|
|
11098
|
+
// Java-scoped.
|
|
11099
|
+
{ annotation: "RestQuery", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11100
|
+
{ annotation: "RestPath", type: "http_path", severity: "medium", param_tainted: true, languages: ["java"] },
|
|
11101
|
+
{ annotation: "RestHeader", type: "http_header", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11102
|
+
{ annotation: "RestForm", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11103
|
+
{ annotation: "RestCookie", type: "http_cookie", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11104
|
+
{ annotation: "RestMatrix", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11014
11105
|
// Jenkins data-binding: every parameter of an annotated constructor is wired
|
|
11015
11106
|
// from form/JSON user input at construction time. Method-level annotation —
|
|
11016
11107
|
// all params tainted.
|
|
@@ -12689,6 +12780,25 @@ var DEFAULT_SINKS = [
|
|
|
12689
12780
|
{ method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
12690
12781
|
{ method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
12691
12782
|
{ method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
12783
|
+
// TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
|
|
12784
|
+
// escape hatch is already covered by the classless `query` sink in the JS
|
|
12785
|
+
// plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
|
|
12786
|
+
// would discard the duplicate anyway. What was uncovered is the
|
|
12787
|
+
// QueryBuilder fragment API: `andWhere`/`orWhere`/`andHaving`/`orHaving`
|
|
12788
|
+
// take raw SQL strings. These names are TypeORM-idiomatic, so classless +
|
|
12789
|
+
// language-scoped is safe and recovers the dominant chained-builder shape
|
|
12790
|
+
// (`repo.createQueryBuilder('u').andWhere(raw)`) that class matching cannot
|
|
12791
|
+
// reach — the receiver of `.andWhere(...)` is the factory call, not a
|
|
12792
|
+
// class-named variable. Generic `where`/`having` are deliberately NOT added
|
|
12793
|
+
// (too generic for classless; a `*QueryBuilder`-scoped entry would be
|
|
12794
|
+
// near-dead since receivers are rarely named that). Precision is inherent
|
|
12795
|
+
// to the flow: parameterised use (`:id` placeholders + a params object)
|
|
12796
|
+
// keeps input out of the string literal, so a finding fires only on
|
|
12797
|
+
// concatenation.
|
|
12798
|
+
{ method: "andWhere", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12799
|
+
{ method: "orWhere", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12800
|
+
{ method: "andHaving", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12801
|
+
{ method: "orHaving", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12692
12802
|
// Browser DOM XSS sinks
|
|
12693
12803
|
{ method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
12694
12804
|
// Angular DomSanitizer.bypassSecurityTrust* — CWE-79 (#184 Sprint 55).
|
|
@@ -12890,6 +13000,12 @@ var DEFAULT_SINKS = [
|
|
|
12890
13000
|
{ method: "executemany", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
12891
13001
|
{ method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
12892
13002
|
{ method: "extra", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
13003
|
+
// Django RawSQL expression (django.db.models.expressions.RawSQL) — the raw
|
|
13004
|
+
// SQL fragment in arg[0] is embedded verbatim into the query when the
|
|
13005
|
+
// annotate/filter/order_by containing it runs. Distinctive constructor name,
|
|
13006
|
+
// so classless + python-scoped is safe. Parameterised use passes user input
|
|
13007
|
+
// through arg[1] (`RawSQL("... %s", [uid])`), which keeps it out of arg[0].
|
|
13008
|
+
{ method: "RawSQL", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
12893
13009
|
// Python Path Traversal
|
|
12894
13010
|
// Language-scoped: classless `open` collides with Java I/O / JS DOM.
|
|
12895
13011
|
{ method: "open", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
@@ -4426,6 +4426,10 @@ function extractJavaScriptTypes(tree, cache) {
|
|
|
4426
4426
|
for (const cls of classes) {
|
|
4427
4427
|
types.push(extractJSClassInfo(cls));
|
|
4428
4428
|
}
|
|
4429
|
+
const interfaces = getNodesFromCache(tree.rootNode, "interface_declaration", cache);
|
|
4430
|
+
for (const iface of interfaces) {
|
|
4431
|
+
types.push(extractTSInterfaceInfo(iface));
|
|
4432
|
+
}
|
|
4429
4433
|
const functions = getNodesFromCache(tree.rootNode, "function_declaration", cache);
|
|
4430
4434
|
if (functions.length > 0) {
|
|
4431
4435
|
const moduleFunctions = [];
|
|
@@ -4761,6 +4765,73 @@ function extractSelfAssignments(body2, fields, fieldNames) {
|
|
|
4761
4765
|
}
|
|
4762
4766
|
}
|
|
4763
4767
|
}
|
|
4768
|
+
function extractTSInterfaceInfo(node) {
|
|
4769
|
+
const name2 = getIdentifier(node, "name") ?? "Anonymous";
|
|
4770
|
+
const parents = [];
|
|
4771
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
4772
|
+
const child = node.child(i2);
|
|
4773
|
+
if (!child || child.type !== "extends_type_clause") continue;
|
|
4774
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
4775
|
+
const gc = child.child(j);
|
|
4776
|
+
if (!gc) continue;
|
|
4777
|
+
if (gc.type === "type_identifier" || gc.type === "generic_type" || gc.type === "nested_type_identifier") {
|
|
4778
|
+
parents.push(getNodeText(gc));
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
const body2 = node.childForFieldName("body");
|
|
4783
|
+
const methods = [];
|
|
4784
|
+
const fields = [];
|
|
4785
|
+
if (body2) {
|
|
4786
|
+
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4787
|
+
const member = body2.child(i2);
|
|
4788
|
+
if (!member) continue;
|
|
4789
|
+
if (member.type === "method_signature") {
|
|
4790
|
+
const methodName = getIdentifier(member, "name") ?? getNodeText(member.child(0) ?? member);
|
|
4791
|
+
const params = member.childForFieldName("parameters");
|
|
4792
|
+
const returnType = member.childForFieldName("return_type");
|
|
4793
|
+
methods.push({
|
|
4794
|
+
name: methodName,
|
|
4795
|
+
return_type: returnType ? getNodeText(returnType).replace(/^:\s*/, "") : null,
|
|
4796
|
+
parameters: params ? extractJSParameters(params) : [],
|
|
4797
|
+
annotations: [],
|
|
4798
|
+
// An interface member has no body and therefore no modifiers in the
|
|
4799
|
+
// class sense; `readonly` / `?` live on property signatures.
|
|
4800
|
+
modifiers: [],
|
|
4801
|
+
start_line: member.startPosition.row + 1,
|
|
4802
|
+
end_line: member.endPosition.row + 1
|
|
4803
|
+
});
|
|
4804
|
+
continue;
|
|
4805
|
+
}
|
|
4806
|
+
if (member.type === "property_signature") {
|
|
4807
|
+
const fieldName = getIdentifier(member, "name") ?? getNodeText(member.child(0) ?? member);
|
|
4808
|
+
const typeNode = member.childForFieldName("type");
|
|
4809
|
+
const modifiers = [];
|
|
4810
|
+
const text = getNodeText(member);
|
|
4811
|
+
if (/^\s*readonly\b/.test(text)) modifiers.push("readonly");
|
|
4812
|
+
if (/\?\s*:/.test(text)) modifiers.push("optional");
|
|
4813
|
+
fields.push({
|
|
4814
|
+
name: fieldName,
|
|
4815
|
+
type: typeNode ? getNodeText(typeNode).replace(/^:\s*/, "") : null,
|
|
4816
|
+
modifiers,
|
|
4817
|
+
annotations: []
|
|
4818
|
+
});
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
}
|
|
4822
|
+
return {
|
|
4823
|
+
name: name2,
|
|
4824
|
+
kind: "interface",
|
|
4825
|
+
package: null,
|
|
4826
|
+
extends: parents[0] ?? null,
|
|
4827
|
+
implements: parents.slice(1),
|
|
4828
|
+
annotations: [],
|
|
4829
|
+
methods,
|
|
4830
|
+
fields,
|
|
4831
|
+
start_line: node.startPosition.row + 1,
|
|
4832
|
+
end_line: node.endPosition.row + 1
|
|
4833
|
+
};
|
|
4834
|
+
}
|
|
4764
4835
|
function extractJSClassInfo(node) {
|
|
4765
4836
|
const name2 = getIdentifier(node, "name") ?? "Anonymous";
|
|
4766
4837
|
let extendsType = null;
|
|
@@ -10406,6 +10477,26 @@ var DEFAULT_SOURCES = [
|
|
|
10406
10477
|
{ annotation: "FormParam", type: "http_param", severity: "high", param_tainted: true },
|
|
10407
10478
|
{ annotation: "PathParam", type: "http_path", severity: "medium", param_tainted: true },
|
|
10408
10479
|
{ annotation: "HeaderParam", type: "http_header", severity: "high", param_tainted: true },
|
|
10480
|
+
// JAX-RS params the block was missing (jakarta.ws.rs). Java-scoped — the
|
|
10481
|
+
// JS decorator ecosystem has its own distinct names and should not match.
|
|
10482
|
+
{ annotation: "CookieParam", type: "http_cookie", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10483
|
+
{ annotation: "MatrixParam", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10484
|
+
{ annotation: "BeanParam", type: "http_body", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10485
|
+
// Micronaut (io.micronaut.http.annotation). @Body / @Header / @CookieValue /
|
|
10486
|
+
// @PathVariable reuse Spring/JAX-RS names already covered above; these three
|
|
10487
|
+
// are Micronaut-specific. Java-scoped.
|
|
10488
|
+
{ annotation: "QueryValue", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10489
|
+
{ annotation: "Part", type: "http_body", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10490
|
+
{ annotation: "RequestBean", type: "http_body", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10491
|
+
// Quarkus / RESTEasy Reactive (org.jboss.resteasy.reactive) — the `@Rest*`
|
|
10492
|
+
// shortcut annotations that infer the binding name from the parameter name.
|
|
10493
|
+
// Java-scoped.
|
|
10494
|
+
{ annotation: "RestQuery", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10495
|
+
{ annotation: "RestPath", type: "http_path", severity: "medium", param_tainted: true, languages: ["java"] },
|
|
10496
|
+
{ annotation: "RestHeader", type: "http_header", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10497
|
+
{ annotation: "RestForm", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10498
|
+
{ annotation: "RestCookie", type: "http_cookie", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10499
|
+
{ annotation: "RestMatrix", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10409
10500
|
// Jenkins data-binding: every parameter of an annotated constructor is wired
|
|
10410
10501
|
// from form/JSON user input at construction time. Method-level annotation —
|
|
10411
10502
|
// all params tainted.
|
|
@@ -12084,6 +12175,25 @@ var DEFAULT_SINKS = [
|
|
|
12084
12175
|
{ method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
12085
12176
|
{ method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
12086
12177
|
{ method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
12178
|
+
// TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
|
|
12179
|
+
// escape hatch is already covered by the classless `query` sink in the JS
|
|
12180
|
+
// plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
|
|
12181
|
+
// would discard the duplicate anyway. What was uncovered is the
|
|
12182
|
+
// QueryBuilder fragment API: `andWhere`/`orWhere`/`andHaving`/`orHaving`
|
|
12183
|
+
// take raw SQL strings. These names are TypeORM-idiomatic, so classless +
|
|
12184
|
+
// language-scoped is safe and recovers the dominant chained-builder shape
|
|
12185
|
+
// (`repo.createQueryBuilder('u').andWhere(raw)`) that class matching cannot
|
|
12186
|
+
// reach — the receiver of `.andWhere(...)` is the factory call, not a
|
|
12187
|
+
// class-named variable. Generic `where`/`having` are deliberately NOT added
|
|
12188
|
+
// (too generic for classless; a `*QueryBuilder`-scoped entry would be
|
|
12189
|
+
// near-dead since receivers are rarely named that). Precision is inherent
|
|
12190
|
+
// to the flow: parameterised use (`:id` placeholders + a params object)
|
|
12191
|
+
// keeps input out of the string literal, so a finding fires only on
|
|
12192
|
+
// concatenation.
|
|
12193
|
+
{ method: "andWhere", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12194
|
+
{ method: "orWhere", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12195
|
+
{ method: "andHaving", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12196
|
+
{ method: "orHaving", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12087
12197
|
// Browser DOM XSS sinks
|
|
12088
12198
|
{ method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
12089
12199
|
// Angular DomSanitizer.bypassSecurityTrust* — CWE-79 (#184 Sprint 55).
|
|
@@ -12285,6 +12395,12 @@ var DEFAULT_SINKS = [
|
|
|
12285
12395
|
{ method: "executemany", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
12286
12396
|
{ method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
12287
12397
|
{ method: "extra", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12398
|
+
// Django RawSQL expression (django.db.models.expressions.RawSQL) — the raw
|
|
12399
|
+
// SQL fragment in arg[0] is embedded verbatim into the query when the
|
|
12400
|
+
// annotate/filter/order_by containing it runs. Distinctive constructor name,
|
|
12401
|
+
// so classless + python-scoped is safe. Parameterised use passes user input
|
|
12402
|
+
// through arg[1] (`RawSQL("... %s", [uid])`), which keeps it out of arg[0].
|
|
12403
|
+
{ method: "RawSQL", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
12288
12404
|
// Python Path Traversal
|
|
12289
12405
|
// Language-scoped: classless `open` collides with Java I/O / JS DOM.
|
|
12290
12406
|
{ method: "open", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
@@ -4360,6 +4360,10 @@ function extractJavaScriptTypes(tree, cache) {
|
|
|
4360
4360
|
for (const cls of classes) {
|
|
4361
4361
|
types.push(extractJSClassInfo(cls));
|
|
4362
4362
|
}
|
|
4363
|
+
const interfaces = getNodesFromCache(tree.rootNode, "interface_declaration", cache);
|
|
4364
|
+
for (const iface of interfaces) {
|
|
4365
|
+
types.push(extractTSInterfaceInfo(iface));
|
|
4366
|
+
}
|
|
4363
4367
|
const functions = getNodesFromCache(tree.rootNode, "function_declaration", cache);
|
|
4364
4368
|
if (functions.length > 0) {
|
|
4365
4369
|
const moduleFunctions = [];
|
|
@@ -4695,6 +4699,73 @@ function extractSelfAssignments(body2, fields, fieldNames) {
|
|
|
4695
4699
|
}
|
|
4696
4700
|
}
|
|
4697
4701
|
}
|
|
4702
|
+
function extractTSInterfaceInfo(node) {
|
|
4703
|
+
const name2 = getIdentifier(node, "name") ?? "Anonymous";
|
|
4704
|
+
const parents = [];
|
|
4705
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
4706
|
+
const child = node.child(i2);
|
|
4707
|
+
if (!child || child.type !== "extends_type_clause") continue;
|
|
4708
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
4709
|
+
const gc = child.child(j);
|
|
4710
|
+
if (!gc) continue;
|
|
4711
|
+
if (gc.type === "type_identifier" || gc.type === "generic_type" || gc.type === "nested_type_identifier") {
|
|
4712
|
+
parents.push(getNodeText(gc));
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
const body2 = node.childForFieldName("body");
|
|
4717
|
+
const methods = [];
|
|
4718
|
+
const fields = [];
|
|
4719
|
+
if (body2) {
|
|
4720
|
+
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4721
|
+
const member = body2.child(i2);
|
|
4722
|
+
if (!member) continue;
|
|
4723
|
+
if (member.type === "method_signature") {
|
|
4724
|
+
const methodName = getIdentifier(member, "name") ?? getNodeText(member.child(0) ?? member);
|
|
4725
|
+
const params = member.childForFieldName("parameters");
|
|
4726
|
+
const returnType = member.childForFieldName("return_type");
|
|
4727
|
+
methods.push({
|
|
4728
|
+
name: methodName,
|
|
4729
|
+
return_type: returnType ? getNodeText(returnType).replace(/^:\s*/, "") : null,
|
|
4730
|
+
parameters: params ? extractJSParameters(params) : [],
|
|
4731
|
+
annotations: [],
|
|
4732
|
+
// An interface member has no body and therefore no modifiers in the
|
|
4733
|
+
// class sense; `readonly` / `?` live on property signatures.
|
|
4734
|
+
modifiers: [],
|
|
4735
|
+
start_line: member.startPosition.row + 1,
|
|
4736
|
+
end_line: member.endPosition.row + 1
|
|
4737
|
+
});
|
|
4738
|
+
continue;
|
|
4739
|
+
}
|
|
4740
|
+
if (member.type === "property_signature") {
|
|
4741
|
+
const fieldName = getIdentifier(member, "name") ?? getNodeText(member.child(0) ?? member);
|
|
4742
|
+
const typeNode = member.childForFieldName("type");
|
|
4743
|
+
const modifiers = [];
|
|
4744
|
+
const text = getNodeText(member);
|
|
4745
|
+
if (/^\s*readonly\b/.test(text)) modifiers.push("readonly");
|
|
4746
|
+
if (/\?\s*:/.test(text)) modifiers.push("optional");
|
|
4747
|
+
fields.push({
|
|
4748
|
+
name: fieldName,
|
|
4749
|
+
type: typeNode ? getNodeText(typeNode).replace(/^:\s*/, "") : null,
|
|
4750
|
+
modifiers,
|
|
4751
|
+
annotations: []
|
|
4752
|
+
});
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
return {
|
|
4757
|
+
name: name2,
|
|
4758
|
+
kind: "interface",
|
|
4759
|
+
package: null,
|
|
4760
|
+
extends: parents[0] ?? null,
|
|
4761
|
+
implements: parents.slice(1),
|
|
4762
|
+
annotations: [],
|
|
4763
|
+
methods,
|
|
4764
|
+
fields,
|
|
4765
|
+
start_line: node.startPosition.row + 1,
|
|
4766
|
+
end_line: node.endPosition.row + 1
|
|
4767
|
+
};
|
|
4768
|
+
}
|
|
4698
4769
|
function extractJSClassInfo(node) {
|
|
4699
4770
|
const name2 = getIdentifier(node, "name") ?? "Anonymous";
|
|
4700
4771
|
let extendsType = null;
|
|
@@ -10340,6 +10411,26 @@ var DEFAULT_SOURCES = [
|
|
|
10340
10411
|
{ annotation: "FormParam", type: "http_param", severity: "high", param_tainted: true },
|
|
10341
10412
|
{ annotation: "PathParam", type: "http_path", severity: "medium", param_tainted: true },
|
|
10342
10413
|
{ annotation: "HeaderParam", type: "http_header", severity: "high", param_tainted: true },
|
|
10414
|
+
// JAX-RS params the block was missing (jakarta.ws.rs). Java-scoped — the
|
|
10415
|
+
// JS decorator ecosystem has its own distinct names and should not match.
|
|
10416
|
+
{ annotation: "CookieParam", type: "http_cookie", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10417
|
+
{ annotation: "MatrixParam", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10418
|
+
{ annotation: "BeanParam", type: "http_body", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10419
|
+
// Micronaut (io.micronaut.http.annotation). @Body / @Header / @CookieValue /
|
|
10420
|
+
// @PathVariable reuse Spring/JAX-RS names already covered above; these three
|
|
10421
|
+
// are Micronaut-specific. Java-scoped.
|
|
10422
|
+
{ annotation: "QueryValue", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10423
|
+
{ annotation: "Part", type: "http_body", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10424
|
+
{ annotation: "RequestBean", type: "http_body", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10425
|
+
// Quarkus / RESTEasy Reactive (org.jboss.resteasy.reactive) — the `@Rest*`
|
|
10426
|
+
// shortcut annotations that infer the binding name from the parameter name.
|
|
10427
|
+
// Java-scoped.
|
|
10428
|
+
{ annotation: "RestQuery", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10429
|
+
{ annotation: "RestPath", type: "http_path", severity: "medium", param_tainted: true, languages: ["java"] },
|
|
10430
|
+
{ annotation: "RestHeader", type: "http_header", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10431
|
+
{ annotation: "RestForm", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10432
|
+
{ annotation: "RestCookie", type: "http_cookie", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10433
|
+
{ annotation: "RestMatrix", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
10343
10434
|
// Jenkins data-binding: every parameter of an annotated constructor is wired
|
|
10344
10435
|
// from form/JSON user input at construction time. Method-level annotation —
|
|
10345
10436
|
// all params tainted.
|
|
@@ -12018,6 +12109,25 @@ var DEFAULT_SINKS = [
|
|
|
12018
12109
|
{ method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
12019
12110
|
{ method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
12020
12111
|
{ method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
12112
|
+
// TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
|
|
12113
|
+
// escape hatch is already covered by the classless `query` sink in the JS
|
|
12114
|
+
// plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
|
|
12115
|
+
// would discard the duplicate anyway. What was uncovered is the
|
|
12116
|
+
// QueryBuilder fragment API: `andWhere`/`orWhere`/`andHaving`/`orHaving`
|
|
12117
|
+
// take raw SQL strings. These names are TypeORM-idiomatic, so classless +
|
|
12118
|
+
// language-scoped is safe and recovers the dominant chained-builder shape
|
|
12119
|
+
// (`repo.createQueryBuilder('u').andWhere(raw)`) that class matching cannot
|
|
12120
|
+
// reach — the receiver of `.andWhere(...)` is the factory call, not a
|
|
12121
|
+
// class-named variable. Generic `where`/`having` are deliberately NOT added
|
|
12122
|
+
// (too generic for classless; a `*QueryBuilder`-scoped entry would be
|
|
12123
|
+
// near-dead since receivers are rarely named that). Precision is inherent
|
|
12124
|
+
// to the flow: parameterised use (`:id` placeholders + a params object)
|
|
12125
|
+
// keeps input out of the string literal, so a finding fires only on
|
|
12126
|
+
// concatenation.
|
|
12127
|
+
{ method: "andWhere", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12128
|
+
{ method: "orWhere", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12129
|
+
{ method: "andHaving", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12130
|
+
{ method: "orHaving", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12021
12131
|
// Browser DOM XSS sinks
|
|
12022
12132
|
{ method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
12023
12133
|
// Angular DomSanitizer.bypassSecurityTrust* — CWE-79 (#184 Sprint 55).
|
|
@@ -12219,6 +12329,12 @@ var DEFAULT_SINKS = [
|
|
|
12219
12329
|
{ method: "executemany", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
12220
12330
|
{ method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
12221
12331
|
{ method: "extra", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12332
|
+
// Django RawSQL expression (django.db.models.expressions.RawSQL) — the raw
|
|
12333
|
+
// SQL fragment in arg[0] is embedded verbatim into the query when the
|
|
12334
|
+
// annotate/filter/order_by containing it runs. Distinctive constructor name,
|
|
12335
|
+
// so classless + python-scoped is safe. Parameterised use passes user input
|
|
12336
|
+
// through arg[1] (`RawSQL("... %s", [uid])`), which keeps it out of arg[0].
|
|
12337
|
+
{ method: "RawSQL", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
12222
12338
|
// Python Path Traversal
|
|
12223
12339
|
// Language-scoped: classless `open` collides with Java I/O / JS DOM.
|
|
12224
12340
|
{ method: "open", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
@@ -100,6 +100,12 @@ function extractJavaScriptTypes(tree, cache) {
|
|
|
100
100
|
for (const cls of classes) {
|
|
101
101
|
types.push(extractJSClassInfo(cls));
|
|
102
102
|
}
|
|
103
|
+
// Extract TypeScript interfaces. Only the TS/TSX grammars produce
|
|
104
|
+
// `interface_declaration`, so this is a no-op for plain JavaScript.
|
|
105
|
+
const interfaces = getNodesFromCache(tree.rootNode, 'interface_declaration', cache);
|
|
106
|
+
for (const iface of interfaces) {
|
|
107
|
+
types.push(extractTSInterfaceInfo(iface));
|
|
108
|
+
}
|
|
103
109
|
// Extract standalone functions as a module-like type
|
|
104
110
|
const functions = getNodesFromCache(tree.rootNode, 'function_declaration', cache);
|
|
105
111
|
if (functions.length > 0) {
|
|
@@ -516,6 +522,97 @@ function extractSelfAssignments(body, fields, fieldNames) {
|
|
|
516
522
|
/**
|
|
517
523
|
* Extract JavaScript class information.
|
|
518
524
|
*/
|
|
525
|
+
/**
|
|
526
|
+
* Extract a TypeScript `interface_declaration` as a `TypeInfo`.
|
|
527
|
+
*
|
|
528
|
+
* The TS grammar produces `interface_declaration` → `interface_body`, whose
|
|
529
|
+
* members are `property_signature` (fields, including function-typed ones
|
|
530
|
+
* such as `onEvent: (e: string) => void`) and `method_signature` (methods).
|
|
531
|
+
* Java interfaces are handled separately by `extractInterfaceInfo`, whose body
|
|
532
|
+
* shape is different — the two cannot share an implementation.
|
|
533
|
+
*
|
|
534
|
+
* Needed because a type contract declared as an interface was previously
|
|
535
|
+
* dropped entirely: `interface UserRepo` alongside
|
|
536
|
+
* `class SqlUserRepo implements UserRepo` yielded one `TypeInfo`, the class.
|
|
537
|
+
* Cross-instance taint analysis (Issue #1) needs the declared contract to
|
|
538
|
+
* resolve a field's type when the declaration site is an interface.
|
|
539
|
+
*/
|
|
540
|
+
function extractTSInterfaceInfo(node) {
|
|
541
|
+
const name = getIdentifier(node, 'name') ?? 'Anonymous';
|
|
542
|
+
// `interface A extends B, C` — tree-sitter-typescript wraps these in an
|
|
543
|
+
// `extends_type_clause`. `TypeInfo.extends` is a single string, so the first
|
|
544
|
+
// parent goes there and the rest land in `implements`, which mirrors how a
|
|
545
|
+
// consumer reads "the contracts this type carries".
|
|
546
|
+
const parents = [];
|
|
547
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
548
|
+
const child = node.child(i);
|
|
549
|
+
if (!child || child.type !== 'extends_type_clause')
|
|
550
|
+
continue;
|
|
551
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
552
|
+
const gc = child.child(j);
|
|
553
|
+
if (!gc)
|
|
554
|
+
continue;
|
|
555
|
+
if (gc.type === 'type_identifier' || gc.type === 'generic_type' || gc.type === 'nested_type_identifier') {
|
|
556
|
+
parents.push(getNodeText(gc));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const body = node.childForFieldName('body');
|
|
561
|
+
const methods = [];
|
|
562
|
+
const fields = [];
|
|
563
|
+
if (body) {
|
|
564
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
565
|
+
const member = body.child(i);
|
|
566
|
+
if (!member)
|
|
567
|
+
continue;
|
|
568
|
+
if (member.type === 'method_signature') {
|
|
569
|
+
const methodName = getIdentifier(member, 'name') ?? getNodeText(member.child(0) ?? member);
|
|
570
|
+
const params = member.childForFieldName('parameters');
|
|
571
|
+
const returnType = member.childForFieldName('return_type');
|
|
572
|
+
methods.push({
|
|
573
|
+
name: methodName,
|
|
574
|
+
return_type: returnType ? getNodeText(returnType).replace(/^:\s*/, '') : null,
|
|
575
|
+
parameters: params ? extractJSParameters(params) : [],
|
|
576
|
+
annotations: [],
|
|
577
|
+
// An interface member has no body and therefore no modifiers in the
|
|
578
|
+
// class sense; `readonly` / `?` live on property signatures.
|
|
579
|
+
modifiers: [],
|
|
580
|
+
start_line: member.startPosition.row + 1,
|
|
581
|
+
end_line: member.endPosition.row + 1,
|
|
582
|
+
});
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
if (member.type === 'property_signature') {
|
|
586
|
+
const fieldName = getIdentifier(member, 'name') ?? getNodeText(member.child(0) ?? member);
|
|
587
|
+
const typeNode = member.childForFieldName('type');
|
|
588
|
+
const modifiers = [];
|
|
589
|
+
const text = getNodeText(member);
|
|
590
|
+
if (/^\s*readonly\b/.test(text))
|
|
591
|
+
modifiers.push('readonly');
|
|
592
|
+
if (/\?\s*:/.test(text))
|
|
593
|
+
modifiers.push('optional');
|
|
594
|
+
fields.push({
|
|
595
|
+
name: fieldName,
|
|
596
|
+
type: typeNode ? getNodeText(typeNode).replace(/^:\s*/, '') : null,
|
|
597
|
+
modifiers,
|
|
598
|
+
annotations: [],
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
name,
|
|
605
|
+
kind: 'interface',
|
|
606
|
+
package: null,
|
|
607
|
+
extends: parents[0] ?? null,
|
|
608
|
+
implements: parents.slice(1),
|
|
609
|
+
annotations: [],
|
|
610
|
+
methods,
|
|
611
|
+
fields,
|
|
612
|
+
start_line: node.startPosition.row + 1,
|
|
613
|
+
end_line: node.endPosition.row + 1,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
519
616
|
function extractJSClassInfo(node) {
|
|
520
617
|
const name = getIdentifier(node, 'name') ?? 'Anonymous';
|
|
521
618
|
// Extract superclass (extends)
|