blitzstrike 1.0.60 → 1.0.63

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/CHANGELOG.md CHANGED
@@ -5,15 +5,47 @@ All notable changes to Blitz Strike are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
- ## [1.0.60] — 2026-09-13
8
+ ## [1.0.63] — 2026-09-13
9
+
10
+ ### Added — complex-bug detection (`src/complex-bugs.ts`)
11
+
12
+ Three "hard" vulnerability classes beyond source→sink taint:
13
+
14
+ - **Deserialization → POP gadget chain** — `unserialize()` of attacker-controlled
15
+ data when a magic method (`__destruct`/`__wakeup`/`__toString`/`__invoke`/`__call`)
16
+ is present in the file (object injection → RCE pivot).
17
+ - **Type juggling** — loose comparison (`==`/`!=`) of user input against a
18
+ hash/secret (PHP `0e…` collision auth bypass); strict `===` is ignored.
19
+ - **Mass assignment** — `extract()` of request data without `EXTR_SKIP`, or
20
+ `parse_str()` of user input (variable injection).
21
+
22
+ Wired into the `@source` agent as `vulnerability` graph nodes. Added a
23
+ `php-complex` fixture to the enterprise benchmark.
24
+
25
+ ## [1.0.62] — 2026-09-13
26
+
27
+ ### Added — route-confusion & dispatch-abuse detection
9
28
 
10
- ### Added — deep taint-flow + CVE pattern coverage (CVE-2026-60137)
29
+ New vulnerability class distinct from source→sink taint (`src/route-confusion.ts`):
30
+ detects when the routing/dispatch layer lets an attacker reach an unintended
31
+ handler or control which handler runs.
11
32
 
12
- Investigated the WordPress core chain CVE-2026-60137 (`author__not_in` SQLi) /
13
- CVE-2026-63030 (REST API batch route confusion → RCE). The SQLi base was a
14
- **type-confusion sanitizer bypass**: `absint` guarded by `if ( is_array() )` +
15
- a `(array)` cast + `implode` that re-interpolates the raw string. The engine
16
- now detects it:
33
+ - `dynamic_dispatch` — attacker-controlled callback/route (`call_user_func`,
34
+ `forward`, `dispatch`, `resolve_route`, `invoke`).
35
+ - `dynamic_method_call` — `$obj->$method()` / `$class::$method()`.
36
+ - `dynamic_include` — `include`/`require` of a variable (LFI via route).
37
+ - `batch_forwarding` — a loop over sub-requests that forwards to inner handlers
38
+ without a per-route authorization re-check.
39
+ - `route_normalization_gap` — route matching against user input without path
40
+ normalization (strtolower/rtrim/urldecode).
41
+
42
+ Wired into the `@source` agent; findings become `route` nodes in the security
43
+ graph. Added a WordPress-style batch-endpoint fixture to the enterprise
44
+ benchmark (`wp-batch-route`).
45
+
46
+ ## [1.0.60] — 2026-09-13
47
+
48
+ ### Added — deeper taint-flow coverage
17
49
 
18
50
  - **Method-call resolution** (`calleeName`) — `$obj->method()` now resolves to
19
51
  its method definition for interprocedural taint, not the object variable.
@@ -21,17 +53,8 @@ now detects it:
21
53
  other array→string/format built-ins now carry taint through to the sink.
22
54
  - **Cast handling** — `(array)`/`(string)` casts propagate taint (a string can
23
55
  still carry a payload); `(int)`/`(float)` casts act as an int_cast sanitizer.
24
- - **`wp_parse_id_list`** added as an integer-cast sanitizer.
25
-
26
- The conditional-flow merge already models the `is_array` guard correctly, so the
27
- engine distinguishes the bypassable sanitizer (vulnerable) from the unconditional
28
- one (fixed). Added two corpus fixtures (`wp-author-not-in-vuln`/`-fixed`).
29
-
30
- ### Not yet detected
31
-
32
- - **CVE-2026-63030** (REST API batch endpoint route confusion) is a routing /
33
- authorization-logic bug, not a source→sink taint flow — out of scope for the
34
- current taint engine.
56
+ - New corpus fixtures covering conditional-sanitizer bypass on array→SQL
57
+ interpolation (`php-int-cast-bypass-vuln`/`-fixed`).
35
58
 
