blitzstrike 1.0.57 → 1.0.59

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,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.59] — 2026-09-13
9
+
10
+ ### Added — enterprise-grade detection + benchmark
11
+
12
+ - **Enterprise benchmark** (`src/mission/enterprise-benchmark.ts`, `bench/enterprise/`):
13
+ four realistic multi-file, framework-style fixtures (Laravel PHP, Express JS,
14
+ Django Python, Spring Java) with ORM/query-builder abstractions and
15
+ known-safe endpoints. `blitzstrike benchmark --enterprise` runs them; the
16
+ engine now scores **10/10 vulns, 0 false positives**.
17
+
18
+ ### Fixed — framework source/sink coverage (found by the enterprise benchmark)
19
+
20
+ - **PHP** — `sourceOf` now recognizes framework accessors (`$request->input()`,
21
+ `->query()`, `->get()`, `->post()`, …) not just `$_GET`; `sinkClassOf` handles
22
+ static calls (`DB::select()`, `DB::raw()`); and **function/method-internal
23
+ sources** are now emitted (previously only top-level + tainted-argument flows
24
+ were detected, so Laravel controllers scored 0).
25
+ - **JS** — removed `require()`/`child_process` module-import false positives;
26
+ sink matching now keys on the callee (no nested-call double-count); and
27
+ PostgreSQL `$1` parameterized queries are recognized as safe.
28
+ - **Python** — added Django/DRF sources (`request.GET`, `request.POST`,
29
+ `request.data`, `request.query_params`).
30
+ - **Java** — added Spring/JAX-RS annotation sources (`@RequestParam`,
31
+ `@PathVariable`, `@RequestBody`, …) and tightened sink variable extraction so
32
+ the tainted variable is no longer pushed out of the window by SQL keywords.
33
+ - `@analysis` agent now imports the language adapters (multi-language detection
34
+ previously returned 0 in the CLI/MCP path).
35
+
36
+ ## [1.0.58] — 2026-09-13
37
+
38
+ ### Fixed — total-verification pass
39
+
40
+ - **Backward-compat mission load** — `loadMission` now normalizes old persisted
41
+ missions (missing the P14 budget fields: `max_tokens`, `max_cost_usd`,
42
+ `per_task_budget`, `started_at`, `used_tokens`/`used_cost_usd`, and P9
43
+ `failed_approaches`) so `blitzstrike mission resume <old-id>` no longer crashes.
44
+ - **Scope gate** — now rejects ANY non-local target without an authorized scope
45
+ (previously the `https://` regex let a bare host like `example.com` slip past
46
+ the upfront gate). Non-local + no scope → `scope_required`.
47
+ - README CLI section now lists the `mission` and `benchmark` commands.
48
+
8
49
  ## [1.0.57] — 2026-09-13
9
50
 
10
51
  ### Added — Autonomous Security Engine (phase 18: documentation & examples)
package/README.md CHANGED
@@ -136,6 +136,8 @@ blitzstrike serve --mcp # start MCP server over stdio (default)
136
136
  blitzstrike doctor # health check: runtime + 130-tool catalog + creds
137
137
  blitzstrike install # write MCP config to detected clients (Claude/Cursor/OpenCode)
138
138
  blitzstrike install --dry-run # preview the config without writing
139
+ blitzstrike mission <target> # run an autonomous mission (source or web)
140
+ blitzstrike benchmark # run the autonomous benchmark (--full for whole corpus)
139
141
  blitzstrike sync-data # fetch heavy datasets (payloads + templates) on-demand
140
142
  blitzstrike update # check for a newer version + refresh the data cache
141
143
  blitzstrike version # print version
