circle-ir 3.53.0 → 3.55.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 +87 -3
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/findings.d.ts.map +1 -1
- package/dist/analysis/findings.js +11 -6
- package/dist/analysis/findings.js.map +1 -1
- package/dist/analysis/passes/csrf-protection-disabled-pass.d.ts +42 -0
- package/dist/analysis/passes/csrf-protection-disabled-pass.d.ts.map +1 -0
- package/dist/analysis/passes/csrf-protection-disabled-pass.js +185 -0
- package/dist/analysis/passes/csrf-protection-disabled-pass.js.map +1 -0
- package/dist/analysis/passes/jwt-verify-disabled-pass.d.ts +45 -0
- package/dist/analysis/passes/jwt-verify-disabled-pass.d.ts.map +1 -0
- package/dist/analysis/passes/jwt-verify-disabled-pass.js +164 -0
- package/dist/analysis/passes/jwt-verify-disabled-pass.js.map +1 -0
- package/dist/analysis/passes/mass-assignment-pass.d.ts +41 -0
- package/dist/analysis/passes/mass-assignment-pass.d.ts.map +1 -0
- package/dist/analysis/passes/mass-assignment-pass.js +124 -0
- package/dist/analysis/passes/mass-assignment-pass.js.map +1 -0
- package/dist/analysis/passes/xml-entity-expansion-pass.d.ts +58 -0
- package/dist/analysis/passes/xml-entity-expansion-pass.d.ts.map +1 -0
- package/dist/analysis/passes/xml-entity-expansion-pass.js +196 -0
- package/dist/analysis/passes/xml-entity-expansion-pass.js.map +1 -0
- package/dist/analysis/rules.d.ts.map +1 -1
- package/dist/analysis/rules.js +36 -0
- package/dist/analysis/rules.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +1 -0
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +12 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +549 -11
- package/dist/core/circle-ir-core.cjs +93 -5
- package/dist/core/circle-ir-core.js +93 -5
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -11177,9 +11177,16 @@ var DEFAULT_SINKS = [
|
|
|
11177
11177
|
{ method: "println", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11178
11178
|
// XSS in error messages (CWE-81)
|
|
11179
11179
|
{ method: "sendError", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
11180
|
-
// Response header injection
|
|
11181
|
-
|
|
11182
|
-
|
|
11180
|
+
// Response header injection — re-categorised from `xss` to `crlf`
|
|
11181
|
+
// (CWE-113) in Sprint 6 of #86. Header injection is HTTP response
|
|
11182
|
+
// splitting / cache-poisoning / cookie forging; reflected XSS via header
|
|
11183
|
+
// reflection remains a downstream concern of body-writing sinks.
|
|
11184
|
+
{ method: "setHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
11185
|
+
{ method: "addHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
11186
|
+
// Note: `sendRedirect` is primarily classified as `ssrf` / open-redirect
|
|
11187
|
+
// (CWE-601) further down — see entry near line 1195. CRLF via Location
|
|
11188
|
+
// header is a secondary concern; keeping the canonical SSRF entry avoids
|
|
11189
|
+
// double-emission that would mask the open-redirect chain.
|
|
11183
11190
|
{ method: "setContentType", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
|
|
11184
11191
|
// JSP output
|
|
11185
11192
|
{ method: "setAttribute", class: "PageContext", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
@@ -12097,7 +12104,84 @@ var DEFAULT_SINKS = [
|
|
|
12097
12104
|
{ method: "from_str", class: "serde_yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
12098
12105
|
{ method: "from_reader", class: "serde_yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
12099
12106
|
{ method: "from_str", class: "serde_json", type: "deserialization", cwe: "CWE-502", severity: "medium", arg_positions: [0] },
|
|
12100
|
-
{ method: "from_slice", class: "serde_json", type: "deserialization", cwe: "CWE-502", severity: "medium", arg_positions: [0] }
|
|
12107
|
+
{ method: "from_slice", class: "serde_json", type: "deserialization", cwe: "CWE-502", severity: "medium", arg_positions: [0] },
|
|
12108
|
+
// =========================================================================
|
|
12109
|
+
// ReDoS sinks (CWE-1333) — issue #86 / Sprint 5
|
|
12110
|
+
// =========================================================================
|
|
12111
|
+
// First argument of regex compile/match functions is the pattern. Tainted
|
|
12112
|
+
// patterns enable catastrophic-backtracking DoS.
|
|
12113
|
+
// Python: re.{match,search,compile,findall,fullmatch,sub,subn,split}
|
|
12114
|
+
{ method: "match", class: "re", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12115
|
+
{ method: "search", class: "re", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12116
|
+
{ method: "fullmatch", class: "re", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12117
|
+
{ method: "compile", class: "re", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12118
|
+
{ method: "findall", class: "re", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12119
|
+
{ method: "finditer", class: "re", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12120
|
+
{ method: "sub", class: "re", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12121
|
+
{ method: "subn", class: "re", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12122
|
+
{ method: "split", class: "re", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12123
|
+
// Java: Pattern.compile / Pattern.matches; String.matches/replaceAll/replaceFirst/split
|
|
12124
|
+
{ method: "compile", class: "Pattern", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12125
|
+
{ method: "matches", class: "Pattern", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12126
|
+
{ method: "matches", class: "String", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12127
|
+
{ method: "replaceAll", class: "String", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12128
|
+
{ method: "replaceFirst", class: "String", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12129
|
+
{ method: "split", class: "String", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12130
|
+
// JS/TS: new RegExp(pat) ctor; receiver_type === 'RegExp'. Also string.match
|
|
12131
|
+
// and string.matchAll, replace, search take a regex/string pattern.
|
|
12132
|
+
{ method: "RegExp", class: "constructor", type: "redos", cwe: "CWE-1333", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12133
|
+
// Go: regexp.Compile / MustCompile / Match / MatchString
|
|
12134
|
+
{ method: "Compile", class: "regexp", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
12135
|
+
{ method: "MustCompile", class: "regexp", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
12136
|
+
{ method: "Match", class: "regexp", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
12137
|
+
{ method: "MatchString", class: "regexp", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
12138
|
+
// =========================================================================
|
|
12139
|
+
// Format-string sinks (CWE-134) — issue #86 / Sprint 5
|
|
12140
|
+
// =========================================================================
|
|
12141
|
+
// First argument is the format string. Tainted format strings enable
|
|
12142
|
+
// information disclosure and (for C-style runtimes) memory writes.
|
|
12143
|
+
// Java: String.format / Formatter.format / printf / format on PrintStream
|
|
12144
|
+
// (note: printf/format on PrintWriter/PrintStream are already XSS sinks above)
|
|
12145
|
+
{ method: "format", class: "String", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12146
|
+
{ method: "format", class: "Formatter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12147
|
+
{ method: "printf", class: "System.out", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12148
|
+
// NOTE: Python `userFmt.format(...)` and `userFmt % args` require
|
|
12149
|
+
// receiver-taint or operator-LHS-taint tracking — the format string is the
|
|
12150
|
+
// receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
|
|
12151
|
+
// C-style: printf / fprintf / sprintf / snprintf via ctypes/cffi.
|
|
12152
|
+
{ method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12153
|
+
{ method: "fprintf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [1], languages: ["python"] },
|
|
12154
|
+
// Go: fmt.Sprintf/Printf/Fprintf/Errorf — format string is first/second arg
|
|
12155
|
+
{ method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
12156
|
+
{ method: "Printf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
12157
|
+
{ method: "Errorf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
12158
|
+
{ method: "Fprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
12159
|
+
// CRLF / HTTP response splitting (CWE-113) — Sprint 6, #86.
|
|
12160
|
+
// Node.js / Express response header / cookie sinks. The header *name* (arg 0)
|
|
12161
|
+
// is also CRLF-sensitive but is almost always a string literal; we model
|
|
12162
|
+
// arg 1 (the value) as the primary sink.
|
|
12163
|
+
{ method: "setHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
|
|
12164
|
+
{ method: "writeHead", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [2], languages: ["javascript", "typescript"] },
|
|
12165
|
+
// Express: res.cookie(name, value, options) — value is CRLF-sensitive.
|
|
12166
|
+
{ method: "cookie", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
|
|
12167
|
+
// Express: res.location(url) and res.redirect(url) — Location header.
|
|
12168
|
+
{ method: "location", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12169
|
+
{ method: "redirect", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12170
|
+
// Go net/http: w.Header().Set(k, v) / Add(k, v) — first arg is the value
|
|
12171
|
+
// (Header is a map; the actual `value` is arg 1 of the call). We flag the
|
|
12172
|
+
// value position so a tainted variable is detected.
|
|
12173
|
+
{ method: "Set", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
12174
|
+
{ method: "Add", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
12175
|
+
// Mass-assignment (CWE-915) — Sprint 6, #86.
|
|
12176
|
+
// JS Object.assign(target, ...sources) — sources are arg 1..N, and if any
|
|
12177
|
+
// source is request-tainted, every key gets written onto the target. We
|
|
12178
|
+
// flag the source positions; the analyzer only needs one tainted to fire.
|
|
12179
|
+
{ method: "assign", class: "Object", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12180
|
+
// Lodash bulk-merge helpers behave identically.
|
|
12181
|
+
{ method: "merge", class: "_", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12182
|
+
{ method: "extend", class: "_", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
12183
|
+
// jQuery $.extend(target, source) (legacy).
|
|
12184
|
+
{ method: "extend", class: "$", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] }
|
|
12101
12185
|
];
|
|
12102
12186
|
var DEFAULT_SANITIZERS = [
|
|
12103
12187
|
// SQL Injection - proper parameter binding sanitizes input
|
|
@@ -13567,12 +13651,17 @@ function canSourceReachSink(sourceType, sinkType) {
|
|
|
13567
13651
|
// code_injection added to http_param/http_query/http_header/http_cookie:
|
|
13568
13652
|
// `eval(req.query.x)`, `Function(req.header('x'))`, `vm.runInThisContext(req.cookies.c)`
|
|
13569
13653
|
// are all real RCE patterns in JS web apps (cognium-dev #83).
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13654
|
+
// crlf added to http_param/http_query/http_header/http_cookie/http_body:
|
|
13655
|
+
// setHeader/setCookie/redirect of any user-controlled string is CRLF / response
|
|
13656
|
+
// splitting (CWE-113) — Sprint 6, issue #86.
|
|
13657
|
+
// mass_assignment added to http_body / http_param: Object.assign(user, req.body),
|
|
13658
|
+
// User(**request.form) — CWE-915.
|
|
13659
|
+
http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment"],
|
|
13660
|
+
http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment"],
|
|
13661
|
+
http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf"],
|
|
13662
|
+
http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf"],
|
|
13574
13663
|
http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call"],
|
|
13575
|
-
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection"],
|
|
13664
|
+
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment"],
|
|
13576
13665
|
io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss"],
|
|
13577
13666
|
env_input: ["command_injection", "path_traversal"],
|
|
13578
13667
|
db_input: ["xss", "sql_injection"],
|
|
@@ -13581,7 +13670,7 @@ function canSourceReachSink(sourceType, sinkType) {
|
|
|
13581
13670
|
network_input: ["sql_injection", "command_injection", "xss", "ssrf"],
|
|
13582
13671
|
config_param: ["sql_injection", "command_injection", "path_traversal", "xss", "ssrf"],
|
|
13583
13672
|
// Servlet init params
|
|
13584
|
-
interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call"],
|
|
13673
|
+
interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment"],
|
|
13585
13674
|
// Cross-method taint
|
|
13586
13675
|
plugin_param: ["sql_injection", "command_injection", "path_traversal", "xss", "code_injection"]
|
|
13587
13676
|
// Plugin/config parameters
|
|
@@ -14780,7 +14869,11 @@ var KNOWN_SINK_TYPES = /* @__PURE__ */ new Set([
|
|
|
14780
14869
|
"xxe",
|
|
14781
14870
|
"deserialization",
|
|
14782
14871
|
"code_injection",
|
|
14783
|
-
"mybatis_mapper_call"
|
|
14872
|
+
"mybatis_mapper_call",
|
|
14873
|
+
"redos",
|
|
14874
|
+
"format_string",
|
|
14875
|
+
"crlf",
|
|
14876
|
+
"mass_assignment"
|
|
14784
14877
|
]);
|
|
14785
14878
|
function checkSanitized(_fromLine, toLine, sinkType, sanitizersByLine) {
|
|
14786
14879
|
const sanitizersAtTarget = sanitizersByLine.get(toLine);
|
|
@@ -27928,6 +28021,447 @@ var TlsVerifyDisabledPass = class {
|
|
|
27928
28021
|
}
|
|
27929
28022
|
};
|
|
27930
28023
|
|
|
28024
|
+
// src/analysis/passes/jwt-verify-disabled-pass.ts
|
|
28025
|
+
var PY_VERIFY_SIGNATURE_FALSE_RE = /["']verify_signature["']\s*:\s*False\b/;
|
|
28026
|
+
var PY_VERIFY_KW_FALSE_RE = /\bverify\s*=\s*False\b/;
|
|
28027
|
+
var PY_ALG_NONE_RE = /\balgorithms\s*=\s*[\[\(]\s*["']none["']/i;
|
|
28028
|
+
var JS_ALG_NONE_RE = /\balgorithms\s*:\s*\[\s*["']none["']/i;
|
|
28029
|
+
var JwtVerifyDisabledPass = class {
|
|
28030
|
+
name = "jwt-verify-disabled";
|
|
28031
|
+
category = "security";
|
|
28032
|
+
run(ctx) {
|
|
28033
|
+
const { graph, language } = ctx;
|
|
28034
|
+
const file = graph.ir.meta.file;
|
|
28035
|
+
const findings = [];
|
|
28036
|
+
for (const call of graph.ir.calls) {
|
|
28037
|
+
const detections = this.detect(call, language);
|
|
28038
|
+
for (const det of detections) {
|
|
28039
|
+
const line = call.location.line;
|
|
28040
|
+
findings.push({ line, language, ...det });
|
|
28041
|
+
ctx.addFinding({
|
|
28042
|
+
id: `${this.name}-${file}-${line}-${det.pattern}`,
|
|
28043
|
+
pass: this.name,
|
|
28044
|
+
category: this.category,
|
|
28045
|
+
rule_id: this.name,
|
|
28046
|
+
cwe: "CWE-347",
|
|
28047
|
+
severity: "critical",
|
|
28048
|
+
level: "error",
|
|
28049
|
+
message: `JWT signature verification disabled via \`${det.pattern}\` in \`${det.api}\`. Any attacker can forge a token with arbitrary claims (user id, roles, expiry) since the signature is not checked.`,
|
|
28050
|
+
file,
|
|
28051
|
+
line,
|
|
28052
|
+
fix: this.fixFor(language),
|
|
28053
|
+
evidence: { ...det, language }
|
|
28054
|
+
});
|
|
28055
|
+
}
|
|
28056
|
+
}
|
|
28057
|
+
return { findings };
|
|
28058
|
+
}
|
|
28059
|
+
detect(call, language) {
|
|
28060
|
+
const method = call.method_name;
|
|
28061
|
+
const receiver = call.receiver ?? "";
|
|
28062
|
+
const out2 = [];
|
|
28063
|
+
if (language === "python") {
|
|
28064
|
+
if (receiver === "jwt" && method === "decode") {
|
|
28065
|
+
for (const arg of call.arguments) {
|
|
28066
|
+
const expr = (arg.expression ?? "").trim();
|
|
28067
|
+
if (!expr) continue;
|
|
28068
|
+
if (PY_VERIFY_SIGNATURE_FALSE_RE.test(expr)) {
|
|
28069
|
+
out2.push({ pattern: "verify_signature: False", api: "jwt.decode" });
|
|
28070
|
+
}
|
|
28071
|
+
if (PY_VERIFY_KW_FALSE_RE.test(expr)) {
|
|
28072
|
+
out2.push({ pattern: "verify=False", api: "jwt.decode" });
|
|
28073
|
+
}
|
|
28074
|
+
if (PY_ALG_NONE_RE.test(expr)) {
|
|
28075
|
+
out2.push({ pattern: "algorithms=['none']", api: "jwt.decode" });
|
|
28076
|
+
}
|
|
28077
|
+
}
|
|
28078
|
+
}
|
|
28079
|
+
return out2;
|
|
28080
|
+
}
|
|
28081
|
+
if (language === "javascript" || language === "typescript") {
|
|
28082
|
+
if (receiver === "jwt" && method === "verify") {
|
|
28083
|
+
for (const arg of call.arguments) {
|
|
28084
|
+
const expr = (arg.expression ?? "").trim();
|
|
28085
|
+
if (!expr) continue;
|
|
28086
|
+
if (JS_ALG_NONE_RE.test(expr)) {
|
|
28087
|
+
out2.push({ pattern: "algorithms: ['none']", api: "jwt.verify" });
|
|
28088
|
+
}
|
|
28089
|
+
if (/\bverify\s*:\s*false\b/i.test(expr)) {
|
|
28090
|
+
out2.push({ pattern: "verify: false", api: "jwt.verify" });
|
|
28091
|
+
}
|
|
28092
|
+
}
|
|
28093
|
+
const keyArg = call.arguments.find((a) => a.position === 1);
|
|
28094
|
+
const keyExpr = (keyArg?.expression ?? keyArg?.literal ?? "").trim();
|
|
28095
|
+
if (keyExpr === "null" || keyExpr === "undefined" || keyExpr === '""' || keyExpr === "''" || keyExpr === "``") {
|
|
28096
|
+
out2.push({ pattern: `empty key (${keyExpr || "missing"})`, api: "jwt.verify" });
|
|
28097
|
+
}
|
|
28098
|
+
}
|
|
28099
|
+
return out2;
|
|
28100
|
+
}
|
|
28101
|
+
if (language === "java") {
|
|
28102
|
+
if (method === "require" && (receiver === "JWT" || receiver.endsWith(".JWT"))) {
|
|
28103
|
+
const arg = call.arguments.find((a) => a.position === 0);
|
|
28104
|
+
const expr = (arg?.expression ?? "").trim();
|
|
28105
|
+
if (/\bAlgorithm\s*\.\s*none\s*\(/.test(expr)) {
|
|
28106
|
+
out2.push({ pattern: "Algorithm.none()", api: "JWT.require" });
|
|
28107
|
+
}
|
|
28108
|
+
}
|
|
28109
|
+
if (method === "parse" && receiver.includes("parser")) {
|
|
28110
|
+
out2.push({ pattern: "parse() instead of parseClaimsJws()", api: "Jwts.parser().parse" });
|
|
28111
|
+
}
|
|
28112
|
+
return out2;
|
|
28113
|
+
}
|
|
28114
|
+
return out2;
|
|
28115
|
+
}
|
|
28116
|
+
fixFor(language) {
|
|
28117
|
+
if (language === "python") {
|
|
28118
|
+
return 'Always pass `options={"verify_signature": True}` (the default in PyJWT 2.0+) and a concrete `algorithms=["HS256"|"RS256"]` list. Never accept `none`.';
|
|
28119
|
+
}
|
|
28120
|
+
if (language === "javascript" || language === "typescript") {
|
|
28121
|
+
return 'Call `jwt.verify(token, secret, { algorithms: ["HS256" | "RS256"] })` with a non-empty key. Never use `algorithms: ["none"]` or pass null/empty as the secret.';
|
|
28122
|
+
}
|
|
28123
|
+
if (language === "java") {
|
|
28124
|
+
return "For auth0/java-jwt: use `JWT.require(Algorithm.HMAC256(secret))` or an RSA algorithm. For jjwt: call `parseClaimsJws(token)` (signature enforced) rather than `parse(token)` (signature ignored).";
|
|
28125
|
+
}
|
|
28126
|
+
return "Enforce JWT signature verification with a concrete algorithm (HS256/RS256/ES256). Never accept `alg: none`.";
|
|
28127
|
+
}
|
|
28128
|
+
};
|
|
28129
|
+
|
|
28130
|
+
// src/analysis/passes/csrf-protection-disabled-pass.ts
|
|
28131
|
+
var JAVA_CSRF_DISABLE_RE = /\.csrf\s*\([^)]*\)\s*\.\s*disable\b/;
|
|
28132
|
+
var JAVA_CSRF_LAMBDA_DISABLE_RE = /\bcsrf\s*\(\s*\w+\s*->\s*\w+\s*\.\s*disable\s*\(/;
|
|
28133
|
+
var JAVA_CSRF_METHODREF_RE = /\bcsrf\s*\(\s*[\w.]+::disable\s*\)/;
|
|
28134
|
+
var JAVA_CSRF_NULL_REPO_RE = /\.csrfTokenRepository\s*\(\s*null\s*\)/;
|
|
28135
|
+
var CsrfProtectionDisabledPass = class {
|
|
28136
|
+
name = "csrf-protection-disabled";
|
|
28137
|
+
category = "security";
|
|
28138
|
+
run(ctx) {
|
|
28139
|
+
const { graph, language } = ctx;
|
|
28140
|
+
const file = graph.ir.meta.file;
|
|
28141
|
+
const findings = [];
|
|
28142
|
+
for (const call of graph.ir.calls) {
|
|
28143
|
+
const detections = this.detectCall(call, language);
|
|
28144
|
+
for (const det of detections) {
|
|
28145
|
+
const line = call.location.line;
|
|
28146
|
+
findings.push({ line, language, ...det });
|
|
28147
|
+
ctx.addFinding({
|
|
28148
|
+
id: `${this.name}-${file}-${line}-${det.pattern}`,
|
|
28149
|
+
pass: this.name,
|
|
28150
|
+
category: this.category,
|
|
28151
|
+
rule_id: this.name,
|
|
28152
|
+
cwe: "CWE-352",
|
|
28153
|
+
severity: "critical",
|
|
28154
|
+
level: "error",
|
|
28155
|
+
message: `CSRF protection explicitly disabled via \`${det.pattern}\` (${det.api}). Any browser session can be silently used to perform state-changing requests from a malicious origin.`,
|
|
28156
|
+
file,
|
|
28157
|
+
line,
|
|
28158
|
+
fix: this.fixFor(language),
|
|
28159
|
+
evidence: { ...det, language }
|
|
28160
|
+
});
|
|
28161
|
+
}
|
|
28162
|
+
}
|
|
28163
|
+
if (language === "java") {
|
|
28164
|
+
const src = ctx.code ?? "";
|
|
28165
|
+
if (src) {
|
|
28166
|
+
const lines = src.split("\n");
|
|
28167
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
28168
|
+
const line = i2 + 1;
|
|
28169
|
+
const text = lines[i2] ?? "";
|
|
28170
|
+
let det = null;
|
|
28171
|
+
if (JAVA_CSRF_LAMBDA_DISABLE_RE.test(text)) {
|
|
28172
|
+
det = { pattern: "csrf(c -> c.disable())", api: "HttpSecurity.csrf" };
|
|
28173
|
+
} else if (JAVA_CSRF_METHODREF_RE.test(text)) {
|
|
28174
|
+
det = { pattern: "csrf(::disable)", api: "HttpSecurity.csrf" };
|
|
28175
|
+
} else if (JAVA_CSRF_NULL_REPO_RE.test(text)) {
|
|
28176
|
+
det = { pattern: "csrfTokenRepository(null)", api: "HttpSecurity.csrfTokenRepository" };
|
|
28177
|
+
} else if (JAVA_CSRF_DISABLE_RE.test(text)) {
|
|
28178
|
+
det = { pattern: "csrf().disable()", api: "HttpSecurity.csrf" };
|
|
28179
|
+
}
|
|
28180
|
+
if (det && !findings.some((f) => f.line === line && f.pattern === det.pattern)) {
|
|
28181
|
+
findings.push({ line, language, ...det });
|
|
28182
|
+
ctx.addFinding({
|
|
28183
|
+
id: `${this.name}-${file}-${line}-${det.pattern}`,
|
|
28184
|
+
pass: this.name,
|
|
28185
|
+
category: this.category,
|
|
28186
|
+
rule_id: this.name,
|
|
28187
|
+
cwe: "CWE-352",
|
|
28188
|
+
severity: "critical",
|
|
28189
|
+
level: "error",
|
|
28190
|
+
message: `CSRF protection explicitly disabled via \`${det.pattern}\` (${det.api}). Any browser session can be silently used to perform state-changing requests from a malicious origin.`,
|
|
28191
|
+
file,
|
|
28192
|
+
line,
|
|
28193
|
+
fix: this.fixFor(language),
|
|
28194
|
+
evidence: { ...det, language }
|
|
28195
|
+
});
|
|
28196
|
+
}
|
|
28197
|
+
}
|
|
28198
|
+
}
|
|
28199
|
+
}
|
|
28200
|
+
if (language === "python") {
|
|
28201
|
+
const src = ctx.code ?? "";
|
|
28202
|
+
if (src) {
|
|
28203
|
+
const lines = src.split("\n");
|
|
28204
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
28205
|
+
const text = lines[i2] ?? "";
|
|
28206
|
+
if (/^\s*@csrf_exempt\b/.test(text)) {
|
|
28207
|
+
const line = i2 + 1;
|
|
28208
|
+
const det = { pattern: "@csrf_exempt", api: "django.views.decorators.csrf" };
|
|
28209
|
+
findings.push({ line, language, ...det });
|
|
28210
|
+
ctx.addFinding({
|
|
28211
|
+
id: `${this.name}-${file}-${line}-${det.pattern}`,
|
|
28212
|
+
pass: this.name,
|
|
28213
|
+
category: this.category,
|
|
28214
|
+
rule_id: this.name,
|
|
28215
|
+
cwe: "CWE-352",
|
|
28216
|
+
severity: "critical",
|
|
28217
|
+
level: "error",
|
|
28218
|
+
message: "Django view is decorated with `@csrf_exempt`, bypassing the framework CSRF middleware for this endpoint. Any browser session can be silently used to invoke this handler from a malicious origin.",
|
|
28219
|
+
file,
|
|
28220
|
+
line,
|
|
28221
|
+
fix: this.fixFor(language),
|
|
28222
|
+
evidence: { ...det, language }
|
|
28223
|
+
});
|
|
28224
|
+
}
|
|
28225
|
+
}
|
|
28226
|
+
}
|
|
28227
|
+
}
|
|
28228
|
+
return { findings };
|
|
28229
|
+
}
|
|
28230
|
+
detectCall(call, language) {
|
|
28231
|
+
const out2 = [];
|
|
28232
|
+
if (language !== "java") return out2;
|
|
28233
|
+
if (call.method_name === "disable") {
|
|
28234
|
+
const recv = call.receiver ?? "";
|
|
28235
|
+
if (/\bcsrf\s*\(\s*\)\s*$/.test(recv) || recv.endsWith(".csrf()")) {
|
|
28236
|
+
out2.push({ pattern: "csrf().disable()", api: "HttpSecurity.csrf" });
|
|
28237
|
+
}
|
|
28238
|
+
}
|
|
28239
|
+
if (call.method_name === "csrfTokenRepository") {
|
|
28240
|
+
const arg = call.arguments.find((a) => a.position === 0);
|
|
28241
|
+
const expr = (arg?.expression ?? arg?.literal ?? "").trim();
|
|
28242
|
+
if (expr === "null") {
|
|
28243
|
+
out2.push({
|
|
28244
|
+
pattern: "csrfTokenRepository(null)",
|
|
28245
|
+
api: "HttpSecurity.csrfTokenRepository"
|
|
28246
|
+
});
|
|
28247
|
+
}
|
|
28248
|
+
}
|
|
28249
|
+
return out2;
|
|
28250
|
+
}
|
|
28251
|
+
fixFor(language) {
|
|
28252
|
+
if (language === "java") {
|
|
28253
|
+
return 'Leave Spring Security CSRF protection enabled. If you need to exempt a specific endpoint (e.g. webhook), use `.csrf(c -> c.ignoringRequestMatchers("/webhook"))` rather than `.disable()`. For stateless APIs, prefer a per-request token over disabling CSRF entirely.';
|
|
28254
|
+
}
|
|
28255
|
+
if (language === "python") {
|
|
28256
|
+
return "Remove `@csrf_exempt`. For stateless API endpoints, use Django REST Framework with a token / session auth backend that does not rely on cookies. For webhook receivers, verify a shared-secret signature instead of disabling CSRF.";
|
|
28257
|
+
}
|
|
28258
|
+
return "Re-enable framework CSRF protection or replace with origin / token validation.";
|
|
28259
|
+
}
|
|
28260
|
+
};
|
|
28261
|
+
|
|
28262
|
+
// src/analysis/passes/xml-entity-expansion-pass.ts
|
|
28263
|
+
var JAVA_FACTORIES = /* @__PURE__ */ new Set([
|
|
28264
|
+
"SAXParserFactory",
|
|
28265
|
+
"DocumentBuilderFactory",
|
|
28266
|
+
"XMLInputFactory",
|
|
28267
|
+
"SchemaFactory",
|
|
28268
|
+
"TransformerFactory"
|
|
28269
|
+
]);
|
|
28270
|
+
var JAVA_SAFE_EVIDENCE_RE = /(disallow-doctype-decl|external-general-entities|external-parameter-entities|SUPPORT_DTD|ACCESS_EXTERNAL_DTD|ACCESS_EXTERNAL_SCHEMA|setXIncludeAware\s*\(\s*false\s*\)|setExpandEntityReferences\s*\(\s*false\s*\))/;
|
|
28271
|
+
var PY_LXML_PARSER_INSECURE_DEFAULT_RE = /\bresolve_entities\s*=\s*False\b/;
|
|
28272
|
+
var XmlEntityExpansionPass = class {
|
|
28273
|
+
name = "xml-entity-expansion";
|
|
28274
|
+
category = "security";
|
|
28275
|
+
run(ctx) {
|
|
28276
|
+
const { graph, language } = ctx;
|
|
28277
|
+
const file = graph.ir.meta.file;
|
|
28278
|
+
const findings = [];
|
|
28279
|
+
const code = ctx.code ?? "";
|
|
28280
|
+
if (language === "java") {
|
|
28281
|
+
const safeInFile = JAVA_SAFE_EVIDENCE_RE.test(code);
|
|
28282
|
+
if (safeInFile) return { findings };
|
|
28283
|
+
for (const call of graph.ir.calls) {
|
|
28284
|
+
const det = this.detectJavaCall(call);
|
|
28285
|
+
if (!det) continue;
|
|
28286
|
+
const line = call.location.line;
|
|
28287
|
+
findings.push({ line, language, ...det });
|
|
28288
|
+
ctx.addFinding({
|
|
28289
|
+
id: `${this.name}-${file}-${line}-${det.api}`,
|
|
28290
|
+
pass: this.name,
|
|
28291
|
+
category: this.category,
|
|
28292
|
+
rule_id: this.name,
|
|
28293
|
+
cwe: det.cwe,
|
|
28294
|
+
severity: "high",
|
|
28295
|
+
level: "error",
|
|
28296
|
+
message: `${det.api} created without disabling DTD / external-entity processing. Vulnerable to billion-laughs / quadratic blow-up DoS (CWE-776) and external-entity disclosure (CWE-611). Add \`setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)\` (or the equivalent) before parsing.`,
|
|
28297
|
+
file,
|
|
28298
|
+
line,
|
|
28299
|
+
fix: this.fixForJava(det.api),
|
|
28300
|
+
evidence: { ...det, language, safeFeatureInFile: false }
|
|
28301
|
+
});
|
|
28302
|
+
}
|
|
28303
|
+
return { findings };
|
|
28304
|
+
}
|
|
28305
|
+
if (language === "python") {
|
|
28306
|
+
const safeInFile = PY_LXML_PARSER_INSECURE_DEFAULT_RE.test(code) || /\bdefusedxml\b/.test(code);
|
|
28307
|
+
if (safeInFile) return { findings };
|
|
28308
|
+
for (const call of graph.ir.calls) {
|
|
28309
|
+
const det = this.detectPythonCall(call);
|
|
28310
|
+
if (!det) continue;
|
|
28311
|
+
const line = call.location.line;
|
|
28312
|
+
findings.push({ line, language, ...det });
|
|
28313
|
+
ctx.addFinding({
|
|
28314
|
+
id: `${this.name}-${file}-${line}-${det.api}`,
|
|
28315
|
+
pass: this.name,
|
|
28316
|
+
category: this.category,
|
|
28317
|
+
rule_id: this.name,
|
|
28318
|
+
cwe: det.cwe,
|
|
28319
|
+
severity: "high",
|
|
28320
|
+
level: "error",
|
|
28321
|
+
message: `${det.api} called without an entity-safe parser. Vulnerable to billion-laughs / quadratic blow-up DoS (CWE-776) and external-entity disclosure (CWE-611). Use \`defusedxml\` or pass an \`XMLParser(resolve_entities=False)\` to lxml.`,
|
|
28322
|
+
file,
|
|
28323
|
+
line,
|
|
28324
|
+
fix: this.fixForPython(det.api),
|
|
28325
|
+
evidence: { ...det, language, safeFeatureInFile: false }
|
|
28326
|
+
});
|
|
28327
|
+
}
|
|
28328
|
+
return { findings };
|
|
28329
|
+
}
|
|
28330
|
+
return { findings };
|
|
28331
|
+
}
|
|
28332
|
+
detectJavaCall(call) {
|
|
28333
|
+
if (call.method_name !== "newInstance") return null;
|
|
28334
|
+
const recv = call.receiver ?? "";
|
|
28335
|
+
const recvType = call.receiver_type ?? "";
|
|
28336
|
+
for (const factory of JAVA_FACTORIES) {
|
|
28337
|
+
if (recv === factory || recvType === factory || recv.endsWith("." + factory) || recvType.endsWith("." + factory)) {
|
|
28338
|
+
return {
|
|
28339
|
+
pattern: `${factory}.newInstance()`,
|
|
28340
|
+
api: factory,
|
|
28341
|
+
cwe: "CWE-776"
|
|
28342
|
+
};
|
|
28343
|
+
}
|
|
28344
|
+
}
|
|
28345
|
+
return null;
|
|
28346
|
+
}
|
|
28347
|
+
detectPythonCall(call) {
|
|
28348
|
+
const recv = call.receiver ?? "";
|
|
28349
|
+
const method = call.method_name;
|
|
28350
|
+
if ((method === "parse" || method === "fromstring" || method === "XML") && (recv === "etree" || recv.endsWith(".etree"))) {
|
|
28351
|
+
return {
|
|
28352
|
+
pattern: `etree.${method}`,
|
|
28353
|
+
api: `lxml.etree.${method}`,
|
|
28354
|
+
cwe: "CWE-776"
|
|
28355
|
+
};
|
|
28356
|
+
}
|
|
28357
|
+
if ((method === "parse" || method === "fromstring") && (recv === "ET" || recv === "ElementTree" || recv.endsWith(".ElementTree"))) {
|
|
28358
|
+
return {
|
|
28359
|
+
pattern: `ElementTree.${method}`,
|
|
28360
|
+
api: `xml.etree.ElementTree.${method}`,
|
|
28361
|
+
cwe: "CWE-776"
|
|
28362
|
+
};
|
|
28363
|
+
}
|
|
28364
|
+
return null;
|
|
28365
|
+
}
|
|
28366
|
+
fixForJava(api) {
|
|
28367
|
+
if (api === "SAXParserFactory") {
|
|
28368
|
+
return 'Call `factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)` and `factory.setXIncludeAware(false)` before `newSAXParser()`.';
|
|
28369
|
+
}
|
|
28370
|
+
if (api === "DocumentBuilderFactory") {
|
|
28371
|
+
return 'Call `factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)` and `factory.setExpandEntityReferences(false)` before `newDocumentBuilder()`.';
|
|
28372
|
+
}
|
|
28373
|
+
if (api === "XMLInputFactory") {
|
|
28374
|
+
return "Call `factory.setProperty(XMLInputFactory.SUPPORT_DTD, false)` and `factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false)` before `createXMLStreamReader`.";
|
|
28375
|
+
}
|
|
28376
|
+
return "Use `XMLConstants.FEATURE_SECURE_PROCESSING` and explicitly disable DTD / external-entity loading on the factory before parsing.";
|
|
28377
|
+
}
|
|
28378
|
+
fixForPython(api) {
|
|
28379
|
+
if (api.startsWith("lxml.etree")) {
|
|
28380
|
+
return "Pass an explicit parser: `etree.parse(src, parser=etree.XMLParser(resolve_entities=False, no_network=True))`. Even better, use the `defusedxml.lxml` wrapper.";
|
|
28381
|
+
}
|
|
28382
|
+
return "Replace `xml.etree.ElementTree` with `defusedxml.ElementTree`, which disables DTD / entity processing by default.";
|
|
28383
|
+
}
|
|
28384
|
+
};
|
|
28385
|
+
|
|
28386
|
+
// src/analysis/passes/mass-assignment-pass.ts
|
|
28387
|
+
var PY_KWARGS_SPLAT_RE = /\*\*\s*(?:request|self\.request|flask\.request|ctx|self)\s*\.\s*(?:form|args|values|json|get_json\s*\(\s*\)|files|data)/;
|
|
28388
|
+
var JS_OBJECT_SPREAD_RE = /\{\s*\.\.\.\s*(?:req|request|ctx|context)(?:\.request)?\s*\.\s*(?:body|query|params|form)\b/;
|
|
28389
|
+
var MassAssignmentPass = class {
|
|
28390
|
+
name = "mass-assignment";
|
|
28391
|
+
category = "security";
|
|
28392
|
+
run(ctx) {
|
|
28393
|
+
const { graph, language } = ctx;
|
|
28394
|
+
const file = graph.ir.meta.file;
|
|
28395
|
+
const findings = [];
|
|
28396
|
+
const code = ctx.code ?? "";
|
|
28397
|
+
if (!code) return { findings };
|
|
28398
|
+
const lines = code.split("\n");
|
|
28399
|
+
if (language === "python") {
|
|
28400
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
28401
|
+
const text = lines[i2] ?? "";
|
|
28402
|
+
const m = PY_KWARGS_SPLAT_RE.exec(text);
|
|
28403
|
+
if (!m) continue;
|
|
28404
|
+
const line = i2 + 1;
|
|
28405
|
+
const det = {
|
|
28406
|
+
pattern: "**request.<bag>",
|
|
28407
|
+
match: m[0]
|
|
28408
|
+
};
|
|
28409
|
+
findings.push({
|
|
28410
|
+
line,
|
|
28411
|
+
language,
|
|
28412
|
+
pattern: det.pattern,
|
|
28413
|
+
snippet: text.trim().slice(0, 200)
|
|
28414
|
+
});
|
|
28415
|
+
ctx.addFinding({
|
|
28416
|
+
id: `${this.name}-${file}-${line}`,
|
|
28417
|
+
pass: this.name,
|
|
28418
|
+
category: this.category,
|
|
28419
|
+
rule_id: this.name,
|
|
28420
|
+
cwe: "CWE-915",
|
|
28421
|
+
severity: "high",
|
|
28422
|
+
level: "error",
|
|
28423
|
+
message: `HTTP request bag splatted into constructor / ORM helper via \`${det.match}\`. Every form field becomes a settable attribute on the domain object, including ones the endpoint did not intend to expose (e.g. \`is_admin\`, \`role\`, \`owner_id\`).`,
|
|
28424
|
+
file,
|
|
28425
|
+
line,
|
|
28426
|
+
fix: "Replace the `**` splat with an explicit allow-list: `Model(name=request.form['name'], email=request.form['email'])`. For Django, use a `ModelForm` / serializer with `fields = [...]`.",
|
|
28427
|
+
evidence: { pattern: det.pattern, match: det.match, language }
|
|
28428
|
+
});
|
|
28429
|
+
}
|
|
28430
|
+
return { findings };
|
|
28431
|
+
}
|
|
28432
|
+
if (language === "javascript" || language === "typescript") {
|
|
28433
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
28434
|
+
const text = lines[i2] ?? "";
|
|
28435
|
+
const m = JS_OBJECT_SPREAD_RE.exec(text);
|
|
28436
|
+
if (!m) continue;
|
|
28437
|
+
const line = i2 + 1;
|
|
28438
|
+
findings.push({
|
|
28439
|
+
line,
|
|
28440
|
+
language,
|
|
28441
|
+
pattern: "{...req.<bag>}",
|
|
28442
|
+
snippet: text.trim().slice(0, 200)
|
|
28443
|
+
});
|
|
28444
|
+
ctx.addFinding({
|
|
28445
|
+
id: `${this.name}-${file}-${line}`,
|
|
28446
|
+
pass: this.name,
|
|
28447
|
+
category: this.category,
|
|
28448
|
+
rule_id: this.name,
|
|
28449
|
+
cwe: "CWE-915",
|
|
28450
|
+
severity: "high",
|
|
28451
|
+
level: "error",
|
|
28452
|
+
message: `HTTP request bag spread into object literal via \`${m[0]}\`. Every body field becomes a settable property on the resulting object, including ones the endpoint did not intend to expose (e.g. \`isAdmin\`, \`role\`, \`ownerId\`).`,
|
|
28453
|
+
file,
|
|
28454
|
+
line,
|
|
28455
|
+
fix: "Replace the spread with an explicit pick: `const { name, email } = req.body; const user = { name, email };`. For ORMs, use a DTO / Zod schema with `.pick(...)` or allow-list serializers.",
|
|
28456
|
+
evidence: { pattern: "{...req.<bag>}", match: m[0], language }
|
|
28457
|
+
});
|
|
28458
|
+
}
|
|
28459
|
+
return { findings };
|
|
28460
|
+
}
|
|
28461
|
+
return { findings };
|
|
28462
|
+
}
|
|
28463
|
+
};
|
|
28464
|
+
|
|
27931
28465
|
// src/analysis/metrics/passes/size-metrics-pass.ts
|
|
27932
28466
|
var SizeMetricsPass = class {
|
|
27933
28467
|
name = "size-metrics";
|
|
@@ -28829,6 +29363,10 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
28829
29363
|
if (!disabledPasses.has("weak-crypto")) pipeline.add(new WeakCryptoPass());
|
|
28830
29364
|
if (!disabledPasses.has("weak-random")) pipeline.add(new WeakRandomPass());
|
|
28831
29365
|
if (!disabledPasses.has("tls-verify-disabled")) pipeline.add(new TlsVerifyDisabledPass());
|
|
29366
|
+
if (!disabledPasses.has("jwt-verify-disabled")) pipeline.add(new JwtVerifyDisabledPass());
|
|
29367
|
+
if (!disabledPasses.has("csrf-protection-disabled")) pipeline.add(new CsrfProtectionDisabledPass());
|
|
29368
|
+
if (!disabledPasses.has("xml-entity-expansion")) pipeline.add(new XmlEntityExpansionPass());
|
|
29369
|
+
if (!disabledPasses.has("mass-assignment")) pipeline.add(new MassAssignmentPass());
|
|
28832
29370
|
const { results, findings } = pipeline.run(graph, code, language, config);
|
|
28833
29371
|
const sinkFilter = results.get("sink-filter");
|
|
28834
29372
|
const interProc = results.get("interprocedural");
|