blitzstrike 1.0.49 → 1.0.52
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 +41 -0
- package/dist/index.js +417 -20
- package/package.json +1 -1
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.52] — 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Added — Autonomous Security Engine (phase 12: browser automation agent)
|
|
11
|
+
|
|
12
|
+
- **Browser Automation Agent** (`src/browser-agent.ts`, §16):
|
|
13
|
+
- Session-based (one persistent page) so multi-step flows work.
|
|
14
|
+
- Ops: `open`/`navigate`/`click`/`type`/`evaluate`/`screenshot`/`back`/`forward`/
|
|
15
|
+
`get_cookie`/`set_cookie`, plus `detectAuthForm()` and `browserDiagnostics()`.
|
|
16
|
+
- Graceful degradation — every op reports `unavailable` when `playwright-core`
|
|
17
|
+
(optional peer) is missing, never throws.
|
|
18
|
+
- New `@browser` mission agent (URL-scope-gated) — detects login forms and JS
|
|
19
|
+
runtime errors and emits auth-flow hypotheses.
|
|
20
|
+
- New MCP tool `browser_agent` (session ops + detect_auth_form + diagnostics + close).
|
|
21
|
+
|
|
22
|
+
## [1.0.51] — 2026-09-13
|
|
23
|
+
|
|
24
|
+
### Added — Autonomous Security Engine (phase 11: adversarial reviewer)
|
|
25
|
+
|
|
26
|
+
- **Adversarial Reviewer** (`src/mission/reviewer.ts`, §15):
|
|
27
|
+
- `reviewFinding()` — challenges a hypothesis with the ten §15 questions
|
|
28
|
+
(reachability, auth required, authorization elsewhere, middleware, attacker
|
|
29
|
+
control, sink reachability, caching, reproducibility, negative control,
|
|
30
|
+
impact accuracy), each a deterministic check against evidence + Security Graph.
|
|
31
|
+
- Returns CONFIRMED / REJECTED / NEEDS_MORE_EVIDENCE.
|
|
32
|
+
- `reviewValidated()` — reviews every validated hypothesis in a mission.
|
|
33
|
+
- `@reviewer` now uses the full §15 review (rejects on any hard fail; demands
|
|
34
|
+
more evidence when supporting signals are too weak).
|
|
35
|
+
|
|
36
|
+
## [1.0.50] — 2026-09-13
|
|
37
|
+
|
|
38
|
+
### Added — Autonomous Security Engine (phase 10: cross-agent memory)
|
|
39
|
+
|
|
40
|
+
- **Cross-agent Memory** (`src/mission/memory.ts`, §20):
|
|
41
|
+
- `persistMissionMemory()` — persists confirmed findings (verified),
|
|
42
|
+
rejected findings, and failed approaches (with `future_policy`) into the
|
|
43
|
+
long-term memory store; pending hypotheses are NOT persisted (unverified).
|
|
44
|
+
- `recallForTarget()` — retrieves memory relevant to a target, to seed a
|
|
45
|
+
fresh mission.
|
|
46
|
+
- Mission Control's `finalize` now persists reusable knowledge and records the
|
|
47
|
+
saved/deduped count in `memory_refs`.
|
|
48
|
+
|
|
8
49
|
## [1.0.49] — 2026-09-13
|
|
9
50
|
|
|
10
51
|
### Added — Autonomous Security Engine (phase 9: failure recovery)
|
package/dist/index.js
CHANGED
|
@@ -17722,6 +17722,219 @@ var init_universal_taint = __esm(() => {
|
|
|
17722
17722
|
extensionMap = new Map;
|
|
17723
17723
|
});
|
|
17724
17724
|
|
|
17725
|
+
// src/browser-agent.ts
|
|
17726
|
+
async function ensureChromium2() {
|
|
17727
|
+
if (chromiumCache2)
|
|
17728
|
+
return chromiumCache2;
|
|
17729
|
+
try {
|
|
17730
|
+
chromiumCache2 = await import("playwright-core");
|
|
17731
|
+
return chromiumCache2;
|
|
17732
|
+
} catch {
|
|
17733
|
+
return null;
|
|
17734
|
+
}
|
|
17735
|
+
}
|
|
17736
|
+
async function ensureSession() {
|
|
17737
|
+
if (page)
|
|
17738
|
+
return { available: true };
|
|
17739
|
+
const chr = await ensureChromium2();
|
|
17740
|
+
if (!chr)
|
|
17741
|
+
return { available: false, reason: "playwright-core not installed (optional peer dependency)" };
|
|
17742
|
+
try {
|
|
17743
|
+
browser = await chr.chromium.launch({ headless: true });
|
|
17744
|
+
page = await browser.newPage();
|
|
17745
|
+
page.on("console", (m) => {
|
|
17746
|
+
const msg = m;
|
|
17747
|
+
if (msg.type() === "error")
|
|
17748
|
+
consoleErrors.push(msg.text());
|
|
17749
|
+
});
|
|
17750
|
+
page.on("pageerror", (e) => pageErrors.push(String(e)));
|
|
17751
|
+
return { available: true };
|
|
17752
|
+
} catch (e) {
|
|
17753
|
+
return { available: false, reason: e instanceof Error ? e.message : String(e) };
|
|
17754
|
+
}
|
|
17755
|
+
}
|
|
17756
|
+
function unavailable2(op, reason) {
|
|
17757
|
+
return { op, ok: false, unavailable: true, detail: reason };
|
|
17758
|
+
}
|
|
17759
|
+
function fail(op, e) {
|
|
17760
|
+
return { op, ok: false, unavailable: false, detail: e instanceof Error ? e.message : String(e) };
|
|
17761
|
+
}
|
|
17762
|
+
async function browserOpen(url) {
|
|
17763
|
+
const s = await ensureSession();
|
|
17764
|
+
if (!s.available || !page)
|
|
17765
|
+
return unavailable2("open", s.reason ?? "unavailable");
|
|
17766
|
+
try {
|
|
17767
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 20000 });
|
|
17768
|
+
return { op: "open", ok: true, unavailable: false, detail: `opened ${redactSecrets(page.url())}` };
|
|
17769
|
+
} catch (e) {
|
|
17770
|
+
return fail("open", e);
|
|
17771
|
+
}
|
|
17772
|
+
}
|
|
17773
|
+
async function browserNavigate(url) {
|
|
17774
|
+
return browserOpen(url);
|
|
17775
|
+
}
|
|
17776
|
+
async function browserClick(selector) {
|
|
17777
|
+
const s = await ensureSession();
|
|
17778
|
+
if (!s.available || !page)
|
|
17779
|
+
return unavailable2("click", s.reason ?? "unavailable");
|
|
17780
|
+
try {
|
|
17781
|
+
await page.click(selector);
|
|
17782
|
+
return { op: "click", ok: true, unavailable: false, detail: `clicked ${selector}` };
|
|
17783
|
+
} catch (e) {
|
|
17784
|
+
return fail("click", e);
|
|
17785
|
+
}
|
|
17786
|
+
}
|
|
17787
|
+
async function browserType(selector, text) {
|
|
17788
|
+
const s = await ensureSession();
|
|
17789
|
+
if (!s.available || !page)
|
|
17790
|
+
return unavailable2("type", s.reason ?? "unavailable");
|
|
17791
|
+
try {
|
|
17792
|
+
await page.fill(selector, text);
|
|
17793
|
+
return { op: "type", ok: true, unavailable: false, detail: `typed into ${selector}` };
|
|
17794
|
+
} catch (e) {
|
|
17795
|
+
return fail("type", e);
|
|
17796
|
+
}
|
|
17797
|
+
}
|
|
17798
|
+
async function browserEvaluate(js) {
|
|
17799
|
+
const s = await ensureSession();
|
|
17800
|
+
if (!s.available || !page)
|
|
17801
|
+
return unavailable2("evaluate", s.reason ?? "unavailable");
|
|
17802
|
+
try {
|
|
17803
|
+
const value = await page.evaluate((code) => {
|
|
17804
|
+
const fn = new Function(`"use strict"; return (${code})`)();
|
|
17805
|
+
return typeof fn === "function" ? fn() : fn;
|
|
17806
|
+
}, js);
|
|
17807
|
+
return { op: "evaluate", ok: true, unavailable: false, detail: "evaluated", value };
|
|
17808
|
+
} catch (e) {
|
|
17809
|
+
return fail("evaluate", e);
|
|
17810
|
+
}
|
|
17811
|
+
}
|
|
17812
|
+
async function browserScreenshot(path) {
|
|
17813
|
+
const s = await ensureSession();
|
|
17814
|
+
if (!s.available || !page)
|
|
17815
|
+
return unavailable2("screenshot", s.reason ?? "unavailable");
|
|
17816
|
+
try {
|
|
17817
|
+
const buf = await page.screenshot(path ? { path } : {});
|
|
17818
|
+
return { op: "screenshot", ok: true, unavailable: false, detail: path ? `saved ${path}` : "captured (in-memory)", value: path ? undefined : buf.byteLength };
|
|
17819
|
+
} catch (e) {
|
|
17820
|
+
return fail("screenshot", e);
|
|
17821
|
+
}
|
|
17822
|
+
}
|
|
17823
|
+
async function browserBack() {
|
|
17824
|
+
const s = await ensureSession();
|
|
17825
|
+
if (!s.available || !page)
|
|
17826
|
+
return unavailable2("back", s.reason ?? "unavailable");
|
|
17827
|
+
try {
|
|
17828
|
+
await page.goBack();
|
|
17829
|
+
return { op: "back", ok: true, unavailable: false, detail: `back to ${redactSecrets(page.url())}` };
|
|
17830
|
+
} catch (e) {
|
|
17831
|
+
return fail("back", e);
|
|
17832
|
+
}
|
|
17833
|
+
}
|
|
17834
|
+
async function browserForward() {
|
|
17835
|
+
const s = await ensureSession();
|
|
17836
|
+
if (!s.available || !page)
|
|
17837
|
+
return unavailable2("forward", s.reason ?? "unavailable");
|
|
17838
|
+
try {
|
|
17839
|
+
await page.goForward();
|
|
17840
|
+
return { op: "forward", ok: true, unavailable: false, detail: `forward to ${redactSecrets(page.url())}` };
|
|
17841
|
+
} catch (e) {
|
|
17842
|
+
return fail("forward", e);
|
|
17843
|
+
}
|
|
17844
|
+
}
|
|
17845
|
+
async function browserGetCookie(name) {
|
|
17846
|
+
const s = await ensureSession();
|
|
17847
|
+
if (!s.available || !page)
|
|
17848
|
+
return unavailable2("get_cookie", s.reason ?? "unavailable");
|
|
17849
|
+
try {
|
|
17850
|
+
const cookies = await page.context().cookies();
|
|
17851
|
+
const hit = cookies.filter((c) => c.name === name);
|
|
17852
|
+
return { op: "get_cookie", ok: true, unavailable: false, detail: hit.length ? "found" : "not found", value: hit };
|
|
17853
|
+
} catch (e) {
|
|
17854
|
+
return fail("get_cookie", e);
|
|
17855
|
+
}
|
|
17856
|
+
}
|
|
17857
|
+
async function browserSetCookie(name, value) {
|
|
17858
|
+
const s = await ensureSession();
|
|
17859
|
+
if (!s.available || !page)
|
|
17860
|
+
return unavailable2("set_cookie", s.reason ?? "unavailable");
|
|
17861
|
+
try {
|
|
17862
|
+
const url = page.url();
|
|
17863
|
+
await page.context().addCookies([{ name, value, url }]);
|
|
17864
|
+
return { op: "set_cookie", ok: true, unavailable: false, detail: `set ${name}` };
|
|
17865
|
+
} catch (e) {
|
|
17866
|
+
return fail("set_cookie", e);
|
|
17867
|
+
}
|
|
17868
|
+
}
|
|
17869
|
+
async function browserClose() {
|
|
17870
|
+
if (browser) {
|
|
17871
|
+
await browser.close().catch(() => {
|
|
17872
|
+
return;
|
|
17873
|
+
});
|
|
17874
|
+
browser = null;
|
|
17875
|
+
page = null;
|
|
17876
|
+
}
|
|
17877
|
+
}
|
|
17878
|
+
async function detectAuthForm() {
|
|
17879
|
+
const s = await ensureSession();
|
|
17880
|
+
if (!s.available || !page)
|
|
17881
|
+
return unavailable2("evaluate", s.reason ?? "unavailable");
|
|
17882
|
+
try {
|
|
17883
|
+
const value = await page.evaluate(() => {
|
|
17884
|
+
const forms = Array.from(document.querySelectorAll("form"));
|
|
17885
|
+
const loginForm = forms.find((f) => /login|signin|auth/i.test(f.action || "") || f.querySelector('input[type="password"]'));
|
|
17886
|
+
if (!loginForm)
|
|
17887
|
+
return { found: false };
|
|
17888
|
+
const userField = loginForm.querySelector('input[type="email"], input[type="text"], input[name*="user"], input[name*="email"], input[name*="login"]');
|
|
17889
|
+
const passField = loginForm.querySelector('input[type="password"]');
|
|
17890
|
+
return {
|
|
17891
|
+
found: true,
|
|
17892
|
+
action: loginForm.action || "(self)",
|
|
17893
|
+
has_user: Boolean(userField),
|
|
17894
|
+
has_pass: Boolean(passField),
|
|
17895
|
+
user_selector: userField ? `input[name="${userField.name || ""}"], input[type="${userField.type}"]` : null,
|
|
17896
|
+
pass_selector: passField ? 'input[type="password"]' : null
|
|
17897
|
+
};
|
|
17898
|
+
});
|
|
17899
|
+
return { op: "evaluate", ok: true, unavailable: false, detail: value.found ? "login form detected" : "no login form", value };
|
|
17900
|
+
} catch (e) {
|
|
17901
|
+
return fail("evaluate", e);
|
|
17902
|
+
}
|
|
17903
|
+
}
|
|
17904
|
+
async function browserAgent(op, args = []) {
|
|
17905
|
+
switch (op) {
|
|
17906
|
+
case "open":
|
|
17907
|
+
return browserOpen(args[0] ?? "");
|
|
17908
|
+
case "navigate":
|
|
17909
|
+
return browserNavigate(args[0] ?? "");
|
|
17910
|
+
case "click":
|
|
17911
|
+
return browserClick(args[0] ?? "");
|
|
17912
|
+
case "type":
|
|
17913
|
+
return browserType(args[0] ?? "", args[1] ?? "");
|
|
17914
|
+
case "evaluate":
|
|
17915
|
+
return browserEvaluate(args[0] ?? "");
|
|
17916
|
+
case "screenshot":
|
|
17917
|
+
return browserScreenshot(args[0] || undefined);
|
|
17918
|
+
case "back":
|
|
17919
|
+
return browserBack();
|
|
17920
|
+
case "forward":
|
|
17921
|
+
return browserForward();
|
|
17922
|
+
case "get_cookie":
|
|
17923
|
+
return browserGetCookie(args[0] ?? "");
|
|
17924
|
+
case "set_cookie":
|
|
17925
|
+
return browserSetCookie(args[0] ?? "", args[1] ?? "");
|
|
17926
|
+
}
|
|
17927
|
+
}
|
|
17928
|
+
function browserDiagnostics() {
|
|
17929
|
+
return { console_errors: [...consoleErrors], page_errors: [...pageErrors] };
|
|
17930
|
+
}
|
|
17931
|
+
var chromiumCache2 = null, browser = null, page = null, consoleErrors, pageErrors;
|
|
17932
|
+
var init_browser_agent = __esm(() => {
|
|
17933
|
+
init_evidence();
|
|
17934
|
+
consoleErrors = [];
|
|
17935
|
+
pageErrors = [];
|
|
17936
|
+
});
|
|
17937
|
+
|
|
17725
17938
|
// src/sync.ts
|
|
17726
17939
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync16, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync3 } from "node:fs";
|
|
17727
17940
|
import { join as join12 } from "node:path";
|
|
@@ -18258,6 +18471,92 @@ var init_planner = __esm(() => {
|
|
|
18258
18471
|
RISK_PENALTY = { low: 0, medium: 0.1, high: 0.2 };
|
|
18259
18472
|
});
|
|
18260
18473
|
|
|
18474
|
+
// src/mission/reviewer.ts
|
|
18475
|
+
function checkAll(h, mission) {
|
|
18476
|
+
const ev = h.evidence.toLowerCase();
|
|
18477
|
+
const sinkNode = mission.graph.nodes.find((n) => n.properties && n.properties.hypothesis === h.id);
|
|
18478
|
+
const reachable = sinkNode ? incoming(mission.graph, sinkNode.id).length > 0 : false;
|
|
18479
|
+
return [
|
|
18480
|
+
{
|
|
18481
|
+
id: "reachable_code",
|
|
18482
|
+
question: "Is the vulnerable code actually reachable?",
|
|
18483
|
+
verdict: reachable ? "pass" : "unknown",
|
|
18484
|
+
note: reachable ? "sink has an incoming flow edge" : "no flow edge in the graph"
|
|
18485
|
+
},
|
|
18486
|
+
{
|
|
18487
|
+
id: "auth_required",
|
|
18488
|
+
question: "Is authentication required?",
|
|
18489
|
+
verdict: /auth-gated|auth_required|login required/i.test(ev) ? "pass" : "unknown",
|
|
18490
|
+
note: /auth-gated|auth_required|login required/i.test(ev) ? "auth gate detected" : "no auth gate signal"
|
|
18491
|
+
},
|
|
18492
|
+
{
|
|
18493
|
+
id: "authz_elsewhere",
|
|
18494
|
+
question: "Is authorization enforced elsewhere?",
|
|
18495
|
+
verdict: /missing_authorization/i.test(h.type) ? "fail" : "unknown",
|
|
18496
|
+
note: /missing_authorization/i.test(h.type) ? "no authorization boundary detected" : "not an authorization finding"
|
|
18497
|
+
},
|
|
18498
|
+
{
|
|
18499
|
+
id: "middleware",
|
|
18500
|
+
question: "Is there middleware protection?",
|
|
18501
|
+
verdict: /sanitized|middleware|waf|firewall/i.test(ev) ? "pass" : "unknown",
|
|
18502
|
+
note: /sanitized|middleware|waf|firewall/i.test(ev) ? "sanitizer/middleware present" : "no protection signal"
|
|
18503
|
+
},
|
|
18504
|
+
{
|
|
18505
|
+
id: "attacker_controlled",
|
|
18506
|
+
question: "Is the input actually attacker-controlled?",
|
|
18507
|
+
verdict: /attacker-controlled|user input|\$_(get|post|request|cookie)/i.test(ev) ? "pass" : "unknown",
|
|
18508
|
+
note: /attacker-controlled|user input|\$_(get|post|request|cookie)/i.test(ev) ? "input is attacker-controlled" : "input provenance unclear"
|
|
18509
|
+
},
|
|
18510
|
+
{
|
|
18511
|
+
id: "sink_reachable",
|
|
18512
|
+
question: "Is the sink reachable?",
|
|
18513
|
+
verdict: sinkNode ? "pass" : "unknown",
|
|
18514
|
+
note: sinkNode ? "sink node present in graph" : "sink not mapped in graph"
|
|
18515
|
+
},
|
|
18516
|
+
{
|
|
18517
|
+
id: "caching",
|
|
18518
|
+
question: "Is the observed behavior caused by caching?",
|
|
18519
|
+
verdict: /cache|etag|304/i.test(ev) ? "fail" : "unknown",
|
|
18520
|
+
note: /cache|etag|304/i.test(ev) ? "possible cache artifact" : "no cache signal"
|
|
18521
|
+
},
|
|
18522
|
+
{
|
|
18523
|
+
id: "reproducible",
|
|
18524
|
+
question: "Is the exploit reproducible?",
|
|
18525
|
+
verdict: /verdict=confirmed|reproducible|marker/i.test(ev) ? "pass" : "unknown",
|
|
18526
|
+
note: /verdict=confirmed|reproducible|marker/i.test(ev) ? "live validation performed" : "no live validation"
|
|
18527
|
+
},
|
|
18528
|
+
{
|
|
18529
|
+
id: "negative_control",
|
|
18530
|
+
question: "Does the negative control remain inert?",
|
|
18531
|
+
verdict: /control (not|did not|inert)|control=false|verdict=confirmed/i.test(ev) ? "pass" : /control.*reflected|benign echo|false_positive/i.test(ev) ? "fail" : "unknown",
|
|
18532
|
+
note: /benign echo|false_positive/i.test(ev) ? "control also reflected" : /verdict=confirmed/i.test(ev) ? "control inert" : "no negative control"
|
|
18533
|
+
},
|
|
18534
|
+
{
|
|
18535
|
+
id: "impact_accurate",
|
|
18536
|
+
question: "Is the impact accurately classified?",
|
|
18537
|
+
verdict: /cwe-\d+/i.test(h.potential_impact) || h.potential_impact !== "unspecified" ? "pass" : "unknown",
|
|
18538
|
+
note: h.potential_impact !== "unspecified" ? `impact: ${h.potential_impact}` : "impact unspecified"
|
|
18539
|
+
}
|
|
18540
|
+
];
|
|
18541
|
+
}
|
|
18542
|
+
function reviewFinding(h, mission) {
|
|
18543
|
+
const questions = checkAll(h, mission);
|
|
18544
|
+
const fails = questions.filter((q) => q.verdict === "fail").length;
|
|
18545
|
+
const passes = questions.filter((q) => q.verdict === "pass").length;
|
|
18546
|
+
let outcome;
|
|
18547
|
+
if (fails > 0)
|
|
18548
|
+
outcome = "REJECTED";
|
|
18549
|
+
else if (passes < 3)
|
|
18550
|
+
outcome = "NEEDS_MORE_EVIDENCE";
|
|
18551
|
+
else
|
|
18552
|
+
outcome = "CONFIRMED";
|
|
18553
|
+
return { hypothesis_id: h.id, outcome, questions };
|
|
18554
|
+
}
|
|
18555
|
+
function reviewValidated(mission) {
|
|
18556
|
+
return mission.hypotheses.filter((h) => h.status === "validated").map((h) => reviewFinding(h, mission));
|
|
18557
|
+
}
|
|
18558
|
+
var init_reviewer = () => {};
|
|
18559
|
+
|
|
18261
18560
|
// src/agents.ts
|
|
18262
18561
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
18263
18562
|
function emptyGraph() {
|
|
@@ -18273,9 +18572,10 @@ function sourceFiles(root, max = 500) {
|
|
|
18273
18572
|
function agentFor(type) {
|
|
18274
18573
|
return AGENTS[type];
|
|
18275
18574
|
}
|
|
18276
|
-
var reconAgent, webAgent, sourceAgent, analysisAgent, authAgent, validatorAgent, reviewerAgent, reporterAgent, mobileAgent, cloudAgent, commanderAgent, apiAgent, AGENTS;
|
|
18575
|
+
var reconAgent, webAgent, sourceAgent, analysisAgent, authAgent, validatorAgent, reviewerAgent, browserAgent2, reporterAgent, mobileAgent, cloudAgent, commanderAgent, apiAgent, AGENTS;
|
|
18277
18576
|
var init_agents = __esm(() => {
|
|
18278
18577
|
init_planner();
|
|
18578
|
+
init_reviewer();
|
|
18279
18579
|
init_orchestrator();
|
|
18280
18580
|
init_scanner();
|
|
18281
18581
|
init_eagle2();
|
|
@@ -18455,25 +18755,64 @@ var init_agents = __esm(() => {
|
|
|
18455
18755
|
responsibility: "Challenge findings before confirmation (reachability, auth, controls).",
|
|
18456
18756
|
tools: ["read_skill", "eagle_eye2"],
|
|
18457
18757
|
async execute(_task, ctx) {
|
|
18458
|
-
const
|
|
18758
|
+
const results = reviewValidated(ctx.mission);
|
|
18459
18759
|
const hypotheses = [];
|
|
18460
|
-
for (const
|
|
18461
|
-
if (
|
|
18760
|
+
for (const r of results) {
|
|
18761
|
+
if (r.outcome !== "REJECTED")
|
|
18462
18762
|
continue;
|
|
18463
|
-
const
|
|
18464
|
-
|
|
18465
|
-
|
|
18466
|
-
|
|
18467
|
-
|
|
18468
|
-
|
|
18469
|
-
|
|
18470
|
-
|
|
18471
|
-
|
|
18472
|
-
|
|
18473
|
-
|
|
18474
|
-
|
|
18475
|
-
|
|
18476
|
-
|
|
18763
|
+
const h = ctx.mission.hypotheses.find((x) => x.id === r.hypothesis_id);
|
|
18764
|
+
if (h) {
|
|
18765
|
+
const failIds = r.questions.filter((q) => q.verdict === "fail").map((q) => q.id).join(", ");
|
|
18766
|
+
hypotheses.push(rejectHypothesis(h, `adversarial review rejected: ${failIds || "unexplainable"}`));
|
|
18767
|
+
}
|
|
18768
|
+
}
|
|
18769
|
+
return { ok: true, output: { outcomes: results.map((r) => ({ id: r.hypothesis_id, outcome: r.outcome })) }, hypotheses, graph: emptyGraph() };
|
|
18770
|
+
}
|
|
18771
|
+
};
|
|
18772
|
+
browserAgent2 = {
|
|
18773
|
+
type: "browser",
|
|
18774
|
+
responsibility: "Drive a real browser to detect DOM issues and auth flows.",
|
|
18775
|
+
tools: ["browser_open", "browser_click", "browser_type", "browser_evaluate", "browser_screenshot", "browser_navigate", "browser_back", "browser_forward", "browser_get_cookie", "browser_set_cookie"],
|
|
18776
|
+
async execute(_task, ctx) {
|
|
18777
|
+
if (!/^https?:\/\//i.test(ctx.mission.target) || !ctx.scopeAllowed) {
|
|
18778
|
+
return { ok: false, output: {}, hypotheses: [], graph: emptyGraph(), error: "browser agent requires an authorized URL target" };
|
|
18779
|
+
}
|
|
18780
|
+
await Promise.resolve().then(() => init_browser_agent());
|
|
18781
|
+
const opened = await browserOpen(ctx.mission.target);
|
|
18782
|
+
if (opened.unavailable) {
|
|
18783
|
+
return { ok: false, output: {}, hypotheses: [], graph: emptyGraph(), error: opened.detail };
|
|
18784
|
+
}
|
|
18785
|
+
const auth = await detectAuthForm();
|
|
18786
|
+
const diag = browserDiagnostics();
|
|
18787
|
+
await browserClose();
|
|
18788
|
+
const hypotheses = [];
|
|
18789
|
+
const authInfo = auth.value ?? {};
|
|
18790
|
+
if (authInfo.found) {
|
|
18791
|
+
hypotheses.push(makeHypothesis({
|
|
18792
|
+
type: "auth_flow_detected",
|
|
18793
|
+
target: ctx.mission.target,
|
|
18794
|
+
evidence: `login form detected (user_field=${authInfo.has_user}, pass_field=${authInfo.has_pass})`,
|
|
18795
|
+
confidence: 0.6,
|
|
18796
|
+
required_validation: "login flow",
|
|
18797
|
+
potential_impact: "auth boundary"
|
|
18798
|
+
}));
|
|
18799
|
+
}
|
|
18800
|
+
if (diag.console_errors.length > 0 || diag.page_errors.length > 0) {
|
|
18801
|
+
hypotheses.push(makeHypothesis({
|
|
18802
|
+
type: "js_runtime_error",
|
|
18803
|
+
target: ctx.mission.target,
|
|
18804
|
+
evidence: `console_errors=${diag.console_errors.length} page_errors=${diag.page_errors.length}`,
|
|
18805
|
+
confidence: 0.4,
|
|
18806
|
+
required_validation: "review",
|
|
18807
|
+
potential_impact: "unspecified"
|
|
18808
|
+
}));
|
|
18809
|
+
}
|
|
18810
|
+
return {
|
|
18811
|
+
ok: true,
|
|
18812
|
+
output: { opened: opened.ok, auth_form: authInfo.found ?? false, console_errors: diag.console_errors.length, page_errors: diag.page_errors.length },
|
|
18813
|
+
hypotheses,
|
|
18814
|
+
graph: emptyGraph()
|
|
18815
|
+
};
|
|
18477
18816
|
}
|
|
18478
18817
|
};
|
|
18479
18818
|
reporterAgent = {
|
|
@@ -18586,7 +18925,8 @@ ${m.hypotheses.map((h) => `- [${h.status}] ${h.type} @ ${h.target} (confidence $
|
|
|
18586
18925
|
analysis: analysisAgent,
|
|
18587
18926
|
validator: validatorAgent,
|
|
18588
18927
|
reviewer: reviewerAgent,
|
|
18589
|
-
reporter: reporterAgent
|
|
18928
|
+
reporter: reporterAgent,
|
|
18929
|
+
browser: browserAgent2
|
|
18590
18930
|
};
|
|
18591
18931
|
});
|
|
18592
18932
|
|
|
@@ -18712,10 +19052,39 @@ var init_recovery = __esm(() => {
|
|
|
18712
19052
|
analysis: ["source"],
|
|
18713
19053
|
validator: ["reviewer"],
|
|
18714
19054
|
reviewer: ["validator"],
|
|
18715
|
-
reporter: []
|
|
19055
|
+
reporter: [],
|
|
19056
|
+
browser: ["web"]
|
|
18716
19057
|
};
|
|
18717
19058
|
});
|
|
18718
19059
|
|
|
19060
|
+
// src/mission/memory.ts
|
|
19061
|
+
function recordHypothesisOutcome(type, target, status, evidence) {
|
|
19062
|
+
return remember(`finding:${type}`, `[${status}] ${type} @ ${target}: ${evidence}`, status === "validated" ? "signature" : "lesson", [type.toLowerCase(), status], "mission", status === "validated").saved === true;
|
|
19063
|
+
}
|
|
19064
|
+
function recordFailedApproach(fa) {
|
|
19065
|
+
return remember(`failed:${fa.technique}`, `${fa.technique} failed (${fa.category}): ${fa.reason} — ${fa.future_policy}`, "lesson", ["failed", fa.category], "mission", false).saved === true;
|
|
19066
|
+
}
|
|
19067
|
+
function persistMissionMemory(mission) {
|
|
19068
|
+
let attempted = 0;
|
|
19069
|
+
let saved = 0;
|
|
19070
|
+
for (const h of mission.hypotheses) {
|
|
19071
|
+
if (h.status !== "validated" && h.status !== "rejected")
|
|
19072
|
+
continue;
|
|
19073
|
+
attempted += 1;
|
|
19074
|
+
if (recordHypothesisOutcome(h.type, h.target, h.status, h.evidence))
|
|
19075
|
+
saved += 1;
|
|
19076
|
+
}
|
|
19077
|
+
for (const fa of mission.failed_approaches) {
|
|
19078
|
+
attempted += 1;
|
|
19079
|
+
if (recordFailedApproach(fa))
|
|
19080
|
+
saved += 1;
|
|
19081
|
+
}
|
|
19082
|
+
return { saved, deduped: attempted - saved };
|
|
19083
|
+
}
|
|
19084
|
+
var init_memory2 = __esm(() => {
|
|
19085
|
+
init_memory();
|
|
19086
|
+
});
|
|
19087
|
+
|
|
18719
19088
|
// src/mission/control.ts
|
|
18720
19089
|
import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
|
|
18721
19090
|
function scopeAllowed(mission) {
|
|
@@ -18878,6 +19247,10 @@ function finalize2(mission) {
|
|
|
18878
19247
|
tasks: allResolved(m.tasks) ? 1 : 0,
|
|
18879
19248
|
hypotheses_validated: m.hypotheses.length > 0 ? m.hypotheses.filter((h) => h.status === "validated").length / m.hypotheses.length : 0
|
|
18880
19249
|
};
|
|
19250
|
+
const mem = persistMissionMemory(m);
|
|
19251
|
+
if (mem.saved > 0) {
|
|
19252
|
+
m = { ...m, memory_refs: [...m.memory_refs, `persisted:${mem.saved} (${mem.deduped} deduped)`] };
|
|
19253
|
+
}
|
|
18881
19254
|
return saveMission(m);
|
|
18882
19255
|
}
|
|
18883
19256
|
async function startMission(target, mode, opts = {}) {
|
|
@@ -18900,6 +19273,7 @@ var init_control = __esm(() => {
|
|
|
18900
19273
|
init_planner();
|
|
18901
19274
|
init_coverage();
|
|
18902
19275
|
init_recovery();
|
|
19276
|
+
init_memory2();
|
|
18903
19277
|
MODE_PROFILES = {
|
|
18904
19278
|
web: { agents: ["recon", "web", "api", "auth", "validator", "reviewer", "reporter"], verify: true, risk: "medium" },
|
|
18905
19279
|
api: { agents: ["recon", "api", "auth", "validator", "reviewer", "reporter"], verify: true, risk: "medium" },
|
|
@@ -53008,6 +53382,7 @@ async function browserValidate(check, target, payload) {
|
|
|
53008
53382
|
}
|
|
53009
53383
|
|
|
53010
53384
|
// src/server.ts
|
|
53385
|
+
init_browser_agent();
|
|
53011
53386
|
init_universal_taint();
|
|
53012
53387
|
|
|
53013
53388
|
// src/guidance.ts
|
|
@@ -54571,6 +54946,28 @@ function createServer() {
|
|
|
54571
54946
|
const result = await browserValidate(check, target, payload);
|
|
54572
54947
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
54573
54948
|
});
|
|
54949
|
+
server.registerTool("browser_agent", {
|
|
54950
|
+
title: "Browser automation agent (session)",
|
|
54951
|
+
description: "PHASE 12: drive a real headless-Chromium session. Ops: open, navigate, click, type, evaluate, screenshot, back, forward, get_cookie, set_cookie, plus detect_auth_form, diagnostics, close. Enables multi-step flows (login/MFA) and DOM issue detection. Requires playwright-core (optional peer dependency).",
|
|
54952
|
+
inputSchema: {
|
|
54953
|
+
op: _enum(["open", "navigate", "click", "type", "evaluate", "screenshot", "back", "forward", "get_cookie", "set_cookie", "detect_auth_form", "diagnostics", "close"]),
|
|
54954
|
+
args: array(string2()).optional().describe("Positional args (url, selector, text, js, cookie name/value, screenshot path)")
|
|
54955
|
+
}
|
|
54956
|
+
}, async ({ op, args }) => {
|
|
54957
|
+
if (op === "detect_auth_form") {
|
|
54958
|
+
const r = await detectAuthForm();
|
|
54959
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }] };
|
|
54960
|
+
}
|
|
54961
|
+
if (op === "diagnostics") {
|
|
54962
|
+
return { content: [{ type: "text", text: JSON.stringify(browserDiagnostics()) }] };
|
|
54963
|
+
}
|
|
54964
|
+
if (op === "close") {
|
|
54965
|
+
await browserClose();
|
|
54966
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: true, detail: "session closed" }) }] };
|
|
54967
|
+
}
|
|
54968
|
+
const r = await browserAgent(op, args ?? []);
|
|
54969
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }] };
|
|
54970
|
+
});
|
|
54574
54971
|
server.registerTool("taint_tree", {
|
|
54575
54972
|
title: "Whole-tree taint scan",
|
|
54576
54973
|
description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.52",
|
|
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": {
|