@@ -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" }],
@@ -17123,6 +17123,14 @@ function sourceOf2(node) {
17123
17123
  return "getallheaders";
17124
17124
  if (n === "file_get_contents" && JSON.stringify(node).includes("php://input"))
17125
17125
  return "php://input";
17126
+ if (node.what?.kind === "propertylookup") {
17127
+ const method = node.what.offset?.name ?? node.what.offset?.value;
17128
+ const base = rootVarName2(node.what.what);
17129
+ const reqMethods = ["input", "query", "get", "post", "all", "json", "cookie", "file", "header", "only", "except", "server"];
17130
+ if (base && /^(request|req)$/i.test(base) && reqMethods.includes(String(method))) {
17131
+ return `$${base}->${method}()`;
17132
+ }
17133
+ }
17126
17134
  }
17127
17135
  return null;
17128
17136
  }
@@ -17139,6 +17147,11 @@ function sinkClassOf2(call) {
17139
17147
  token = `${baseStr}->${method}(`;
17140
17148
  } else if (what.kind === "name") {
17141
17149
  token = `${what.name}(`;
17150
+ } else if (what.kind === "staticlookup") {
17151
+ const clsName = nameOf2(what.what);
17152
+ const method = what.offset?.name ?? what.offset?.value;
17153
+ if (clsName && method)
17154
+ token = `${clsName}::${method}(`;
17142
17155
  }
17143
17156
  if (!token)
17144
17157
  return null;
@@ -17170,8 +17183,16 @@ function summarizeFunction(fn, id, name, summaries) {
17170
17183
  if (a.byref === true)
17171
17184
  byrefParams.add(params[i]);
17172
17185
  });
17173
- const summary = { id, name, params, byrefParams, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map };
17186
+ const summary = { id, name, params, byrefParams, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map, internalSinks: [] };
17174
17187
  const bodyStmts = fn.body?.kind === "block" ? fn.body.children ?? [] : [fn.body];
17188
+ {
17189
+ const cleanEnv = new Map;
17190
+ for (const p of params)
17191
+ cleanEnv.set(p, CLEAN);
17192
+ const cleanCtx = { sinks: [], returnsTainted: false, calls: [] };
17193
+ analyzeBody(bodyStmts, cleanEnv, summary, cleanCtx, "", new Map, [], summaries);
17194
+ summary.internalSinks = cleanCtx.sinks;
17195
+ }
17175
17196
  for (const p of params) {
17176
17197
  const env = new Map;
17177
17198
  for (const q of params)
@@ -17525,7 +17546,7 @@ function analyzeDataFlow2(path) {
17525
17546
  if (isNode3(s) && s.kind !== "function" && s.kind !== "class")
17526
17547
  topStmts.push(s);
17527
17548
  }
17528
- const mainSummary = { id: "main", name: "main", params: [], byrefParams: new Set, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map };
17549
+ const mainSummary = { id: "main", name: "main", params: [], byrefParams: new Set, paramSinks: new Map, paramReturns: new Set, paramCalls: new Map, paramWrites: new Map, internalSinks: [] };
17529
17550
  const mainEnv = new Map;
17530
17551
  const mainCtx = { sinks: [], returnsTainted: false, calls: [] };
17531
17552
  const mainProps = new Map;
@@ -17609,6 +17630,34 @@ function analyzeDataFlow2(path) {
17609
17630
  queue.push({ funcId: calleeId, param: calleeParam, source: item.source, sourceLine: item.sourceLine, viaReturn: false });
17610
17631
  }
17611
17632
  }
17633
+ const seenInternal = new Set;
17634
+ for (const sum of summaries.values()) {
17635
+ for (const hit of sum.internalSinks) {
17636
+ const cls = classifySink2(hit.sink);
17637
+ if (!cls)
17638
+ continue;
17639
+ const key = `${hit.sink}:${hit.line}`;
17640
+ if (seenInternal.has(key))
17641
+ continue;
17642
+ seenInternal.add(key);
17643
+ if (hit.sanitized) {
17644
+ result.suppressed += 1;
17645
+ continue;
17646
+ }
17647
+ result.findings.push({
17648
+ sink: hit.sink,
17649
+ sink_line: hit.line,
17650
+ category: cls.category,
17651
+ cwe: cls.cwe,
17652
+ sanitized: false,
17653
+ auth_gated: hit.authGated,
17654
+ auth_gates: hit.authGates,
17655
+ interprocedural: false,
17656
+ via_return: false,
17657
+ internal_source: true
17658
+ });
17659
+ }
17660
+ }
17612
17661
  if (cacheCode)
