blitzstrike 1.0.58 → 1.0.60

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,6 +5,62 @@ 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
9
+
10
+ ### Added — deep taint-flow + CVE pattern coverage (CVE-2026-60137)
11
+
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:
17
+
18
+ - **Method-call resolution** (`calleeName`) — `$obj->method()` now resolves to
19
+ its method definition for interprocedural taint, not the object variable.
20
+ - **Built-in taint propagation** — `implode`/`join`/`sprintf`/`str_replace` and
21
+ other array→string/format built-ins now carry taint through to the sink.
22
+ - **Cast handling** — `(array)`/`(string)` casts propagate taint (a string can
23
+ 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.
35
+
36
+ ## [1.0.59] — 2026-09-13
37
+
38
+ ### Added — enterprise-grade detection + benchmark
39
+
40
+ - **Enterprise benchmark** (`src/mission/enterprise-benchmark.ts`, `bench/enterprise/`):
41
+ four realistic multi-file, framework-style fixtures (Laravel PHP, Express JS,
42
+ Django Python, Spring Java) with ORM/query-builder abstractions and
43
+ known-safe endpoints. `blitzstrike benchmark --enterprise` runs them; the
44
+ engine now scores **10/10 vulns, 0 false positives**.
45
+
46
+ ### Fixed — framework source/sink coverage (found by the enterprise benchmark)
47
+
48
+ - **PHP** — `sourceOf` now recognizes framework accessors (`$request->input()`,
49
+ `->query()`, `->get()`, `->post()`, …) not just `$_GET`; `sinkClassOf` handles
50
+ static calls (`DB::select()`, `DB::raw()`); and **function/method-internal
51
+ sources** are now emitted (previously only top-level + tainted-argument flows
52
+ were detected, so Laravel controllers scored 0).
53
+ - **JS** — removed `require()`/`child_process` module-import false positives;
54
+ sink matching now keys on the callee (no nested-call double-count); and
55
+ PostgreSQL `$1` parameterized queries are recognized as safe.
56
+ - **Python** — added Django/DRF sources (`request.GET`, `request.POST`,
57
+ `request.data`, `request.query_params`).
58
+ - **Java** — added Spring/JAX-RS annotation sources (`@RequestParam`,
59
+ `@PathVariable`, `@RequestBody`, …) and tightened sink variable extraction so
60
+ the tainted variable is no longer pushed out of the window by SQL keywords.
61
+ - `@analysis` agent now imports the language adapters (multi-language detection
62
+ previously returned 0 in the CLI/MCP path).
63
+
8
64
  ## [1.0.58] — 2026-09-13
9
65
 
10
66
  ### Fixed — total-verification pass
@@ -0,0 +1,21 @@
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
+ ```
@@ -0,0 +1,23 @@
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
+ ```
@@ -0,0 +1,35 @@
1
+ package com.example.controller;
2
+
3
+ import org.springframework.web.bind.annotation.*;
4
+ import java.sql.*;
5
+
6
+ @RestController
7
+ @RequestMapping("/api/users")
8
+ public class UserController {
9
+
10
+ // VULNERABLE: SQL string concatenation of user input
11
+ @GetMapping("/search")
12
+ public String search(@RequestParam String q) throws Exception {
13
+ Connection conn = DriverManager.getConnection("jdbc:postgresql://db/app");
14
+ Statement st = conn.createStatement();
15
+ ResultSet rs = st.executeQuery("SELECT * FROM users WHERE name LIKE '%" + q + "%'");
16
+ return rs.getString(1);
17
+ }
18
+
19
+ // SAFE: PreparedStatement (parameterized)
20
+ @GetMapping("/show")
21
+ public String show(@RequestParam String id) throws Exception {
22
+ Connection conn = DriverManager.getConnection("jdbc:postgresql://db/app");
23
+ PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
24
+ ps.setString(1, id);
25
+ ResultSet rs = ps.executeQuery();
26
+ return rs.getString(1);
27
+ }
28
+
29
+ // VULNERABLE: command execution from user input
30
+ @GetMapping("/export")
31
+ public String export(@RequestParam String file) throws Exception {
32
+ Runtime.getRuntime().exec("tar -czf /tmp/export.tar.gz " + file);
33
+ return "ok";
34
+ }
35
+ }
@@ -0,0 +1,10 @@
1
+ const { Pool } = require('pg');
2
+
3
+ // Realistic enterprise DB connection module with a safe helper.
4
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
5
+
6
+ async function safeQuery(text, params) {
7
+ return pool.query(text, params);
8
+ }
9
+
10
+ module.exports = { pool, safeQuery };
@@ -0,0 +1,32 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { pool, safeQuery } = require('../db/connection');
4
+
5
+ // VULNERABLE: raw SQL string concatenation of user input
6
+ router.get('/search', async (req, res) => {
7
+ const q = req.query.q;
8
+ const rows = await pool.query(`SELECT * FROM users WHERE name LIKE '%${q}%'`);
9
+ res.json(rows);
10
+ });
11
+
12
+ // VULNERABLE: command execution from user input
13
+ router.get('/export', (req, res) => {
14
+ const file = req.query.file;
15
+ require('child_process').execSync(`tar -czf /tmp/export.tar.gz ${file}`);
16
+ res.json({ ok: true });
17
+ });
18
+
19
+ // SAFE: parameterized query
20
+ router.get('/show', async (req, res) => {
21
+ const id = req.query.id;
22
+ const rows = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
23
+ res.json(rows);
24
+ });
25
+
26
+ // SAFE: escaped output
27
+ router.get('/preview', (req, res) => {
28
+ const html = req.query.html;
29
+ res.send(`<div>${String(html).replace(/[<>&"]/g, '')}</div>`);
30
+ });
31
+
32
+ module.exports = router;
@@ -0,0 +1,30 @@
1
+ <?php
2
+ namespace App\Http\Controllers;
3
+
4
+ use Illuminate\Http\Request;
5
+
6
+ class ReportController extends Controller
7
+ {
8
+ // VULNERABLE: command execution from user input
9
+ public function export(Request $request)
10
+ {
11
+ $file = $request->input('file');
12
+ system("tar -czf /tmp/export.tar.gz {$file}");
13
+ return response()->json(['ok' => true]);
14
+ }
15
+
16
+ // VULNERABLE: code execution from user input
17
+ public function render(Request $request)
18
+ {
19
+ $tpl = $request->input('tpl');
20
+ eval("return {$tpl};");
21
+ return response()->json(['ok' => true]);
22
+ }
23
+
24
+ // SAFE: escaped output
25
+ public function preview(Request $request)
26
+ {
27
+ $html = $request->input('html');
28
+ return view('preview', ['content' => htmlspecialchars($html)]);
29
+ }
30
+ }
@@ -0,0 +1,33 @@
1
+ <?php
2
+ namespace App\Http\Controllers;
3
+
4
+ use Illuminate\Http\Request;
5
+ use Illuminate\Support\Facades\DB;
6
+ use App\Models\User;
7
+
8
+ class UserController extends Controller
9
+ {
10
+ // VULNERABLE: raw SQL interpolation of user input
11
+ public function search(Request $request)
12
+ {
13
+ $q = $request->input('q');
14
+ $users = DB::select("SELECT * FROM users WHERE name LIKE '%{$q}%'");
15
+ return response()->json($users);
16
+ }
17
+
18
+ // VULNERABLE: raw where with user input (query builder bypass)
19
+ public function filter(Request $request)
20
+ {
21
+ $col = $request->input('col');
22
+ $rows = DB::table('users')->whereRaw("{$col} = 1")->get();
23
+ return response()->json($rows);
24
+ }
25
+
26
+ // SAFE: Eloquent ORM (parameterized)
27
+ public function show(Request $request)
28
+ {
29
+ $id = $request->input('id');
30
+ $user = User::where('id', $id)->first();
31
+ return response()->json($user);
32
+ }
33
+ }
@@ -0,0 +1,10 @@
1
+ <?php
2
+ namespace App\Models;
3
+
4
+ use Illuminate\Database\Eloquent\Model;
5
+
6
+ class User extends Model
7
+ {
8
+ protected $table = 'users';
9
+ protected $fillable = ['name', 'email'];
10
+ }
@@ -0,0 +1,35 @@
1
+ from django.http import JsonResponse
2
+ from django.db import connection
3
+ from django.shortcuts import render
4
+
5
+
6
+ def search(request):
7
+ # VULNERABLE: raw SQL interpolation of user input
8
+ q = request.GET.get('q')
9
+ cursor = connection.cursor()
10
+ cursor.execute("SELECT * FROM users WHERE name LIKE '%%%s%%'" % q)
11
+ rows = cursor.fetchall()
12
+ return JsonResponse({'rows': rows})
13
+
14
+
15
+ def export(request):
16
+ # VULNERABLE: command execution from user input
17
+ import os
18
+ file = request.GET.get('file')
19
+ os.system("tar -czf /tmp/export.tar.gz " + file)
20
+ return JsonResponse({'ok': True})
21
+
22
+
23
+ def show(request):
24
+ # SAFE: Django ORM (parameterized)
25
+ from .models import User
26
+ uid = request.GET.get('id')
27
+ user = User.objects.filter(id=uid).first()
28
+ return JsonResponse({'user': str(user)})
29
+
30
+
31
+ def preview(request):
32
+ # SAFE: escaped output
33
+ from django.utils.html import escape
34
+ html = request.GET.get('html')
35
+ return JsonResponse({'content': escape(html)})
package/dist/index.js CHANGED
@@ -9160,7 +9160,7 @@ var init_dataflow = __esm(() => {
9160
9160
  wp_remote_get: { id: "external_api", label: "External API data", attacker_controlled: false }
9161
9161
  };
9162
9162
  SINK_CLASS_MAP = [
9163
- [/->query\(|\$wpdb->query|\$wpdb->get_var|\$wpdb->get_results|->whereRaw|->selectRaw/, { id: "sql_execution", category: "SQL execution", cwe: "CWE-89" }],
9163
+ [/->query\(|\$wpdb->query|\$wpdb->get_var|\$wpdb->get_results|->whereRaw|->selectRaw|DB::select|DB::raw|DB::statement|DB::insert|DB::update|DB::unprepared/, { id: "sql_execution", category: "SQL execution", cwe: "CWE-89" }],
9164
9164
  [/\beval\(|\bassert\(|\bcreate_function\(|\bcall_user_func\(|\bpreg_replace\(/, { id: "code_execution", category: "Dynamic evaluation / code execution", cwe: "CWE-94" }],
9165
9165
  [/\bsystem\(|\bexec\(|\bshell_exec\(|\bpassthru\(|\bproc_open\(|\bpopen\(/, { id: "command_execution", category: "Command execution", cwe: "CWE-78" }],
9166
9166
  [/\bmove_uploaded_file\(|\bfile_put_contents\(|\bfwrite\(|\bfopen\(|\bunlink\(/, { id: "file_operations", category: "File operations", cwe: "CWE-434" }],
@@ -9190,6 +9190,7 @@ 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"] },
9193
9194
  "preg_replace.*FILTER": { id: "regex_filter", label: "Regex filter", neutralizes: ["html_render"] }
9194
9195
  };
9195
9196
  });
@@ -17104,6 +17105,15 @@ function rootVarName2(node) {
17104
17105
  return rootVarName2(node.what);
17105
17106
  return null;
17106
17107
  }
17108
+ function calleeName(what) {
17109
+ if (!isNode3(what))
17110
+ return null;
17111
+ if (what.kind === "name")
17112
+ return typeof what.name === "string" ? what.name : what.name?.name ?? null;
17113
+ if (what.kind === "propertylookup" || what.kind === "staticlookup")
17114
+ return what.offset?.name ?? what.offset?.value ?? null;
17115
+ return null;
17116
+ }
17107
17117
  function sourceOf2(node) {
17108
17118
  if (!isNode3(node))
17109
17119
  return null;
@@ -17123,6 +17133,14 @@ function sourceOf2(node) {
17123
17133
  return "getallheaders";
17124
17134
  if (n === "file_get_contents" && JSON.stringify(node).includes("php://input"))
17125
17135
  return "php://input";
17136
+ if (node.what?.kind === "propertylookup") {
17137
+ const method = node.what.offset?.name ?? node.what.offset?.value;
17138
+ const base = rootVarName2(node.what.what);
17139
+ const reqMethods = ["input", "query", "get", "post", "all", "json", "cookie", "file", "header", "only", "except", "server"];
17140
+ if (base && /^(request|req)$/i.test(base) && reqMethods.includes(String(method))) {
17141
+ return `$${base}->${method}()`;
17142
+ }
17143
+ }
17126
17144
  }
17127
17145
  return null;
17128
17146
  }
@@ -17139,6 +17157,11 @@ function sinkClassOf2(call) {
17139
17157
  token = `${baseStr}->${method}(`;
17140
17158
  } else if (what.kind === "name") {
17141
17159
  token = `${what.name}(`;
17160
+ } else if (what.kind === "staticlookup") {
17161
+ const clsName = nameOf2(what.what);
17162
+ const method = what.offset?.name ?? what.offset?.value;
17163
+ if (clsName && method)
17164
+ token = `${clsName}::${method}(`;
17142
17165
  }
17143
17166
  if (!token)
17144
17167
  return null;
@@ -17170,8 +17193,16 @@ function summarizeFunction(fn, id, name, summaries) {
17170
17193
  if (a.byref === true)
17171
17194
  byrefParams.add(params[i]);
17172
17195
  });
17173
- const summary = { id, name, params, byrefParams, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map };
17196
+ const summary = { id, name, params, byrefParams, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map, internalSinks: [] };
17174
17197
  const bodyStmts = fn.body?.kind === "block" ? fn.body.children ?? [] : [fn.body];
17198
+ {
17199
+ const cleanEnv = new Map;
17200
+ for (const p of params)
17201
+ cleanEnv.set(p, CLEAN);
17202
+ const cleanCtx = { sinks: [], returnsTainted: false, calls: [] };
17203
+ analyzeBody(bodyStmts, cleanEnv, summary, cleanCtx, "", new Map, [], summaries);
17204
+ summary.internalSinks = cleanCtx.sinks;
17205
+ }
17175
17206
  for (const p of params) {
17176
17207
  const env = new Map;
17177
17208
  for (const q of params)
@@ -17247,6 +17278,14 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
17247
17278
  const src = sourceOf2(node);
17248
17279
  if (src)
17249
17280
  return { tainted: true, sources: [src], sanitizers: [], sourceLine: node.loc?.start?.line };
17281
+ if (node.kind === "cast") {
17282
+ const inner = analyzeExpr(node.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
17283
+ const castType = String(node.type ?? "");
17284
+ if (/^(int|integer|bool|boolean|float|double)$/i.test(castType) && inner.tainted) {
17285
+ return { ...inner, sanitizers: [...inner.sanitizers, { id: "int_cast", label: "Numeric cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] }] };
17286
+ }
17287
+ return inner;
17288
+ }
17250
17289
  if (node.kind === "assign") {
17251
17290
  const byref = node.operator === "=&";
17252
17291
  const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
@@ -17294,8 +17333,11 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
17294
17333
  analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
17295
17334
  return taintedArgs.length > 0 ? mergeTaint(...taintedArgs) : CLEAN;
17296
17335
  }
17297
- const fnName = rootVarName2(node.what);
17336
+ const fnName = calleeName(node.what);
17298
17337
  if (fnName) {
17338
+ if (TAINT_PROPAGATING_BUILTINS.has(fnName) && taintedArgs.length > 0) {
17339
+ return mergeTaint(...taintedArgs);
17340
+ }
17299
17341
  const san = findSanitizers(fnName + "(");
17300
17342
  if (san.length > 0) {
17301
17343
  if (taintedArgs.length > 0) {
@@ -17525,7 +17567,7 @@ function analyzeDataFlow2(path) {
17525
17567
  if (isNode3(s) && s.kind !== "function" && s.kind !== "class")
17526
17568
  topStmts.push(s);
17527
17569
  }
17528
- const mainSummary = { id: "main", name: "main", params: [], byrefParams: new Set, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map };
17570
+ const mainSummary = { id: "main", name: "main", params: [], byrefParams: new Set, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map, internalSinks: [] };
17529
17571
  const mainEnv = new Map;
17530
17572
  const mainCtx = { sinks: [], returnsTainted: false, calls: [] };
17531
17573
  const mainProps = new Map;
@@ -17554,7 +17596,7 @@ function analyzeDataFlow2(path) {
17554
17596
  const seen = new Set;
17555
17597
  const mainCalls = topStmts.flatMap((s) => findKind2(s, "call"));
17556
17598
  for (const call of mainCalls) {
17557
- const fnName = rootVarName2(call.what);
17599
+ const fnName = calleeName(call.what);
17558
17600
  if (!fnName || sinkClassOf2(call))
17559
17601
  continue;
17560
17602
  const target = resolveFuncId(fnName, summaries);
@@ -17609,11 +17651,39 @@ function analyzeDataFlow2(path) {
17609
17651
  queue.push({ funcId: calleeId, param: calleeParam, source: item.source, sourceLine: item.sourceLine, viaReturn: false });
17610
17652
  }
17611
17653
  }
17654
+ const seenInternal = new Set;
17655
+ for (const sum of summaries.values()) {
17656
+ for (const hit of sum.internalSinks) {
17657
+ const cls = classifySink2(hit.sink);
17658
+ if (!cls)
17659
+ continue;
17660
+ const key = `${hit.sink}:${hit.line}`;
17661
+ if (seenInternal.has(key))
17662
+ continue;
17663
+ seenInternal.add(key);
17664
+ if (hit.sanitized) {
17665
+ result.suppressed += 1;
17666
+ continue;
17667
+ }
17668
+ result.findings.push({
17669
+ sink: hit.sink,
17670
+ sink_line: hit.line,
17671
+ category: cls.category,
17672
+ cwe: cls.cwe,
17673
+ sanitized: false,
17674
+ auth_gated: hit.authGated,
17675
+ auth_gates: hit.authGates,
17676
+ interprocedural: false,
17677
+ via_return: false,
17678
+ internal_source: true
17679
+ });
17680
+ }
17681
+ }
17612
17682
  if (cacheCode)
17613
17683
  setCache("eagle2", cacheCode, result);
17614
17684
  return result;
17615
17685
  }
17616
- var import_php_parser3, parser3, CLEAN, VALIDATION_GATES;
17686
+ var import_php_parser3, parser3, TAINT_PROPAGATING_BUILTINS, CLEAN, VALIDATION_GATES;
17617
17687
  var init_eagle2 = __esm(() => {
17618
17688
  init_dataflow();
17619
17689
  init_scanner();
@@ -17624,6 +17694,33 @@ var init_eagle2 = __esm(() => {
17624
17694
  parser: { extractDoc: false, php7: true },
17625
17695
  ast: { withPositions: true, withSource: false }
17626
17696
  });
17697
+ TAINT_PROPAGATING_BUILTINS = new Set([
17698
+ "implode",
17699
+ "join",
17700
+ "sprintf",
17701
+ "vsprintf",
17702
+ "str_replace",
17703
+ "str_ireplace",
17704
+ "substr",
17705
+ "mb_substr",
17706
+ "substr_replace",
17707
+ "trim",
17708
+ "ltrim",
17709
+ "rtrim",
17710
+ "strtolower",
17711
+ "strtoupper",
17712
+ "ucfirst",
17713
+ "lcfirst",
17714
+ "urldecode",
17715
+ "rawurldecode",
17716
+ "base64_decode",
17717
+ "html_entity_decode",
17718
+ "stripslashes",
17719
+ "addslashes",
17720
+ "strval",
17721
+ "preg_quote",
17722
+ "nl2br"
17723
+ ]);
17627
17724
  CLEAN = { tainted: false, sources: [], sanitizers: [] };
17628
17725
  VALIDATION_GATES = ["in_array", "preg_match", "ctype_digit", "ctype_alnum", "is_numeric"];
17629
17726
  });
@@ -35599,7 +35696,6 @@ var init_adapters = __esm(() => {
35599
35696
  [/\.query\s*\(/i, "sql_execution", "CWE-89"],
35600
35697
  [/eval\s*\(/i, "code_execution", "CWE-94"],
35601
35698
  [/exec\s*\(/i, "command_execution", "CWE-78"],
35602
- [/child_process/i, "command_execution", "CWE-78"],
35603
35699
  [/execSync\s*\(/i, "command_execution", "CWE-78"],
35604
35700
  [/\.send\s*\(/i, "html_render", "CWE-79"],
35605
35701
  [/innerHTML/i, "html_render", "CWE-79"],
@@ -35609,7 +35705,6 @@ var init_adapters = __esm(() => {
35609
35705
  [/fs\.readFile/i, "path_traversal", "CWE-22"],
35610
35706
  [/fs\.readFileSync/i, "path_traversal", "CWE-22"],
35611
35707
  [/fs\.unlink/i, "file_operations", "CWE-434"],
35612
- [/require\s*\(/i, "file_inclusion", "CWE-98"],
35613
35708
  [/fetch\s*\(/i, "http_request", "CWE-918"],
35614
35709
  [/http\.get|https\.get|axios/i, "http_request", "CWE-918"],
35615
35710
  [/http\.request|https\.request/i, "http_request", "CWE-918"],
@@ -35692,8 +35787,8 @@ var init_adapters = __esm(() => {
35692
35787
  if (c.type === "Identifier" && c.name)
35693
35788
  argVars.push(c.name);
35694
35789
  for (const [re, kind, cwe] of JS_SINKS) {
35695
- if (re.test(fullCallText) || re.test(calleeText + "(")) {
35696
- if (kind === "sql_execution" && /\?/.test(fullCallText) && /,\s*\[/.test(fullCallText))
35790
+ if (re.test(calleeText + "(")) {
35791
+ if (kind === "sql_execution" && /(\$\d+|\?)/.test(fullCallText) && /,\s*\[/.test(fullCallText))
35697
35792
  continue;
35698
35793
  for (const v of argVars)
35699
35794
  sinks.push({ variable: v, kind, line: n.loc?.start?.line ?? 0, cwe });
@@ -35719,6 +35814,10 @@ var init_adapters = __esm(() => {
35719
35814
  [/request\.cookies/i, "http_cookie"],
35720
35815
  [/request\.files/i, "uploaded_file"],
35721
35816
  [/request\.get/i, "http_parameter"],
35817
+ [/request\.GET/i, "http_parameter"],
35818
+ [/request\.POST/i, "http_body"],
35819
+ [/request\.data/i, "http_body"],
35820
+ [/request\.query_params/i, "http_parameter"],
35722
35821
  [/sys\.argv/i, "cli_argument"],
35723
35822
  [/os\.environ/i, "env_variable"]
35724
35823
  ];
@@ -35900,6 +35999,14 @@ var init_adapters = __esm(() => {
35900
35999
  sources.push({ variable: varName, kind: src[1], line, attacker_controlled: src[1] !== "cli_argument" });
35901
36000
  assigns.push({ target: varName, sources: [], line });
35902
36001
  }
36002
+ const annotRe = /@(?:RequestParam|PathVariable|RequestBody|RequestHeader|CookieValue)(?:\([^)]*\))?\s+(?:[\w<>,\.\[\]\s]+\s+)?([A-Za-z_]\w*)/g;
36003
+ while ((m = annotRe.exec(code)) !== null) {
36004
+ const varName = m[1];
36005
+ const line = code.slice(0, m.index).split(`
36006
+ `).length;
36007
+ sources.push({ variable: varName, kind: "http_parameter", line, attacker_controlled: true });
36008
+ assigns.push({ target: varName, sources: [], line });
36009
+ }
35903
36010
  for (let i = 0;i < lines.length; i++) {
35904
36011
  const line = lines[i];
35905
36012
  for (const [re, kind, cwe] of JAVA_SINKS) {
@@ -35908,9 +36015,12 @@ var init_adapters = __esm(() => {
35908
36015
  const vre = /([A-Za-z_][A-Za-z0-9_]*)/g;
35909
36016
  let vm;
35910
36017
  while ((vm = vre.exec(line)) !== null) {
35911
- if (!/Runtime|getRuntime|ProcessBuilder|ObjectInputStream|File|HtmlUtils|StringEscapeUtils|PreparedStatement|Integer|Long|sendRedirect|forward/.test(vm[1])) {
35912
- argVars.push(vm[1]);
35913
- }
36018
+ const id = vm[1];
36019
+ if (/Runtime|getRuntime|ProcessBuilder|ObjectInputStream|HtmlUtils|StringEscapeUtils|PreparedStatement|Integer|Long|sendRedirect|forward|executeQuery|execute|exec|createStatement|getConnection|prepareStatement|getString|DriverManager/.test(id))
36020
+ continue;
36021
+ if (/^[A-Z]/.test(id))
36022
+ continue;
36023
+ argVars.push(id);
35914
36024
  }
35915
36025
  for (const v of argVars.slice(0, 8))
35916
36026
  sinks.push({ variable: v, kind, line: i + 1, cwe });
@@ -36963,6 +37073,7 @@ var init_agents = __esm(() => {
36963
37073
  init_scanner();
36964
37074
  init_eagle2();
36965
37075
  init_universal_taint();
37076
+ init_adapters();
36966
37077
  init_active();
36967
37078
  init_strike();
36968
37079
  reconAgent = {
@@ -37912,13 +38023,86 @@ var init_cli = __esm(() => {
37912
38023
  VALID_MODES = ["web", "api", "source", "mobile", "cloud", "bugbounty", "redteam", "audit"];
37913
38024
  });
37914
38025
 
38026
+ // src/mission/enterprise-benchmark.ts
38027
+ import { existsSync as existsSync11 } from "node:fs";
38028
+ import { join as join17, resolve } from "node:path";
38029
+ async function runEnterpriseBenchmark() {
38030
+ const fixtures = [];
38031
+ for (const fx of ENTERPRISE_FIXTURES) {
38032
+ if (!existsSync11(fx.dir)) {
38033
+ fixtures.push({ id: fx.id, available: false, findings: [], detected_vulns: 0, expected_vulns: fx.expected_vulns, recall: 0, false_positive_tokens: [] });
38034
+ continue;
38035
+ }
38036
+ const m = await startMission(fx.dir, "source", { scope: [] });
38037
+ const findings = m.hypotheses.filter((h) => h.type !== "missing_authorization" && h.type !== "auth_flow_detected").map((h) => h.type);
38038
+ const falsePositives = fx.safe_tokens.filter((tok) => m.hypotheses.some((h) => h.target.includes(tok) || h.evidence.includes(tok)));
38039
+ fixtures.push({
38040
+ id: fx.id,
38041
+ available: true,
38042
+ findings,
38043
+ detected_vulns: findings.length,
38044
+ expected_vulns: fx.expected_vulns,
38045
+ recall: fx.expected_vulns > 0 ? findings.length / fx.expected_vulns : 0,
38046
+ false_positive_tokens: falsePositives
38047
+ });
38048
+ }
38049
+ const totalDetected = fixtures.filter((f) => f.available).reduce((s, f) => s + f.detected_vulns, 0);
38050
+ const totalExpected = fixtures.filter((f) => f.available).reduce((s, f) => s + f.expected_vulns, 0);
38051
+ const totalFP = fixtures.filter((f) => f.available).reduce((s, f) => s + f.false_positive_tokens.length, 0);
38052
+ return {
38053
+ fixtures,
38054
+ summary: {
38055
+ fixtures: fixtures.filter((f) => f.available).length,
38056
+ detected_vulns: totalDetected,
38057
+ expected_vulns: totalExpected,
38058
+ recall: Number((totalExpected > 0 ? totalDetected / totalExpected : 0).toFixed(2)),
38059
+ false_positives: totalFP
38060
+ }
38061
+ };
38062
+ }
38063
+ var ROOT10, ENTERPRISE_FIXTURES;
38064
+ var init_enterprise_benchmark = __esm(() => {
38065
+ init_control();
38066
+ ROOT10 = resolve(process.cwd(), "bench", "enterprise");
38067
+ ENTERPRISE_FIXTURES = [
38068
+ {
38069
+ id: "php-laravel",
38070
+ dir: join17(ROOT10, "php-laravel"),
38071
+ expected_vulns: 4,
38072
+ expected_sinks: ["DB::select", "whereRaw", "system", "eval"],
38073
+ safe_tokens: ["User::where", "htmlspecialchars"]
38074
+ },
38075
+ {
38076
+ id: "js-express",
38077
+ dir: join17(ROOT10, "js-express"),
38078
+ expected_vulns: 2,
38079
+ expected_sinks: ["sql_execution", "command_execution"],
38080
+ safe_tokens: ["$1", "replace"]
38081
+ },
38082
+ {
38083
+ id: "py-django",
38084
+ dir: join17(ROOT10, "py-django"),
38085
+ expected_vulns: 2,
38086
+ expected_sinks: ["sql_execution", "command_execution"],
38087
+ safe_tokens: ["filter", "escape"]
38088
+ },
38089
+ {
38090
+ id: "java-spring",
38091
+ dir: join17(ROOT10, "java-spring"),
38092
+ expected_vulns: 2,
38093
+ expected_sinks: ["sql_execution", "command_execution"],
38094
+ safe_tokens: ["prepareStatement", "PreparedStatement"]
38095
+ }
38096
+ ];
38097
+ });
38098
+
37915
38099
  // src/mission/benchmark.ts
37916
38100
  import { writeFileSync as writeFileSync7, rmSync as rmSync5, mkdtempSync as mkdtempSync2 } from "node:fs";
37917
- import { join as join17 } from "node:path";
38101
+ import { join as join18 } from "node:path";
37918
38102
  import { tmpdir as tmpdir3 } from "node:os";
37919
38103
  async function runOne(id, vulnerable, language, code) {
37920
- const dir = mkdtempSync2(join17(tmpdir3(), "blitz-auto-"));
37921
- const file = join17(dir, `sample${EXT[language] ?? ".txt"}`);
38104
+ const dir = mkdtempSync2(join18(tmpdir3(), "blitz-auto-"));
38105
+ const file = join18(dir, `sample${EXT[language] ?? ".txt"}`);
37922
38106
  writeFileSync7(file, code + `
37923
38107
  `);
37924
38108
  const t0 = Date.now();
@@ -55685,14 +55869,14 @@ Registered with ${ok}/${installed.length} agent(s).`);
55685
55869
  }
55686
55870
 
55687
55871
  // src/index.ts
55688
- import { readFileSync as readFileSync23, existsSync as existsSync11 } from "node:fs";
55689
- import { join as join18 } from "node:path";
55872
+ import { readFileSync as readFileSync23, existsSync as existsSync12 } from "node:fs";
55873
+ import { join as join19 } from "node:path";
55690
55874
  import { fileURLToPath as fileURLToPath12 } from "node:url";
55691
- var ROOT10 = join18(fileURLToPath12(new URL(".", import.meta.url)), "..");
55875
+ var ROOT11 = join19(fileURLToPath12(new URL(".", import.meta.url)), "..");
55692
55876
  function readVersion() {
55693
55877
  try {
55694
- const p = join18(ROOT10, "package.json");
55695
- if (existsSync11(p))
55878
+ const p = join19(ROOT11, "package.json");
55879
+ if (existsSync12(p))
55696
55880
  return JSON.parse(readFileSync23(p, "utf8")).version ?? "1.0.0";
55697
55881
  } catch {}
55698
55882
  return "1.0.0";
@@ -55737,6 +55921,13 @@ async function main() {
55737
55921
  return;
55738
55922
  }
55739
55923
  if (args[0] === "benchmark") {
55924
+ if (args.includes("--enterprise")) {
55925
+ await Promise.resolve().then(() => init_enterprise_benchmark());
55926
+ const r = await runEnterpriseBenchmark();
55927
+ console.log("Blitz Strike enterprise benchmark (framework-style fixtures)");
55928
+ console.log(JSON.stringify(r, null, 2));
55929
+ return;
55930
+ }
55740
55931
  await Promise.resolve().then(() => init_benchmark2());
55741
55932
  const limit = args.includes("--full") ? undefined : 10;
55742
55933
  const r = await runAutonomousBenchmark(limit);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.58",
3
+ "version": "1.0.60",
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": {