blitzstrike 1.0.58 → 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 +28 -0
- package/bench/enterprise/java-spring/src/UserController.java +35 -0
- package/bench/enterprise/js-express/db/connection.js +10 -0
- package/bench/enterprise/js-express/routes/users.js +32 -0
- package/bench/enterprise/php-laravel/app/Http/Controllers/ReportController.php +30 -0
- package/bench/enterprise/php-laravel/app/Http/Controllers/UserController.php +33 -0
- package/bench/enterprise/php-laravel/app/Models/User.php +10 -0
- package/bench/enterprise/py-django/app/views.py +35 -0
- package/dist/index.js +161 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ 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
|
+
|
|
8
36
|
## [1.0.58] — 2026-09-13
|
|
9
37
|
|
|
10
38
|
### Fixed — total-verification pass
|
|
@@ -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,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(
|
|
35696
|
-
if (kind === "sql_execution" &&
|
|
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
|
-
|
|
35912
|
-
|
|
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 });
|
|
@@ -36963,6 +37025,7 @@ var init_agents = __esm(() => {
|
|
|
36963
37025
|
init_scanner();
|
|
36964
37026
|
init_eagle2();
|
|
36965
37027
|
init_universal_taint();
|
|
37028
|
+
init_adapters();
|
|
36966
37029
|
init_active();
|
|
36967
37030
|
init_strike();
|
|
36968
37031
|
reconAgent = {
|
|
@@ -37912,13 +37975,86 @@ var init_cli = __esm(() => {
|
|
|
37912
37975
|
VALID_MODES = ["web", "api", "source", "mobile", "cloud", "bugbounty", "redteam", "audit"];
|
|
37913
37976
|
});
|
|
37914
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
|
+
|
|
37915
38051
|
// src/mission/benchmark.ts
|
|
37916
38052
|
import { writeFileSync as writeFileSync7, rmSync as rmSync5, mkdtempSync as mkdtempSync2 } from "node:fs";
|
|
37917
|
-
import { join as
|
|
38053
|
+
import { join as join18 } from "node:path";
|
|
37918
38054
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
37919
38055
|
async function runOne(id, vulnerable, language, code) {
|
|
37920
|
-
const dir = mkdtempSync2(
|
|
37921
|
-
const file =
|
|
38056
|
+
const dir = mkdtempSync2(join18(tmpdir3(), "blitz-auto-"));
|
|
38057
|
+
const file = join18(dir, `sample${EXT[language] ?? ".txt"}`);
|
|
37922
38058
|
writeFileSync7(file, code + `
|
|
37923
38059
|
`);
|
|
37924
38060
|
const t0 = Date.now();
|
|
@@ -55685,14 +55821,14 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
55685
55821
|
}
|
|
55686
55822
|
|
|
55687
55823
|
// src/index.ts
|
|
55688
|
-
import { readFileSync as readFileSync23, existsSync as
|
|
55689
|
-
import { join as
|
|
55824
|
+
import { readFileSync as readFileSync23, existsSync as existsSync12 } from "node:fs";
|
|
55825
|
+
import { join as join19 } from "node:path";
|
|
55690
55826
|
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
55691
|
-
var
|
|
55827
|
+
var ROOT11 = join19(fileURLToPath12(new URL(".", import.meta.url)), "..");
|
|
55692
55828
|
function readVersion() {
|
|
55693
55829
|
try {
|
|
55694
|
-
const p =
|
|
55695
|
-
if (
|
|
55830
|
+
const p = join19(ROOT11, "package.json");
|
|
55831
|
+
if (existsSync12(p))
|
|
55696
55832
|
return JSON.parse(readFileSync23(p, "utf8")).version ?? "1.0.0";
|
|
55697
55833
|
} catch {}
|
|
55698
55834
|
return "1.0.0";
|
|
@@ -55737,6 +55873,13 @@ async function main() {
|
|
|
55737
55873
|
return;
|
|
55738
55874
|
}
|
|
55739
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
|
+
}
|
|
55740
55883
|
await Promise.resolve().then(() => init_benchmark2());
|
|
55741
55884
|
const limit = args.includes("--full") ? undefined : 10;
|
|
55742
55885
|
const r = await runAutonomousBenchmark(limit);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
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": {
|