36
59
  ## [1.0.59] — 2026-09-13
37
60
 
@@ -0,0 +1,20 @@
1
+ - id: php-int-cast-bypass-fixed
2
+ - language: php
3
+ - vulnerable: false
4
+ - sink_type: sql_execution
5
+ ```php
6
+ <?php
7
+ class Query {
8
+ public function run($params) {
9
+ $where = "";
10
+ if ( ! empty( $params["ids"] ) ) {
11
+ $ids = implode( ",", array_map( "intval", (array) $params["ids"] ) );
12
+ $where .= " AND id NOT IN ($ids) ";
13
+ }
14
+ return $db->query("SELECT * FROM t WHERE 1=1 $where");
15
+ }
16
+ }
17
+ $q = $_GET;
18
+ $w = new Query();
19
+ $w->run($q);
20
+ ```
@@ -0,0 +1,23 @@
1
+ - id: php-int-cast-bypass-vuln
2
+ - language: php
3
+ - vulnerable: true
4
+ - sink_type: sql_execution
5
+ ```php
6
+ <?php
7
+ class Query {
8
+ public function run($params) {
9
+ $where = "";
10
+ if ( ! empty( $params["ids"] ) ) {
11
+ if ( is_array( $params["ids"] ) ) {
12
+ $params["ids"] = array_map( "intval", $params["ids"] );
13
+ }
14
+ $ids = implode( ",", (array) $params["ids"] );
15
+ $where .= " AND id NOT IN ($ids) ";
16
+ }
17
+ return $db->query("SELECT * FROM t WHERE 1=1 $where");
18
+ }
19
+ }
20
+ $q = $_GET;
21
+ $w = new Query();
22
+ $w->run($q);
23
+ ```
@@ -0,0 +1,34 @@
1
+ <?php
2
+ /**
3
+ * Plugin helper (simplified) with multiple complex-bug patterns.
4
+ */
5
+
6
+ // 1. Deserialization → object injection (magic method present)
7
+ function restore_session($payload) {
8
+ $data = $_POST['data'];
9
+ $obj = unserialize($data);
10
+ return $obj;
11
+ }
12
+
13
+ class CacheManager {
14
+ public function __destruct() {
15
+ // Magic method — a POP gadget can pivot object injection to RCE here.
16
+ system("rm -rf /tmp/cache");
17
+ }
18
+ }
19
+
20
+ // 2. Type juggling — loose comparison of user input against a hash
21
+ function verify_signature() {
22
+ $sig = $_GET['sig'];
23
+ $secret = get_option('signing_secret');
24
+ if ($sig == $secret) { // <-- loose == , 0e... collision bypass
25
+ return true;
26
+ }
27
+ return false;
28
+ }
29
+
30
+ // 3. Mass assignment — extract() of request data without EXTR_SKIP
31
+ function bootstrap_options() {
32
+ extract($_REQUEST); // <-- variable injection
33
+ return $admin; // $admin is attacker-controlled
34
+ }
@@ -0,0 +1,43 @@
1
+ <?php
2
+ /**
3
+ * REST API batch endpoint (simplified) — forwards to inner handlers.
4
+ *
5
+ * The `/batch` route is registered WITHOUT a permission_callback, so it is
6
+ * reachable unauthenticated. A malformed nested request desynchronizes the
7
+ * batch router, letting an attacker reach handlers they should not.
8
+ */
9
+
10
+ add_action('rest_api_init', function () {
11
+ register_rest_route('my/v1', '/batch', array(
12
+ 'methods' => 'POST',
13
+ 'callback' => 'handle_batch',
14
+ 'permission_callback' => '__return_true', // <-- no real authz check
15
+ ));
16
+ });
17
+
18
+ function handle_batch($request) {
19
+ $operations = $request['requests'];
20
+ $responses = array();
21
+
22
+ foreach ($operations as $op) {
23
+ $route = $op['route'];
24
+ $handler = resolve_route($route);
25
+ $responses[] = $handler($op['params']);
26
+ }
27
+
28
+ return $responses;
29
+ }
30
+
31
+ function resolve_route($route) {
32
+ $registry = array(
33
+ 'users' => 'get_users',
34
+ 'posts' => 'get_posts',
35
+ 'options' => 'get_options',
36
+ );
37
+ return $registry[$route];
38
+ }
39
+
40
+ function get_users($params) {
41
+ global $wpdb;
42
+ return $wpdb->get_results("SELECT * FROM users WHERE id = " . $params['id']);
43
+ }
package/dist/index.js CHANGED
@@ -9190,7 +9190,6 @@ var init_dataflow = __esm(() => {
9190
9190
  escapeshellcmd: { id: "shell_escape", label: "Shell command escape", neutralizes: ["command_execution"] },
9191
9191
  intval: { id: "int_cast", label: "Integer cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] },
9192
9192
  absint: { id: "int_cast", label: "Absolute integer cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] },
9193
- wp_parse_id_list: { id: "int_cast", label: "WordPress ID list cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] },
9194
9193
  "preg_replace.*FILTER": { id: "regex_filter", label: "Regex filter", neutralizes: ["html_render"] }
9195
9194
  };
9196
9195
  });
@@ -37050,6 +37049,269 @@ function reviewValidated(mission) {
37050
37049
  }
37051
37050
  var init_reviewer = () => {};
37052
37051
 
37052
+ // src/route-confusion.ts
37053
+ function lines(code) {
37054
+ return code.split(`
37055
+ `);
37056
+ }
37057
+ function lineNo(code, pos) {
37058
+ let n = 1;
37059
+ for (let i = 0;i < pos; i++)
37060
+ if (code.charCodeAt(i) === 10)
37061
+ n++;
37062
+ return n;
37063
+ }
37064
+ function userControlled(scope) {
37065
+ return SOURCE_TOKENS.test(scope);
37066
+ }
37067
+ function detectDynamicDispatch(code, file, out) {
37068
+ const ls = lines(code);
37069
+ DYNAMIC_DISPATCH_RE.lastIndex = 0;
37070
+ let m;
37071
+ while ((m = DYNAMIC_DISPATCH_RE.exec(code)) !== null) {
37072
+ const ln = lineNo(code, m.index);
37073
+ const l = ls[ln - 1] ?? "";
37074
+ const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
37075
+ `);
37076
+ if (!userControlled(l) && !userControlled(scope))
37077
+ continue;
37078
+ out.push({
37079
+ file,
37080
+ line: ln,
37081
+ type: "dynamic_dispatch",
37082
+ category: "Route confusion (user-controlled dispatch)",
37083
+ severity: "high",
37084
+ evidence: l.trim(),
37085
+ detail: `\`${m[1]}()\` invoked with an attacker-controlled callback/route — the request selects the handler.`
37086
+ });
37087
+ }
37088
+ }
37089
+ function detectDynamicMethod(code, file, out) {
37090
+ const ls = lines(code);
37091
+ DYNAMIC_METHOD_RE.lastIndex = 0;
37092
+ let m;
37093
+ while ((m = DYNAMIC_METHOD_RE.exec(code)) !== null) {
37094
+ const ln = lineNo(code, m.index);
37095
+ const l = ls[ln - 1] ?? "";
37096
+ const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
37097
+ `);
37098
+ if (!userControlled(l) && !userControlled(scope))
37099
+ continue;
37100
+ out.push({
37101
+ file,
37102
+ line: ln,
37103
+ type: "dynamic_method_call",
37104
+ category: "Route confusion (dynamic method dispatch)",
37105
+ severity: "high",
37106
+ evidence: l.trim(),
37107
+ detail: "Dynamic method/static call `" + m[1] + "$var()` — the invoked symbol is attacker-influenced."
37108
+ });
37109
+ }
37110
+ }
37111
+ function detectDynamicInclude(code, file, out) {
37112
+ const ls = lines(code);
37113
+ const re = /\b(include|require)(_once)?\s*\(\s*(\$[A-Za-z_]\w*)/g;
37114
+ let m;
37115
+ while ((m = re.exec(code)) !== null) {
37116
+ const ln = lineNo(code, m.index);
37117
+ const l = ls[ln - 1] ?? "";
37118
+ const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
37119
+ `);
37120
+ if (!userControlled(l) && !userControlled(scope))
37121
+ continue;
37122
+ out.push({
37123
+ file,
37124
+ line: ln,
37125
+ type: "dynamic_include",
37126
+ category: "Route confusion (dynamic file include)",
37127
+ severity: "high",
37128
+ evidence: l.trim(),
37129
+ detail: `\`${m[1]}(…)\` of a variable — potential LFI/RCE when the path is attacker-controlled.`
37130
+ });
37131
+ }
37132
+ }
37133
+ function detectBatchForwarding(code, file, out) {
37134
+ BATCH_LOOP_RE.lastIndex = 0;
37135
+ let m;
37136
+ while ((m = BATCH_LOOP_RE.exec(code)) !== null) {
37137
+ const ln = lineNo(code, m.index);
37138
+ const body = lines(code).slice(ln, ln + 8).join(`
37139
+ `);
37140
+ const forwards = /\b(call_user_func|forward|dispatch|resolve_route|invoke|handle|route)\s*\(/i.test(body);
37141
+ if (!forwards)
37142
+ continue;
37143
+ const rechecks = /\b(current_user_can|permission_callback|check_ajax_referer|authorize|authorise|is_user_logged_in)\b/i.test(body);
37144
+ out.push({
37145
+ file,
37146
+ line: ln,
37147
+ type: "batch_forwarding",
37148
+ category: "Route confusion (batch/proxy forwarding)",
37149
+ severity: rechecks ? "medium" : "high",
37150
+ evidence: lines(code)[ln - 1]?.trim() ?? "",
37151
+ detail: rechecks ? "Batch/proxy loop forwards to inner handlers — auth is re-checked in scope, but verify it applies per-route." : "Batch/proxy loop forwards to inner handlers with no per-route authorization re-check — an attacker may reach a protected handler."
37152
+ });
37153
+ }
37154
+ }
37155
+ function detectRouteNormalization(code, file, out) {
37156
+ const ls = lines(code);
37157
+ for (let i = 0;i < ls.length; i++) {
37158
+ const l = ls[i];
37159
+ if (!ROUTE_MATCH_RE.test(l))
37160
+ continue;
37161
+ const scope = ls.slice(Math.max(0, i - 3), i + 1).join(`
37162
+ `);
37163
+ if (!userControlled(scope))
37164
+ continue;
37165
+ if (NORMALIZERS.test(l) || NORMALIZERS.test(scope))
37166
+ continue;
37167
+ out.push({
37168
+ file,
37169
+ line: i + 1,
37170
+ type: "route_normalization_gap",
37171
+ category: "Route confusion (missing path normalization)",
37172
+ severity: "medium",
37173
+ evidence: l.trim(),
37174
+ detail: "Route matched against user input without visible normalization (strtolower/rtrim/urldecode) — route desync possible."
37175
+ });
37176
+ }
37177
+ }
37178
+ function detectRouteConfusion(code, file) {
37179
+ const out = [];
37180
+ detectDynamicDispatch(code, file, out);
37181
+ detectDynamicMethod(code, file, out);
37182
+ detectDynamicInclude(code, file, out);
37183
+ detectBatchForwarding(code, file, out);
37184
+ detectRouteNormalization(code, file, out);
37185
+ return out;
37186
+ }
37187
+ var SOURCE_TOKENS, DYNAMIC_DISPATCH_RE, DYNAMIC_METHOD_RE, BATCH_LOOP_RE, ROUTE_MATCH_RE, NORMALIZERS;
37188
+ var init_route_confusion = __esm(() => {
37189
+ SOURCE_TOKENS = /\$_(GET|POST|REQUEST|COOKIE|FILES|SERVER)\b|\$request->|\$req->|request\.(get|input|query|post)|getParameter\(|req\.(query|body|params)/;
37190
+ DYNAMIC_DISPATCH_RE = /\b(call_user_func|call_user_func_array|forward|dispatch|resolve_route|invoke)\s*\(\s*(\$|\[)/gi;
37191
+ DYNAMIC_METHOD_RE = /(->|::)\s*\$[A-Za-z_]/g;
37192
+ BATCH_LOOP_RE = /\bforeach\s*\([^)]*(requests|batch|sub_requests|operations)\b/gi;
37193
+ ROUTE_MATCH_RE = /\b(preg_match|preg_match_all|strpos|stripos|str_contains)\s*\(/i;
37194
+ NORMALIZERS = /\b(strtolower|strtoupper|rtrim|urldecode|rawurldecode|parse_url|normalize|canonical)\b/i;
37195
+ });
37196
+
37197
+ // src/complex-bugs.ts
37198
+ function lines2(code) {
37199
+ return code.split(`
37200
+ `);
37201
+ }
37202
+ function lineNo2(code, pos) {
37203
+ let n = 1;
37204
+ for (let i = 0;i < pos; i++)
37205
+ if (code.charCodeAt(i) === 10)
37206
+ n++;
37207
+ return n;
37208
+ }
37209
+ function detectDeserialization(code, file, out) {
37210
+ if (!MAGIC_METHODS.test(code))
37211
+ return;
37212
+ const ls = lines2(code);
37213
+ const re = /\b(unserialize|maybe_unserialize)\s*\(/g;
37214
+ let m;
37215
+ while ((m = re.exec(code)) !== null) {
37216
+ const ln = lineNo2(code, m.index);
37217
+ const l = ls[ln - 1] ?? "";
37218
+ const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
37219
+ `);
37220
+ if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
37221
+ continue;
37222
+ out.push({
37223
+ file,
37224
+ line: ln,
37225
+ type: "deserialization",
37226
+ category: "Deserialization → POP gadget chain",
37227
+ severity: "high",
37228
+ evidence: l.trim(),
37229
+ detail: `\`${m[1]}()\` of attacker-controlled data AND a magic method is present — object injection can pivot to RCE via the gadget chain.`
37230
+ });
37231
+ }
37232
+ }
37233
+ function detectTypeJuggling(code, file, out) {
37234
+ const ls = lines2(code);
37235
+ const re = /(?<![=!<>])(==|!=)(?!=)/g;
37236
+ let m;
37237
+ while ((m = re.exec(code)) !== null) {
37238
+ const ln = lineNo2(code, m.index);
37239
+ const l = ls[ln - 1] ?? "";
37240
+ const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
37241
+ `);
37242
+ const hasSource = SOURCE_TOKENS2.test(l) || SOURCE_TOKENS2.test(scope);
37243
+ const hasHash = /\b(md5|sha1|sha256|hash|crc32|crypt|password_verify|strcmp|hash_equals|secret|token|nonce)\s*\(?/i.test(l) || /\b(secret|token|nonce|hash)\b/i.test(l);
37244
+ if (!hasSource || !hasHash)
37245
+ continue;
37246
+ out.push({
37247
+ file,
37248
+ line: ln,
37249
+ type: "type_juggling",
37250
+ category: "Type juggling (loose comparison)",
37251
+ severity: "medium",
37252
+ evidence: l.trim(),
37253
+ detail: 'Loose comparison of user input against a hash/secret — PHP type juggling (`0e…`, `"str" == 0`) may bypass the check. Use strict `===`.'
37254
+ });
37255
+ }
37256
+ }
37257
+ function detectMassAssignment(code, file, out) {
37258
+ const ls = lines2(code);
37259
+ const extractRe = /\bextract\s*\(\s*[^)]*(REQUEST|POST|GET|COOKIE|FILES|\$_(REQUEST|POST|GET|COOKIE|FILES))/g;
37260
+ let m;
37261
+ while ((m = extractRe.exec(code)) !== null) {
37262
+ const ln = lineNo2(code, m.index);
37263
+ const l = ls[ln - 1] ?? "";
37264
+ if (/EXTR_SKIP|EXTR_PREFIX_ALL/.test(l))
37265
+ continue;
37266
+ out.push({
37267
+ file,
37268
+ line: ln,
37269
+ type: "mass_assignment",
37270
+ category: "Mass assignment (variable injection)",
37271
+ severity: "high",
37272
+ evidence: l.trim(),
37273
+ detail: "`extract()` of request data without EXTR_SKIP — attacker can overwrite arbitrary variables."
37274
+ });
37275
+ }
37276
+ const parseRe = /\bparse_str\s*\(/g;
37277
+ while ((m = parseRe.exec(code)) !== null) {
37278
+ const ln = lineNo2(code, m.index);
37279
+ const l = ls[ln - 1] ?? "";
37280
+ const scope = ls.slice(Math.max(0, ln - 3), ln).join(`
37281
+ `);
37282
+ if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
37283
+ continue;
37284
+ out.push({
37285
+ file,
37286
+ line: ln,
37287
+ type: "mass_assignment",
37288
+ category: "Mass assignment (variable injection)",
37289
+ severity: "medium",
37290
+ evidence: l.trim(),
37291
+ detail: "`parse_str()` of user input into the symbol table — variable injection."
37292
+ });
37293
+ }
37294
+ }
37295
+ function detectComplexBugs(code, file) {
37296
+ const out = [];
37297
+ detectDeserialization(code, file, out);
37298
+ detectTypeJuggling(code, file, out);
37299
+ detectMassAssignment(code, file, out);
37300
+ const seen = new Set;
37301
+ return out.filter((f) => {
37302
+ const key = `${f.type}:${f.line}`;
37303
+ if (seen.has(key))
37304
+ return false;
37305
+ seen.add(key);
37306
+ return true;
37307
+ });
37308
+ }
37309
+ var SOURCE_TOKENS2, MAGIC_METHODS;
37310
+ var init_complex_bugs = __esm(() => {
37311
+ SOURCE_TOKENS2 = /\$_(GET|POST|REQUEST|COOKIE|FILES|SERVER)\b|\$request->|\$req->|request\.(get|input|query|post|data)|getParameter\(|req\.(query|body|params)/;
37312
+ MAGIC_METHODS = /\b(__destruct|__wakeup|__toString|__invoke|__call|__get|__set|__isset|__unset|__sleep)\s*\(/;
37313
+ });
37314
+
37053
37315
  // src/agents.ts
37054
37316
  import { readFileSync as readFileSync18 } from "node:fs";
37055
37317
  function emptyGraph() {
@@ -37073,6 +37335,8 @@ var init_agents = __esm(() => {
37073
37335
  init_scanner();
37074
37336
  init_eagle2();
37075
37337
  init_universal_taint();
37338
+ init_route_confusion();
37339
+ init_complex_bugs();
37076
37340
  init_adapters();
37077
37341
  init_active();
37078
37342
  init_strike();
@@ -37157,6 +37421,44 @@ var init_agents = __esm(() => {
37157
37421
  graph = addEdge2(graph, tid, sinkId, "flows_to");
37158
37422
  }
37159
37423
  }
37424
+ for (const f of files.filter((x) => /\.php$/i.test(x))) {
37425
+ let code;
37426
+ try {
37427
+ code = readFileSync18(f, "utf8");
37428
+ } catch {
37429
+ continue;
37430
+ }
37431
+ for (const rc of detectRouteConfusion(code, f).slice(0, 50)) {
37432
+ const hyp = makeHypothesis({
37433
+ type: rc.category,
37434
+ target: `${f}:${rc.line}`,
37435
+ evidence: rc.detail,
37436
+ confidence: rc.severity === "high" ? 0.7 : 0.5,
37437
+ required_validation: "confirm reachability of the dispatch/route with attacker-controlled input",
37438
+ potential_impact: "route confusion / authorization bypass"
37439
+ });
37440
+ hypotheses.push(hyp);
37441
+ findings += 1;
37442
+ const nodeId = `route:${f}:${rc.line}:${rc.type}`;
37443
+ graph = upsertNode(graph, nodeId, "route", { file: f, line: rc.line, type: rc.type, hypothesis: hyp.id });
37444
+ graph = addEdge2(graph, tid, nodeId, "routes_to");
37445
+ }
37446
+ for (const cb of detectComplexBugs(code, f).slice(0, 50)) {
37447
+ const hyp = makeHypothesis({
37448
+ type: cb.category,
37449
+ target: `${f}:${cb.line}`,
37450
+ evidence: cb.detail,
37451
+ confidence: cb.severity === "high" ? 0.7 : 0.55,
37452
+ required_validation: "confirm the payload reaches the sink and is exploitable",
37453
+ potential_impact: cb.type
37454
+ });
37455
+ hypotheses.push(hyp);
37456
+ findings += 1;
37457
+ const nodeId = `complex:${f}:${cb.line}:${cb.type}`;
37458
+ graph = upsertNode(graph, nodeId, "vulnerability", { file: f, line: cb.line, type: cb.type, hypothesis: hyp.id });
37459
+ graph = addEdge2(graph, tid, nodeId, "vulnerable_to");
37460
+ }
37461
+ }
37160
37462
  return { ok: true, output: { files: files.length, findings }, hypotheses, graph };
37161
37463
  }
37162
37464
  };
@@ -38092,6 +38394,20 @@ var init_enterprise_benchmark = __esm(() => {
38092
38394
  expected_vulns: 2,
38093
38395
  expected_sinks: ["sql_execution", "command_execution"],
38094
38396
  safe_tokens: ["prepareStatement", "PreparedStatement"]
38397
+ },
38398
+ {
38399
+ id: "wp-batch-route",
38400
+ dir: join17(ROOT10, "wp-batch-route"),
38401
+ expected_vulns: 1,
38402
+ expected_sinks: ["Route confusion"],
38403
+ safe_tokens: []
38404
+ },
38405
+ {
38406
+ id: "php-complex",
38407
+ dir: join17(ROOT10, "php-complex"),
38408
+ expected_vulns: 4,
38409
+ expected_sinks: ["Deserialization", "Type juggling", "Mass assignment"],
38410
+ safe_tokens: []
38095
38411
  }
38096
38412
  ];
38097
38413
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.60",
3
+ "version": "1.0.63",
4
4
  "description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,21 +0,0 @@
1
- - id: wp-author-not-in-fixed
2
- - language: php
3
- - vulnerable: false
4
- - sink_type: sql_execution
5
- ```php
6
- <?php
7
- class WP_Query {
8
- public function get_posts($qv) {
9
- $where = "";
10
- if ( ! empty( $qv["author__not_in"] ) ) {
11
- $ids = wp_parse_id_list( $qv["author__not_in"] );
12
- $author__not_in = implode( ",", $ids );
13
- $where .= " AND post_author NOT IN ($author__not_in) ";
14
- }
15
- return $wpdb->query("SELECT * FROM posts WHERE 1=1 $where");
16
- }
17
- }
18
- $q = $_GET;
19
- $w = new WP_Query();
20
- $w->get_posts($q);
21
- ```
@@ -1,23 +0,0 @@
1
- - id: wp-author-not-in-vuln
2
- - language: php
3
- - vulnerable: true
4
- - sink_type: sql_execution
5
- ```php
6
- <?php
7
- class WP_Query {
8
- public function get_posts($qv) {
9
- $where = "";
10
- if ( ! empty( $qv["author__not_in"] ) ) {
11
- if ( is_array( $qv["author__not_in"] ) ) {
12
- $qv["author__not_in"] = array_map( "absint", $qv["author__not_in"] );
13
- }
14
- $author__not_in = implode( ",", (array) $qv["author__not_in"] );
15
- $where .= " AND post_author NOT IN ($author__not_in) ";
16
- }
17
- return $wpdb->query("SELECT * FROM posts WHERE 1=1 $where");
18
- }
19
- }
20
- $q = $_GET;
21
- $w = new WP_Query();
22
- $w->get_posts($q);
23
- ```