blitzstrike 2.4.22 → 2.4.24
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 +30 -0
- package/bench/corpus/cb-crlf-safe.md +9 -0
- package/bench/corpus/cb-crlf-vuln.md +9 -0
- package/bench/corpus/cb-deser-safe.md +9 -0
- package/bench/corpus/cb-deser-vuln.md +11 -0
- package/bench/corpus/cb-massassign-safe.md +9 -0
- package/bench/corpus/cb-massassign-vuln.md +9 -0
- package/bench/corpus/cb-pathconf-safe.md +10 -0
- package/bench/corpus/cb-pathconf-vuln.md +10 -0
- package/bench/corpus/cb-proto-safe.md +8 -0
- package/bench/corpus/cb-proto-vuln.md +8 -0
- package/bench/corpus/cb-ssrf-safe.md +9 -0
- package/bench/corpus/cb-ssrf-vuln.md +10 -0
- package/bench/corpus/cb-ssti-safe.md +8 -0
- package/bench/corpus/cb-ssti-vuln.md +8 -0
- package/bench/corpus/cb-typejuggle-safe.md +9 -0
- package/bench/corpus/cb-typejuggle-vuln.md +9 -0
- package/bench/corpus/cb-xxe-safe.md +11 -0
- package/bench/corpus/cb-xxe-vuln.md +10 -0
- package/bench/corpus/rc-batch-safe.md +9 -0
- package/bench/corpus/rc-batch-vuln.md +9 -0
- package/bench/corpus/rc-dispatch-safe.md +10 -0
- package/bench/corpus/rc-dispatch-vuln.md +9 -0
- package/bench/corpus/rc-include-safe.md +10 -0
- package/bench/corpus/rc-include-vuln.md +9 -0
- package/bench/corpus/rc-method-safe.md +10 -0
- package/bench/corpus/rc-method-vuln.md +10 -0
- package/bench/corpus/rc-normalize-safe.md +10 -0
- package/bench/corpus/rc-normalize-vuln.md +10 -0
- package/dist/index.js +87 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ 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
|
+
## [2.4.24] — 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Fixed — LLM finding-lifecycle loop (opencode "repeated failures")
|
|
11
|
+
|
|
12
|
+
- `finding_transition` was STATELESS (returned `{legal: bool}` for a status
|
|
13
|
+
string), so an agent calling it to "advance a finding" saw no state change
|
|
14
|
+
and looped, tripping opencode's "Continue after repeated failures" gate.
|
|
15
|
+
- Now STATEFUL: pass the full finding JSON + target status and it returns the
|
|
16
|
+
UPDATED finding (detected->triaged->hypothesis->validating->confirmed),
|
|
17
|
+
with the legal next states, a clear "illegal transition" error, and an
|
|
18
|
+
explicit TERMINAL-state hint.
|
|
19
|
+
- `confidence_score` response now carries a "deterministic — do not retry"
|
|
20
|
+
note and points to strike_resolve for stateful advancement.
|
|
21
|
+
- Added a FINDING LIFECYCLE directive to the system prompt (finding_create ->
|
|
22
|
+
strike_verify -> strike_resolve; never loop stateless tools).
|
|
23
|
+
|
|
24
|
+
## [2.4.23] — 2026-09-14
|
|
25
|
+
|
|
26
|
+
### Precision — false-positive reduction + per-detector benchmark
|
|
27
|
+
|
|
28
|
+
- Fixed 6 false-positive patterns in the heuristic detectors (complex-bugs +
|
|
29
|
+
route-confusion): canonicalized include (`basename`/`realpath`), whitelist
|
|
30
|
+
lookup (`$allowed[$p]`), batch-forwarding WITH an auth re-check, anchored
|
|
31
|
+
validation regex (`preg_match('/^[a-z]+$/')`), constrained SSRF (fixed
|
|
32
|
+
`scheme://` host literal), and URL literals in the path-confusion detector.
|
|
33
|
+
- Extended the benchmark to measure per-detector precision/recall/F1 (taint +
|
|
34
|
+
complex_bugs + route_confusion) with 28 new labelled fixtures.
|
|
35
|
+
- Corpus now at 137 cases: **precision 1.0 / recall 1.0 / F1 1.0, 0 FP, 0 FN**
|
|
36
|
+
across all three detectors.
|
|
37
|
+
|
|
8
38
|
## [2.4.22] — 2026-09-14
|
|
9
39
|
|
|
10
40
|
### Added — on-disk report persistence (reports/ folder)
|
package/dist/index.js
CHANGED
|
@@ -55630,6 +55630,8 @@ var DYNAMIC_METHOD_RE = /(->|::)\s*\$[A-Za-z_]/g;
|
|
|
55630
55630
|
var BATCH_LOOP_RE = /\bforeach\s*\([^)]*(requests|batch|sub_requests|operations)\b/gi;
|
|
55631
55631
|
var ROUTE_MATCH_RE = /\b(preg_match|preg_match_all|strpos|stripos|str_contains)\s*\(/i;
|
|
55632
55632
|
var NORMALIZERS = /\b(strtolower|strtoupper|rtrim|urldecode|rawurldecode|parse_url|normalize|canonical)\b/i;
|
|
55633
|
+
var WHITELIST_LOOKUP = /\$[A-Za-z_]\w*\s*\[\s*\$[A-Za-z_]\w*\s*\]/;
|
|
55634
|
+
var CANONICALIZE = /\b(basename|realpath|normalize|canonical|resolve|toRealPath)\s*\(/i;
|
|
55633
55635
|
function detectDynamicDispatch(code, file, out) {
|
|
55634
55636
|
const ls = lines(code);
|
|
55635
55637
|
DYNAMIC_DISPATCH_RE.lastIndex = 0;
|
|
@@ -55641,6 +55643,8 @@ function detectDynamicDispatch(code, file, out) {
|
|
|
55641
55643
|
`);
|
|
55642
55644
|
if (!userControlled(l) && !userControlled(scope))
|
|
55643
55645
|
continue;
|
|
55646
|
+
if (WHITELIST_LOOKUP.test(l) || WHITELIST_LOOKUP.test(scope))
|
|
55647
|
+
continue;
|
|
55644
55648
|
out.push({
|
|
55645
55649
|
file,
|
|
55646
55650
|
line: ln,
|
|
@@ -55663,6 +55667,8 @@ function detectDynamicMethod(code, file, out) {
|
|
|
55663
55667
|
`);
|
|
55664
55668
|
if (!userControlled(l) && !userControlled(scope))
|
|
55665
55669
|
continue;
|
|
55670
|
+
if (WHITELIST_LOOKUP.test(l) || WHITELIST_LOOKUP.test(scope))
|
|
55671
|
+
continue;
|
|
55666
55672
|
out.push({
|
|
55667
55673
|
file,
|
|
55668
55674
|
line: ln,
|
|
@@ -55685,6 +55691,11 @@ function detectDynamicInclude(code, file, out) {
|
|
|
55685
55691
|
`);
|
|
55686
55692
|
if (!userControlled(l) && !userControlled(scope))
|
|
55687
55693
|
continue;
|
|
55694
|
+
if (CANONICALIZE.test(scope + `
|
|
55695
|
+
` + l))
|
|
55696
|
+
continue;
|
|
55697
|
+
if (WHITELIST_LOOKUP.test(l) || WHITELIST_LOOKUP.test(scope))
|
|
55698
|
+
continue;
|
|
55688
55699
|
out.push({
|
|
55689
55700
|
file,
|
|
55690
55701
|
line: ln,
|
|
@@ -55707,14 +55718,16 @@ function detectBatchForwarding(code, file, out) {
|
|
|
55707
55718
|
if (!forwards)
|
|
55708
55719
|
continue;
|
|
55709
55720
|
const rechecks = /\b(current_user_can|permission_callback|check_ajax_referer|authorize|authorise|is_user_logged_in)\b/i.test(body);
|
|
55721
|
+
if (rechecks)
|
|
55722
|
+
continue;
|
|
55710
55723
|
out.push({
|
|
55711
55724
|
file,
|
|
55712
55725
|
line: ln,
|
|
55713
55726
|
type: "batch_forwarding",
|
|
55714
55727
|
category: "Route confusion (batch/proxy forwarding)",
|
|
55715
|
-
severity:
|
|
55728
|
+
severity: "high",
|
|
55716
55729
|
evidence: lines(code)[ln - 1]?.trim() ?? "",
|
|
55717
|
-
detail:
|
|
55730
|
+
detail: "Batch/proxy loop forwards to inner handlers with no per-route authorization re-check — an attacker may reach a protected handler."
|
|
55718
55731
|
});
|
|
55719
55732
|
}
|
|
55720
55733
|
}
|
|
@@ -55730,6 +55743,8 @@ function detectRouteNormalization(code, file, out) {
|
|
|
55730
55743
|
continue;
|
|
55731
55744
|
if (NORMALIZERS.test(l) || NORMALIZERS.test(scope))
|
|
55732
55745
|
continue;
|
|
55746
|
+
if (/preg_match\s*\(\s*["'][^"']*\^[^"']*\$[^"']*["']/.test(l))
|
|
55747
|
+
continue;
|
|
55733
55748
|
out.push({
|
|
55734
55749
|
file,
|
|
55735
55750
|
line: i + 1,
|
|
@@ -55927,6 +55942,12 @@ function detectPathConfusion(code, file, out) {
|
|
|
55927
55942
|
if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
|
|
55928
55943
|
continue;
|
|
55929
55944
|
if (/\b(basename|realpath|canonicalPath|getCanonicalPath|normalize|resolve|toRealPath)\s*\(/i.test(scope + `
|
|
55945
|
+
` + l))
|
|
55946
|
+
continue;
|
|
55947
|
+
if (/\$[A-Za-z_]\w*\s*\[\s*\$[A-Za-z_]\w*\s*\]/.test(scope + `
|
|
55948
|
+
` + l))
|
|
55949
|
+
continue;
|
|
55950
|
+
if (/["'][^"']*:\/\/[^"']*["']/.test(scope + `
|
|
55930
55951
|
` + l))
|
|
55931
55952
|
continue;
|
|
55932
55953
|
out.push({
|
|
@@ -55952,6 +55973,9 @@ function detectSsrF(code, file, out) {
|
|
|
55952
55973
|
if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
|
|
55953
55974
|
continue;
|
|
55954
55975
|
if (/(allowlist|whitelist|in_array|preg_match|parse_url|startsWith\(["']https?|endsWith\(["']|basename|realpath|normalize)/i.test(scope + `
|
|
55976
|
+
` + l))
|
|
55977
|
+
continue;
|
|
55978
|
+
if (/["'][^"']*:\/\/[^"']*["']/.test(scope + `
|
|
55955
55979
|
` + l))
|
|
55956
55980
|
continue;
|
|
55957
55981
|
out.push({
|
|
@@ -56088,11 +56112,12 @@ function parseCorpusFile(path) {
|
|
|
56088
56112
|
let language = "php";
|
|
56089
56113
|
let vulnerable = true;
|
|
56090
56114
|
let sinkType;
|
|
56115
|
+
let detector;
|
|
56091
56116
|
let code = "";
|
|
56092
56117
|
let inCode = false;
|
|
56093
56118
|
let codeLang = "";
|
|
56094
56119
|
for (const line of lines) {
|
|
56095
|
-
const m = line.match(/^\s*-\s*(id|language|vulnerable|sink_type)\s*:\s*(.+)$/i);
|
|
56120
|
+
const m = line.match(/^\s*-\s*(id|language|vulnerable|sink_type|detector)\s*:\s*(.+)$/i);
|
|
56096
56121
|
if (!inCode && m) {
|
|
56097
56122
|
const key = m[1].toLowerCase();
|
|
56098
56123
|
const val = m[2].trim();
|
|
@@ -56104,6 +56129,8 @@ function parseCorpusFile(path) {
|
|
|
56104
56129
|
vulnerable = val === "true";
|
|
56105
56130
|
else if (key === "sink_type")
|
|
56106
56131
|
sinkType = val;
|
|
56132
|
+
else if (key === "detector")
|
|
56133
|
+
detector = val;
|
|
56107
56134
|
continue;
|
|
56108
56135
|
}
|
|
56109
56136
|
const start = line.match(/^```(\w*)/);
|
|
@@ -56120,7 +56147,7 @@ function parseCorpusFile(path) {
|
|
|
56120
56147
|
}
|
|
56121
56148
|
if (!id)
|
|
56122
56149
|
id = path.split("/").pop().replace(/\.md$/, "");
|
|
56123
|
-
return { id, language: language || codeLang || "php", vulnerable, sink_type: sinkType, code: code.trim() };
|
|
56150
|
+
return { id, language: language || codeLang || "php", vulnerable, sink_type: sinkType, detector, code: code.trim() };
|
|
56124
56151
|
}
|
|
56125
56152
|
function loadCorpus() {
|
|
56126
56153
|
let entries = [];
|
|
@@ -56138,6 +56165,18 @@ function loadCorpus() {
|
|
|
56138
56165
|
return entries;
|
|
56139
56166
|
}
|
|
56140
56167
|
function detect(entry) {
|
|
56168
|
+
if (entry.detector === "route_confusion") {
|
|
56169
|
+
const f = detectRouteConfusion(entry.code, "bench." + entry.language);
|
|
56170
|
+
if (f.length > 0)
|
|
56171
|
+
return { detected: true, sinkKind: f[0].type };
|
|
56172
|
+
return { detected: false };
|
|
56173
|
+
}
|
|
56174
|
+
if (entry.detector === "complex_bugs") {
|
|
56175
|
+
const f = detectComplexBugs(entry.code, "bench." + entry.language);
|
|
56176
|
+
if (f.length > 0)
|
|
56177
|
+
return { detected: true, sinkKind: f[0].type };
|
|
56178
|
+
return { detected: false };
|
|
56179
|
+
}
|
|
56141
56180
|
const lang = entry.language;
|
|
56142
56181
|
if (lang === "php") {
|
|
56143
56182
|
const tmp = join15(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
@@ -56169,6 +56208,19 @@ function runBenchmark() {
|
|
|
56169
56208
|
const detectionRate = tp + fn > 0 ? tp / (tp + fn) : 0;
|
|
56170
56209
|
const falsePositiveRate = fp + tn > 0 ? fp / (fp + tn) : 0;
|
|
56171
56210
|
const precision = tp + fp > 0 ? tp / (tp + fp) : 0;
|
|
56211
|
+
const byDetector = {};
|
|
56212
|
+
for (const r of results) {
|
|
56213
|
+
const d = r.entry.detector ?? "taint";
|
|
56214
|
+
byDetector[d] = byDetector[d] ?? { tp: 0, tn: 0, fp: 0, fn: 0, total: 0 };
|
|
56215
|
+
byDetector[d][r.outcome] += 1;
|
|
56216
|
+
byDetector[d].total += 1;
|
|
56217
|
+
}
|
|
56218
|
+
const detectorMetrics = Object.fromEntries(Object.entries(byDetector).map(([d, m]) => {
|
|
56219
|
+
const p = m.tp + m.fp > 0 ? m.tp / (m.tp + m.fp) : 0;
|
|
56220
|
+
const rec = m.tp + m.fn > 0 ? m.tp / (m.tp + m.fn) : 0;
|
|
56221
|
+
const f1 = p + rec > 0 ? 2 * p * rec / (p + rec) : 0;
|
|
56222
|
+
return [d, { ...m, precision: Number(p.toFixed(3)), recall: Number(rec.toFixed(3)), f1: Number(f1.toFixed(3)) }];
|
|
56223
|
+
}));
|
|
56172
56224
|
return {
|
|
56173
56225
|
total_cases: total,
|
|
56174
56226
|
tp,
|
|
@@ -56178,7 +56230,8 @@ function runBenchmark() {
|
|
|
56178
56230
|
detection_rate: Number(detectionRate.toFixed(3)),
|
|
56179
56231
|
false_positive_rate: Number(falsePositiveRate.toFixed(3)),
|
|
56180
56232
|
precision: Number(precision.toFixed(3)),
|
|
56181
|
-
|
|
56233
|
+
by_detector: detectorMetrics,
|
|
56234
|
+
results: results.map((r) => ({ id: r.entry.id, language: r.entry.language, detector: r.entry.detector ?? "taint", outcome: r.outcome, sink: r.sink_kind }))
|
|
56182
56235
|
};
|
|
56183
56236
|
}
|
|
56184
56237
|
|
|
@@ -56414,7 +56467,7 @@ function createServer() {
|
|
|
56414
56467
|
}, {
|
|
56415
56468
|
instructions: "Blitz Strike — deterministic security-audit toolbelt. You reason; its tools do the deterministic work (scan, taint, verify, evidence).\\n\\n" + "FIRST ACTION (when the user says test / scan / audit / engagement / pentest <URL or path>):\\n" + "- Call run_engagement(target) — or run_autonomous(target) to also get escalation paths. ONE call runs the whole engagement (recon -> sweep -> checks -> report).\\n" + "- NEVER do manual live_recon / blitz_scan first, and never fall back to manual probing. Manual tools are ONLY for follow-up AFTER run_engagement, or when the user names a specific sub-step.\\n\\n" + "RULES (never violate):\\n" + `- A hit is a HYPOTHESIS; live verification (marker + negative control) is the verdict. Never report unverified.
|
|
56416
56469
|
` + `- run_engagement / run_autonomous return status DETECTED (NOT "complete") — that is the signal to VERIFY. After detection you MUST strike_verify (or verify_file_read for CWE-22, drive_devtools for DOM/JS precision) on each finding, then generate_report. Detection without verification is NOT a deliverable.
|
|
56417
|
-
` + "- scope_check is intel-only, never gates. No scope = proceed (repo disclaimer carries responsibility). No authorization gate — hunt the target you are given.\\n" + "- strike_verify + live checks AUTO-attempt WAF bypass. Don't report 'needs bypass' without re-probing.\\n" + "- NEVER ask 'should I continue?' / 'say the word' / 'want me to pursue' / 'next?' — that is a hard violation. Execute the full chain autonomously until COMPLETE. Pause only: destructive/DoS consent, external block (OTP/cred), or COMPLETE.\\n" + "- One hard stop: malicious/third-party infra ('find the actor') -> static + takedown only.\\n\\n" + "CHAIN DECISIONS (a confirmed finding is the START, not the end):\\n" + "- For each confirmed finding, enumerate follow-ups with list_chains + chain_links: ESCALATE (what it becomes), CHAIN (compose with other findings), BYPASS, PIVOT (new surface now reachable).\\n" + "- Ask the chain-aware question: 'I found X -> what can I NOW read / do / access?' e.g. SQLi -> query the DB for credentials AND for the Werkzeug console PIN / secrets / source; LFI -> read configs, keys, PIN inputs; cred dump -> crack offline -> authenticate -> privileged actions.\\n" + "- Every follow-up is a HYPOTHESIS — verify each before it enters the report. Don't stop at 'found SQLi'; follow where the chain leads.\\n" + "- EXECUTE follow-ups NOW, not as 'next phase': any leaked source/error -> scan_leaked_source(text) to enumerate sinks (eval/exec/SECRET_KEY/creds) then exploit each; dumped hashes -> crack_hash(hash) now; hidden-content IDOR -> test the direct route. A 'next chain' you write is a TODO — run it, never leave it as a recommendation.\\n\\n" + `DELIVERABLE + PERSISTENCE (recon is INPUT, not output):
|
|
56470
|
+
` + "- scope_check is intel-only, never gates. No scope = proceed (repo disclaimer carries responsibility). No authorization gate — hunt the target you are given.\\n" + "- strike_verify + live checks AUTO-attempt WAF bypass. Don't report 'needs bypass' without re-probing.\\n" + "- NEVER ask 'should I continue?' / 'say the word' / 'want me to pursue' / 'next?' — that is a hard violation. Execute the full chain autonomously until COMPLETE. Pause only: destructive/DoS consent, external block (OTP/cred), or COMPLETE.\\n" + "- One hard stop: malicious/third-party infra ('find the actor') -> static + takedown only.\\n" + "- FINDING LIFECYCLE (stateful — never loop stateless tools): advance findings via finding_create -> strike_verify -> strike_resolve. finding_transition now takes a FULL finding JSON + target status and RETURNS the updated finding (pass the object, never a status string); a bad move returns 'illegal transition' + the legal next states. confidence_score is DETERMINISTIC (same factors -> same score) — call it ONCE, never retry; it changes nothing.\\n\\n" + "CHAIN DECISIONS (a confirmed finding is the START, not the end):\\n" + "- For each confirmed finding, enumerate follow-ups with list_chains + chain_links: ESCALATE (what it becomes), CHAIN (compose with other findings), BYPASS, PIVOT (new surface now reachable).\\n" + "- Ask the chain-aware question: 'I found X -> what can I NOW read / do / access?' e.g. SQLi -> query the DB for credentials AND for the Werkzeug console PIN / secrets / source; LFI -> read configs, keys, PIN inputs; cred dump -> crack offline -> authenticate -> privileged actions.\\n" + "- Every follow-up is a HYPOTHESIS — verify each before it enters the report. Don't stop at 'found SQLi'; follow where the chain leads.\\n" + "- EXECUTE follow-ups NOW, not as 'next phase': any leaked source/error -> scan_leaked_source(text) to enumerate sinks (eval/exec/SECRET_KEY/creds) then exploit each; dumped hashes -> crack_hash(hash) now; hidden-content IDOR -> test the direct route. A 'next chain' you write is a TODO — run it, never leave it as a recommendation.\\n\\n" + `DELIVERABLE + PERSISTENCE (recon is INPUT, not output):
|
|
56418
56471
|
` + `- The deliverable is a HackerOne-grade FINDING report via generate_report, NEVER a hand-written recon/surface map. Recon is input; confirmed findings are the output.
|
|
56419
56472
|
` + `- After recon, VERIFY every lead before it enters the report: strike_verify (marker + negative control) for HTTP, browser_validate for DOM/JS. A lead is a HYPOTHESIS until verified.
|
|
56420
56473
|
` + `- A hard block (Cloudflare/WAF/IP-block/captcha) is NOT the end: mark the finding blocked + note the required vantage (residential / authorized / correct-SNI / --resolve direct-to-origin), then CONTINUE to the next lead (origin bypass, non-prod cluster, mobile APK, subdomain).
|
|
@@ -57377,19 +57430,39 @@ ${statusLine}` : statusLine },
|
|
|
57377
57430
|
};
|
|
57378
57431
|
});
|
|
57379
57432
|
server.registerTool("finding_transition", {
|
|
57380
|
-
title: "Advance a finding lifecycle",
|
|
57381
|
-
description: "FINDINGS: advance a
|
|
57433
|
+
title: "Advance a finding lifecycle (STATEFUL)",
|
|
57434
|
+
description: "FINDINGS: advance a canonical Finding through its lifecycle (detected->triaged->hypothesis->validating->confirmed) and RETURN the updated finding. STATEFUL — pass the FULL finding JSON (from finding_create / run_engagement), not a status string. Rejects illegal transitions with the legal next states. Confirmed is terminal. To confirm FROM a live verdict use strike_resolve instead.",
|
|
57382
57435
|
inputSchema: {
|
|
57383
|
-
|
|
57436
|
+
finding: string2().describe("JSON of the canonical Finding object (from finding_create / run_engagement)"),
|
|
57384
57437
|
to: string2().describe("Target status (triaged/hypothesis/validating/confirmed/false_positive/rejected/blocked/out_of_scope)")
|
|
57385
57438
|
}
|
|
57386
|
-
}, async ({
|
|
57387
|
-
|
|
57388
|
-
|
|
57439
|
+
}, async ({ finding, to }) => {
|
|
57440
|
+
let f;
|
|
57441
|
+
try {
|
|
57442
|
+
f = JSON.parse(finding);
|
|
57443
|
+
} catch {
|
|
57444
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid finding JSON — pass the full Finding object, not a status string" }) }] };
|
|
57445
|
+
}
|
|
57446
|
+
const from = f.status ?? "detected";
|
|
57447
|
+
if (!canTransition(from, to)) {
|
|
57448
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `illegal transition ${from} -> ${to}`, from, legal_next: LIFECYCLE_TRANSITIONS[from] ?? [] }) }] };
|
|
57449
|
+
}
|
|
57450
|
+
const updated = transition(f, to);
|
|
57451
|
+
const terminal = (LIFECYCLE_TRANSITIONS[updated.status] ?? []).length === 0;
|
|
57452
|
+
return {
|
|
57453
|
+
content: [{
|
|
57454
|
+
type: "text",
|
|
57455
|
+
text: JSON.stringify({
|
|
57456
|
+
...updated,
|
|
57457
|
+
transition: `${from} -> ${updated.status}`,
|
|
57458
|
+
...terminal ? { hint: "TERMINAL state reached. A 'confirmed' claim must be evidence-backed (strike_verify -> strike_resolve); otherwise treat it as unverified. Then generate_report." } : { next: LIFECYCLE_TRANSITIONS[updated.status] ?? [] }
|
|
57459
|
+
})
|
|
57460
|
+
}]
|
|
57461
|
+
};
|
|
57389
57462
|
});
|
|
57390
57463
|
server.registerTool("confidence_score", {
|
|
57391
57464
|
title: "Compute deterministic confidence",
|
|
57392
|
-
description: "FINDINGS: compute a deterministic weighted confidence score (static/data-flow/reachability/preconditions/validation/negative-control). Never an AI opinion.",
|
|
57465
|
+
description: "FINDINGS: compute a deterministic weighted confidence score (static/data-flow/reachability/preconditions/validation/negative-control). Never an AI opinion. DETERMINISTIC — identical inputs give the identical score, so never retry with the same factors. The score does NOT change a finding; use strike_resolve (finding + verdict) to attach a confidence + advance the lifecycle.",
|
|
57393
57466
|
inputSchema: {
|
|
57394
57467
|
static_analysis: boolean2().optional(),
|
|
57395
57468
|
data_flow: boolean2().optional(),
|
|
@@ -57400,7 +57473,7 @@ ${statusLine}` : statusLine },
|
|
|
57400
57473
|
}
|
|
57401
57474
|
}, async (factors) => {
|
|
57402
57475
|
const score = computeConfidence(factors);
|
|
57403
|
-
return { content: [{ type: "text", text: JSON.stringify({ score, level: confidenceLevel(score) }) }] };
|
|
57476
|
+
return { content: [{ type: "text", text: JSON.stringify({ score, level: confidenceLevel(score), note: "deterministic — same factors = same score; do not retry. Attach to a finding via strike_resolve." }) }] };
|
|
57404
57477
|
});
|
|
57405
57478
|
server.registerTool("confidence_weights", {
|
|
57406
57479
|
title: "Get/set confidence weights",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.24",
|
|
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": {
|