blitzstrike 1.0.59 → 1.0.62
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,40 @@ 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.62] — 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Added — route-confusion & dispatch-abuse detection
|
|
11
|
+
|
|
12
|
+
New vulnerability class distinct from source→sink taint (`src/route-confusion.ts`):
|
|
13
|
+
detects when the routing/dispatch layer lets an attacker reach an unintended
|
|
14
|
+
handler or control which handler runs.
|
|
15
|
+
|
|
16
|
+
- `dynamic_dispatch` — attacker-controlled callback/route (`call_user_func`,
|
|
17
|
+
`forward`, `dispatch`, `resolve_route`, `invoke`).
|
|
18
|
+
- `dynamic_method_call` — `$obj->$method()` / `$class::$method()`.
|
|
19
|
+
- `dynamic_include` — `include`/`require` of a variable (LFI via route).
|
|
20
|
+
- `batch_forwarding` — a loop over sub-requests that forwards to inner handlers
|
|
21
|
+
without a per-route authorization re-check.
|
|
22
|
+
- `route_normalization_gap` — route matching against user input without path
|
|
23
|
+
normalization (strtolower/rtrim/urldecode).
|
|
24
|
+
|
|
25
|
+
Wired into the `@source` agent; findings become `route` nodes in the security
|
|
26
|
+
graph. Added a WordPress-style batch-endpoint fixture to the enterprise
|
|
27
|
+
benchmark (`wp-batch-route`).
|
|
28
|
+
|
|
29
|
+
## [1.0.60] — 2026-09-13
|
|
30
|
+
|
|
31
|
+
### Added — deeper taint-flow coverage
|
|
32
|
+
|
|
33
|
+
- **Method-call resolution** (`calleeName`) — `$obj->method()` now resolves to
|
|
34
|
+
its method definition for interprocedural taint, not the object variable.
|
|
35
|
+
- **Built-in taint propagation** — `implode`/`join`/`sprintf`/`str_replace` and
|
|
36
|
+
other array→string/format built-ins now carry taint through to the sink.
|
|
37
|
+
- **Cast handling** — `(array)`/`(string)` casts propagate taint (a string can
|
|
38
|
+
still carry a payload); `(int)`/`(float)` casts act as an int_cast sanitizer.
|
|
39
|
+
- New corpus fixtures covering conditional-sanitizer bypass on array→SQL
|
|
40
|
+
interpolation (`php-int-cast-bypass-vuln`/`-fixed`).
|
|
41
|
+
|
|
8
42
|
## [1.0.59] — 2026-09-13
|
|
9
43
|
|
|
10
44
|
### Added — enterprise-grade detection + benchmark
|
|
@@ -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,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
|
@@ -17104,6 +17104,15 @@ function rootVarName2(node) {
|
|
|
17104
17104
|
return rootVarName2(node.what);
|
|
17105
17105
|
return null;
|
|
17106
17106
|
}
|
|
17107
|
+
function calleeName(what) {
|
|
17108
|
+
if (!isNode3(what))
|
|
17109
|
+
return null;
|
|
17110
|
+
if (what.kind === "name")
|
|
17111
|
+
return typeof what.name === "string" ? what.name : what.name?.name ?? null;
|
|
17112
|
+
if (what.kind === "propertylookup" || what.kind === "staticlookup")
|
|
17113
|
+
return what.offset?.name ?? what.offset?.value ?? null;
|
|
17114
|
+
return null;
|
|
17115
|
+
}
|
|
17107
17116
|
function sourceOf2(node) {
|
|
17108
17117
|
if (!isNode3(node))
|
|
17109
17118
|
return null;
|
|
@@ -17268,6 +17277,14 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
17268
17277
|
const src = sourceOf2(node);
|
|
17269
17278
|
if (src)
|
|
17270
17279
|
return { tainted: true, sources: [src], sanitizers: [], sourceLine: node.loc?.start?.line };
|
|
17280
|
+
if (node.kind === "cast") {
|
|
17281
|
+
const inner = analyzeExpr(node.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
17282
|
+
const castType = String(node.type ?? "");
|
|
17283
|
+
if (/^(int|integer|bool|boolean|float|double)$/i.test(castType) && inner.tainted) {
|
|
17284
|
+
return { ...inner, sanitizers: [...inner.sanitizers, { id: "int_cast", label: "Numeric cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] }] };
|
|
17285
|
+
}
|
|
17286
|
+
return inner;
|
|
17287
|
+
}
|
|
17271
17288
|
if (node.kind === "assign") {
|
|
17272
17289
|
const byref = node.operator === "=&";
|
|
17273
17290
|
const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
@@ -17315,8 +17332,11 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
|
|
|
17315
17332
|
analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
|
|
17316
17333
|
return taintedArgs.length > 0 ? mergeTaint(...taintedArgs) : CLEAN;
|
|
17317
17334
|
}
|
|
17318
|
-
const fnName =
|
|
17335
|
+
const fnName = calleeName(node.what);
|
|
17319
17336
|
if (fnName) {
|
|
17337
|
+
if (TAINT_PROPAGATING_BUILTINS.has(fnName) && taintedArgs.length > 0) {
|
|
17338
|
+
return mergeTaint(...taintedArgs);
|
|
17339
|
+
}
|
|
17320
17340
|
const san = findSanitizers(fnName + "(");
|
|
17321
17341
|
if (san.length > 0) {
|
|
17322
17342
|
if (taintedArgs.length > 0) {
|
|
@@ -17575,7 +17595,7 @@ function analyzeDataFlow2(path) {
|
|
|
17575
17595
|
const seen = new Set;
|
|
17576
17596
|
const mainCalls = topStmts.flatMap((s) => findKind2(s, "call"));
|
|
17577
17597
|
for (const call of mainCalls) {
|
|
17578
|
-
const fnName =
|
|
17598
|
+
const fnName = calleeName(call.what);
|
|
17579
17599
|
if (!fnName || sinkClassOf2(call))
|
|
17580
17600
|
continue;
|
|
17581
17601
|
const target = resolveFuncId(fnName, summaries);
|
|
@@ -17662,7 +17682,7 @@ function analyzeDataFlow2(path) {
|
|
|
17662
17682
|
setCache("eagle2", cacheCode, result);
|
|
17663
17683
|
return result;
|
|
17664
17684
|
}
|
|
17665
|
-
var import_php_parser3, parser3, CLEAN, VALIDATION_GATES;
|
|
17685
|
+
var import_php_parser3, parser3, TAINT_PROPAGATING_BUILTINS, CLEAN, VALIDATION_GATES;
|
|
17666
17686
|
var init_eagle2 = __esm(() => {
|
|
17667
17687
|
init_dataflow();
|
|
17668
17688
|
init_scanner();
|
|
@@ -17673,6 +17693,33 @@ var init_eagle2 = __esm(() => {
|
|
|
17673
17693
|
parser: { extractDoc: false, php7: true },
|
|
17674
17694
|
ast: { withPositions: true, withSource: false }
|
|
17675
17695
|
});
|
|
17696
|
+
TAINT_PROPAGATING_BUILTINS = new Set([
|
|
17697
|
+
"implode",
|
|
17698
|
+
"join",
|
|
17699
|
+
"sprintf",
|
|
17700
|
+
"vsprintf",
|
|
17701
|
+
"str_replace",
|
|
17702
|
+
"str_ireplace",
|
|
17703
|
+
"substr",
|
|
17704
|
+
"mb_substr",
|
|
17705
|
+
"substr_replace",
|
|
17706
|
+
"trim",
|
|
17707
|
+
"ltrim",
|
|
17708
|
+
"rtrim",
|
|
17709
|
+
"strtolower",
|
|
17710
|
+
"strtoupper",
|
|
17711
|
+
"ucfirst",
|
|
17712
|
+
"lcfirst",
|
|
17713
|
+
"urldecode",
|
|
17714
|
+
"rawurldecode",
|
|
17715
|
+
"base64_decode",
|
|
17716
|
+
"html_entity_decode",
|
|
17717
|
+
"stripslashes",
|
|
17718
|
+
"addslashes",
|
|
17719
|
+
"strval",
|
|
17720
|
+
"preg_quote",
|
|
17721
|
+
"nl2br"
|
|
17722
|
+
]);
|
|
17676
17723
|
CLEAN = { tainted: false, sources: [], sanitizers: [] };
|
|
17677
17724
|
VALIDATION_GATES = ["in_array", "preg_match", "ctype_digit", "ctype_alnum", "is_numeric"];
|
|
17678
17725
|
});
|
|
@@ -37002,6 +37049,151 @@ function reviewValidated(mission) {
|
|
|
37002
37049
|
}
|
|
37003
37050
|
var init_reviewer = () => {};
|
|
37004
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)\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
|
+
|
|
37005
37197
|
// src/agents.ts
|
|
37006
37198
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
37007
37199
|
function emptyGraph() {
|
|
@@ -37025,6 +37217,7 @@ var init_agents = __esm(() => {
|
|
|
37025
37217
|
init_scanner();
|
|
37026
37218
|
init_eagle2();
|
|
37027
37219
|
init_universal_taint();
|
|
37220
|
+
init_route_confusion();
|
|
37028
37221
|
init_adapters();
|
|
37029
37222
|
init_active();
|
|
37030
37223
|
init_strike();
|
|
@@ -37109,6 +37302,29 @@ var init_agents = __esm(() => {
|
|
|
37109
37302
|
graph = addEdge2(graph, tid, sinkId, "flows_to");
|
|
37110
37303
|
}
|
|
37111
37304
|
}
|
|
37305
|
+
for (const f of files.filter((x) => /\.php$/i.test(x))) {
|
|
37306
|
+
let code;
|
|
37307
|
+
try {
|
|
37308
|
+
code = readFileSync18(f, "utf8");
|
|
37309
|
+
} catch {
|
|
37310
|
+
continue;
|
|
37311
|
+
}
|
|
37312
|
+
for (const rc of detectRouteConfusion(code, f).slice(0, 50)) {
|
|
37313
|
+
const hyp = makeHypothesis({
|
|
37314
|
+
type: rc.category,
|
|
37315
|
+
target: `${f}:${rc.line}`,
|
|
37316
|
+
evidence: rc.detail,
|
|
37317
|
+
confidence: rc.severity === "high" ? 0.7 : 0.5,
|
|
37318
|
+
required_validation: "confirm reachability of the dispatch/route with attacker-controlled input",
|
|
37319
|
+
potential_impact: "route confusion / authorization bypass"
|
|
37320
|
+
});
|
|
37321
|
+
hypotheses.push(hyp);
|
|
37322
|
+
findings += 1;
|
|
37323
|
+
const nodeId = `route:${f}:${rc.line}:${rc.type}`;
|
|
37324
|
+
graph = upsertNode(graph, nodeId, "route", { file: f, line: rc.line, type: rc.type, hypothesis: hyp.id });
|
|
37325
|
+
graph = addEdge2(graph, tid, nodeId, "routes_to");
|
|
37326
|
+
}
|
|
37327
|
+
}
|
|
37112
37328
|
return { ok: true, output: { files: files.length, findings }, hypotheses, graph };
|
|
37113
37329
|
}
|
|
37114
37330
|
};
|
|
@@ -38044,6 +38260,13 @@ var init_enterprise_benchmark = __esm(() => {
|
|
|
38044
38260
|
expected_vulns: 2,
|
|
38045
38261
|
expected_sinks: ["sql_execution", "command_execution"],
|
|
38046
38262
|
safe_tokens: ["prepareStatement", "PreparedStatement"]
|
|
38263
|
+
},
|
|
38264
|
+
{
|
|
38265
|
+
id: "wp-batch-route",
|
|
38266
|
+
dir: join17(ROOT10, "wp-batch-route"),
|
|
38267
|
+
expected_vulns: 1,
|
|
38268
|
+
expected_sinks: ["Route confusion"],
|
|
38269
|
+
safe_tokens: []
|
|
38047
38270
|
}
|
|
38048
38271
|
];
|
|
38049
38272
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.62",
|
|
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": {
|