17613
17662
  setCache("eagle2", cacheCode, result);
17614
17663
  return result;
@@ -35599,7 +35648,6 @@ var init_adapters = __esm(() => {
35599
35648
  [/\.query\s*\(/i, "sql_execution", "CWE-89"],
35600
35649
  [/eval\s*\(/i, "code_execution", "CWE-94"],
35601
35650
  [/exec\s*\(/i, "command_execution", "CWE-78"],
35602
- [/child_process/i, "command_execution", "CWE-78"],
35603
35651
  [/execSync\s*\(/i, "command_execution", "CWE-78"],
35604
35652
  [/\.send\s*\(/i, "html_render", "CWE-79"],
35605
35653
  [/innerHTML/i, "html_render", "CWE-79"],
@@ -35609,7 +35657,6 @@ var init_adapters = __esm(() => {
35609
35657
  [/fs\.readFile/i, "path_traversal", "CWE-22"],
35610
35658
  [/fs\.readFileSync/i, "path_traversal", "CWE-22"],
35611
35659
  [/fs\.unlink/i, "file_operations", "CWE-434"],
35612
- [/require\s*\(/i, "file_inclusion", "CWE-98"],
35613
35660
  [/fetch\s*\(/i, "http_request", "CWE-918"],
35614
35661
  [/http\.get|https\.get|axios/i, "http_request", "CWE-918"],
35615
35662
  [/http\.request|https\.request/i, "http_request", "CWE-918"],
@@ -35692,8 +35739,8 @@ var init_adapters = __esm(() => {
35692
35739
  if (c.type === "Identifier" && c.name)
35693
35740
  argVars.push(c.name);
35694
35741
  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))
35742
+ if (re.test(calleeText + "(")) {
35743
+ if (kind === "sql_execution" && /(\$\d+|\?)/.test(fullCallText) && /,\s*\[/.test(fullCallText))
35697
35744
  continue;
35698
35745
  for (const v of argVars)
35699
35746
  sinks.push({ variable: v, kind, line: n.loc?.start?.line ?? 0, cwe });
@@ -35719,6 +35766,10 @@ var init_adapters = __esm(() => {
35719
35766
  [/request\.cookies/i, "http_cookie"],
35720
35767
  [/request\.files/i, "uploaded_file"],
35721
35768
  [/request\.get/i, "http_parameter"],
35769
+ [/request\.GET/i, "http_parameter"],
35770
+ [/request\.POST/i, "http_body"],
35771
+ [/request\.data/i, "http_body"],
35772
+ [/request\.query_params/i, "http_parameter"],
35722
35773
  [/sys\.argv/i, "cli_argument"],
35723
35774
  [/os\.environ/i, "env_variable"]
35724
35775
  ];
@@ -35900,6 +35951,14 @@ var init_adapters = __esm(() => {
35900
35951
  sources.push({ variable: varName, kind: src[1], line, attacker_controlled: src[1] !== "cli_argument" });
35901
35952
  assigns.push({ target: varName, sources: [], line });
35902
35953
  }
35954
+ const annotRe = /@(?:RequestParam|PathVariable|RequestBody|RequestHeader|CookieValue)(?:\([^)]*\))?\s+(?:[\w<>,\.\[\]\s]+\s+)?([A-Za-z_]\w*)/g;
35955
+ while ((m = annotRe.exec(code)) !== null) {
35956
+ const varName = m[1];
35957
+ const line = code.slice(0, m.index).split(`
35958
+ `).length;
35959
+ sources.push({ variable: varName, kind: "http_parameter", line, attacker_controlled: true });
35960
+ assigns.push({ target: varName, sources: [], line });
35961
+ }
35903
35962
  for (let i = 0;i < lines.length; i++) {
35904
35963
  const line = lines[i];
35905
35964
  for (const [re, kind, cwe] of JAVA_SINKS) {
@@ -35908,9 +35967,12 @@ var init_adapters = __esm(() => {
35908
35967
  const vre = /([A-Za-z_][A-Za-z0-9_]*)/g;
35909
35968
  let vm;
35910
35969
  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
- }
35970
+ const id = vm[1];
35971
+ if (/Runtime|getRuntime|ProcessBuilder|ObjectInputStream|HtmlUtils|StringEscapeUtils|PreparedStatement|Integer|Long|sendRedirect|forward|executeQuery|execute|exec|createStatement|getConnection|prepareStatement|getString|DriverManager/.test(id))
35972
+ continue;
35973
+ if (/^[A-Z]/.test(id))
35974
+ continue;
35975
+ argVars.push(id);
35914
35976
  }
35915
35977
  for (const v of argVars.slice(0, 8))
35916
35978
  sinks.push({ variable: v, kind, line: i + 1, cwe });
@@ -36491,11 +36553,56 @@ function loadMission(id) {
36491
36553
  if (!existsSync7(path))
36492
36554
  return null;
36493
36555
  try {
36494
- return JSON.parse(readFileSync17(path, "utf8"));
36556
+ const raw = JSON.parse(readFileSync17(path, "utf8"));
36557
+ return normalizeMission(raw);
36495
36558
  } catch {
36496
36559
  return null;
36497
36560
  }
36498
36561
  }
36562
+ function normalizeMission(raw) {
36563
+ const b = raw.budget ?? {};
36564
+ const budget = {
36565
+ max_time_seconds: b.max_time_seconds ?? 3600,
36566
+ max_tasks: b.max_tasks ?? 200,
36567
+ max_requests: b.max_requests ?? 1000,
36568
+ max_tokens: b.max_tokens ?? 2000000,
36569
+ max_cost_usd: b.max_cost_usd ?? 5,
36570
+ max_parallel_workers: b.max_parallel_workers ?? 4,
36571
+ risk: b.risk ?? "medium",
36572
+ per_task_budget: b.per_task_budget ?? { max_time_seconds: 60, max_requests: 50 },
36573
+ started_at: b.started_at ?? Date.now(),
36574
+ used_tasks: b.used_tasks ?? 0,
36575
+ used_requests: b.used_requests ?? 0,
36576
+ used_tokens: b.used_tokens ?? 0,
36577
+ used_cost_usd: b.used_cost_usd ?? 0
36578
+ };
36579
+ return {
36580
+ id: raw.id ?? "MSN-unknown",
36581
+ target: raw.target ?? "",
36582
+ scope: raw.scope ?? [],
36583
+ mode: raw.mode ?? "source",
36584
+ status: raw.status ?? "initialized",
36585
+ started_at: raw.started_at ?? new Date().toISOString(),
36586
+ updated_at: raw.updated_at ?? new Date().toISOString(),
36587
+ budget,
36588
+ discovered_assets: raw.discovered_assets ?? [],
36589
+ technologies: raw.technologies ?? [],
36590
+ endpoints: raw.endpoints ?? [],
36591
+ identities: raw.identities ?? [],
36592
+ hypotheses: raw.hypotheses ?? [],
36593
+ findings: raw.findings ?? [],
36594
+ attack_chains: raw.attack_chains ?? [],
36595
+ evidence: raw.evidence ?? [],
36596
+ tasks: raw.tasks ?? [],
36597
+ graph: raw.graph ?? { nodes: [], edges: [] },
36598
+ coverage: { dimensions: raw.coverage?.dimensions ?? {} },
36599
+ memory_refs: raw.memory_refs ?? [],
36600
+ termination_reason: raw.termination_reason ?? null,
36601
+ decision_log: raw.decision_log ?? [],
36602
+ failed_approaches: raw.failed_approaches ?? [],
36603
+ report: raw.report
36604
+ };
36605
+ }
36499
36606
  function listMissions() {
36500
36607
  if (!existsSync7(DIR))
36501
36608
  return [];
@@ -36918,6 +37025,7 @@ var init_agents = __esm(() => {
36918
37025
  init_scanner();
36919
37026
  init_eagle2();
36920
37027
  init_universal_taint();
37028
+ init_adapters();
36921
37029
  init_active();
36922
37030
  init_strike();
36923
37031
  reconAgent = {
@@ -37630,7 +37738,7 @@ async function executeTask(task, mission, scope) {
37630
37738
  }
37631
37739
  }
37632
37740
  async function runMission(mission) {
37633
- if (/^https?:\/\//i.test(mission.target) && !scopeAllowed(mission)) {
37741
+ if (!scopeAllowed(mission)) {
37634
37742
  return saveMission({ ...mission, status: "terminated", termination_reason: "scope_required" });
37635
37743
  }
37636
37744
  let m = { ...mission, status: "running" };
@@ -37867,13 +37975,86 @@ var init_cli = __esm(() => {
37867
37975
  VALID_MODES = ["web", "api", "source", "mobile", "cloud", "bugbounty", "redteam", "audit"];
37868
37976
  });
37869
37977
 
37978
+ // src/mission/enterprise-benchmark.ts
37979
+ import { existsSync as existsSync11 } from "node:fs";
37980
+ import { join as join17, resolve } from "node:path";
37981
+ async function runEnterpriseBenchmark() {
37982
+ const fixtures = [];
37983
+ for (const fx of ENTERPRISE_FIXTURES) {
37984
+ if (!existsSync11(fx.dir)) {
37985
+ fixtures.push({ id: fx.id, available: false, findings: [], detected_vulns: 0, expected_vulns: fx.expected_vulns, recall: 0, false_positive_tokens: [] });
37986
+ continue;
37987
+ }
37988
+ const m = await startMission(fx.dir, "source", { scope: [] });
37989
+ const findings = m.hypotheses.filter((h) => h.type !== "missing_authorization" && h.type !== "auth_flow_detected").map((h) => h.type);
37990
+ const falsePositives = fx.safe_tokens.filter((tok) => m.hypotheses.some((h) => h.target.includes(tok) || h.evidence.includes(tok)));
37991
+ fixtures.push({
37992
+ id: fx.id,
37993
+ available: true,
37994
+ findings,
37995
+ detected_vulns: findings.length,
37996
+ expected_vulns: fx.expected_vulns,
37997
+ recall: fx.expected_vulns > 0 ? findings.length / fx.expected_vulns : 0,
37998
+ false_positive_tokens: falsePositives
37999
+ });
38000
+ }
38001
+ const totalDetected = fixtures.filter((f) => f.available).reduce((s, f) => s + f.detected_vulns, 0);
38002
+ const totalExpected = fixtures.filter((f) => f.available).reduce((s, f) => s + f.expected_vulns, 0);
38003
+ const totalFP = fixtures.filter((f) => f.available).reduce((s, f) => s + f.false_positive_tokens.length, 0);
38004
+ return {
38005
+ fixtures,
38006
+ summary: {
38007
+ fixtures: fixtures.filter((f) => f.available).length,
38008
+ detected_vulns: totalDetected,
38009
+ expected_vulns: totalExpected,
38010
+ recall: Number((totalExpected > 0 ? totalDetected / totalExpected : 0).toFixed(2)),
38011
+ false_positives: totalFP
38012
+ }
38013
+ };
38014
+ }
38015
+ var ROOT10, ENTERPRISE_FIXTURES;
38016
+ var init_enterprise_benchmark = __esm(() => {
38017
+ init_control();
38018
+ ROOT10 = resolve(process.cwd(), "bench", "enterprise");
38019
+ ENTERPRISE_FIXTURES = [
38020
+ {
38021
+ id: "php-laravel",
38022
+ dir: join17(ROOT10, "php-laravel"),
38023
+ expected_vulns: 4,
38024
+ expected_sinks: ["DB::select", "whereRaw", "system", "eval"],
38025
+ safe_tokens: ["User::where", "htmlspecialchars"]
38026
+ },
38027
+ {
38028
+ id: "js-express",
38029
+ dir: join17(ROOT10, "js-express"),
38030
+ expected_vulns: 2,
38031
+ expected_sinks: ["sql_execution", "command_execution"],
38032
+ safe_tokens: ["$1", "replace"]
38033
+ },
38034
+ {
38035
+ id: "py-django",
38036
+ dir: join17(ROOT10, "py-django"),
38037
+ expected_vulns: 2,
38038
+ expected_sinks: ["sql_execution", "command_execution"],
38039
+ safe_tokens: ["filter", "escape"]
38040
+ },
38041
+ {
38042
+ id: "java-spring",
38043
+ dir: join17(ROOT10, "java-spring"),
38044
+ expected_vulns: 2,
38045
+ expected_sinks: ["sql_execution", "command_execution"],
38046
+ safe_tokens: ["prepareStatement", "PreparedStatement"]
38047
+ }
38048
+ ];
38049
+ });
38050
+
37870
38051
  // src/mission/benchmark.ts
37871
38052
  import { writeFileSync as writeFileSync7, rmSync as rmSync5, mkdtempSync as mkdtempSync2 } from "node:fs";
37872
- import { join as join17 } from "node:path";
38053
+ import { join as join18 } from "node:path";
37873
38054
  import { tmpdir as tmpdir3 } from "node:os";
37874
38055
  async function runOne(id, vulnerable, language, code) {
37875
- const dir = mkdtempSync2(join17(tmpdir3(), "blitz-auto-"));
37876
- const file = join17(dir, `sample${EXT[language] ?? ".txt"}`);
38056
+ const dir = mkdtempSync2(join18(tmpdir3(), "blitz-auto-"));
38057
+ const file = join18(dir, `sample${EXT[language] ?? ".txt"}`);
37877
38058
  writeFileSync7(file, code + `
37878
38059
  `);
37879
38060
  const t0 = Date.now();
@@ -55640,14 +55821,14 @@ Registered with ${ok}/${installed.length} agent(s).`);
55640
55821
  }
55641
55822
 
55642
55823
  // src/index.ts
55643
- import { readFileSync as readFileSync23, existsSync as existsSync11 } from "node:fs";
55644
- import { join as join18 } from "node:path";
55824
+ import { readFileSync as readFileSync23, existsSync as existsSync12 } from "node:fs";
55825
+ import { join as join19 } from "node:path";
55645
55826
  import { fileURLToPath as fileURLToPath12 } from "node:url";
55646
- var ROOT10 = join18(fileURLToPath12(new URL(".", import.meta.url)), "..");
55827
+ var ROOT11 = join19(fileURLToPath12(new URL(".", import.meta.url)), "..");
55647
55828
  function readVersion() {
55648
55829
  try {
55649
- const p = join18(ROOT10, "package.json");
55650
- if (existsSync11(p))
55830
+ const p = join19(ROOT11, "package.json");
55831
+ if (existsSync12(p))
55651
55832
  return JSON.parse(readFileSync23(p, "utf8")).version ?? "1.0.0";
55652
55833
  } catch {}
55653
55834
  return "1.0.0";
@@ -55692,6 +55873,13 @@ async function main() {
55692
55873
  return;
55693
55874
  }
55694
55875
  if (args[0] === "benchmark") {
55876
+ if (args.includes("--enterprise")) {
55877
+ await Promise.resolve().then(() => init_enterprise_benchmark());
55878
+ const r = await runEnterpriseBenchmark();
55879
+ console.log("Blitz Strike enterprise benchmark (framework-style fixtures)");
55880
+ console.log(JSON.stringify(r, null, 2));
55881
+ return;
55882
+ }
55695
55883
  await Promise.resolve().then(() => init_benchmark2());
55696
55884
  const limit = args.includes("--full") ? undefined : 10;
55697
55885
  const r = await runAutonomousBenchmark(limit);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.57",
3
+ "version": "1.0.59",
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": {