blitzstrike 1.0.31 → 1.0.33
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 +29 -0
- package/README.md +3 -0
- package/dist/index.js +371 -182
- package/docs/findings.md +22 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,32 @@ 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.33] — 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Phase 1 — Core Integrity** (per the master upgrade spec):
|
|
13
|
+
- **Finding invariants** (`src/invariants.ts`) — hard, machine-checkable rules:
|
|
14
|
+
a finding cannot be CONFIRMED without evidence, cannot be CONFIRMED with a
|
|
15
|
+
failed negative control, cannot be CONFIRMED out of scope, cannot persist a
|
|
16
|
+
raw secret, and its evidence hash must stay stable. Enforced in
|
|
17
|
+
`confirmFinding` and re-checkable via `checkFindingInvariants`.
|
|
18
|
+
- **Evidence provenance** (`src/evidence.ts` §16) — every evidence record now
|
|
19
|
+
carries chain-of-custody metadata (`tool`, `version`, `target`, `scope`,
|
|
20
|
+
`parentEvidence`).
|
|
21
|
+
- **Audit log** (`src/audit.ts` §45) — structured append-only audit events
|
|
22
|
+
(`scope_checked`, `finding_created`, `finding_updated`, `validation_finished`,
|
|
23
|
+
`evidence_created`, …) at `~/.blitzstrike/audit.jsonl`, wired into scope
|
|
24
|
+
check, finding creation, evidence attachment, and validation.
|
|
25
|
+
- `confirmFinding` now **throws** if called with no evidence (hard invariant).
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `scopeCheck` refactored to a single exit path that always records a
|
|
30
|
+
`scope_checked` audit event.
|
|
31
|
+
- Shared `src/version.ts` — single source of truth for the package version
|
|
32
|
+
(removed the duplicate VERSION readers in `server.ts`).
|
|
33
|
+
|
|
8
34
|
## [1.0.26] — 2026-09-13
|
|
9
35
|
|
|
10
36
|
The first hardening milestone: the whole roadmap is implemented, measured, and
|
|
@@ -51,6 +77,9 @@ code path, not a slogan.
|
|
|
51
77
|
master reference of web attack vectors (RCE, SQL, auth, SSRF, XSS, API,
|
|
52
78
|
business logic, race conditions, cloud, supply chain, CI/CD, AI/LLM, Web3,
|
|
53
79
|
XS-leaks, …) for BLITZ attack-surface mapping.
|
|
80
|
+
- **Self-update** (`blitzstrike update` + `check_update` tool) — query the npm
|
|
81
|
+
registry for the latest version (semver-aware) and refresh the heavy data
|
|
82
|
+
cache (payloads + templates) in one shot.
|
|
54
83
|
|
|
55
84
|
### Changed
|
|
56
85
|
|
package/README.md
CHANGED
|
@@ -117,6 +117,8 @@ blitzstrike serve --mcp # start MCP server over stdio (default)
|
|
|
117
117
|
blitzstrike doctor # health check: runtime + 130-tool catalog + creds
|
|
118
118
|
blitzstrike install # write MCP config to detected clients (Claude/Cursor/OpenCode)
|
|
119
119
|
blitzstrike install --dry-run # preview the config without writing
|
|
120
|
+
blitzstrike sync-data # fetch heavy datasets (payloads + templates) on-demand
|
|
121
|
+
blitzstrike update # check for a newer version + refresh the data cache
|
|
120
122
|
blitzstrike version # print version
|
|
121
123
|
```
|
|
122
124
|
|
|
@@ -226,6 +228,7 @@ MCP servers. With no client detected, it prints the snippet for manual paste.
|
|
|
226
228
|
| `template_lookup(topic)` | Find nuclei detection templates (11.9k YAML signatures). |
|
|
227
229
|
| `list_attack_vectors()` | List the full web attack-vector taxonomy (34 categories, 588 vectors). |
|
|
228
230
|
| `attack_vectors(category)` | Enumerate the vector list for one category (e.g. ssrf, business logic, ai/llm). |
|
|
231
|
+
| `check_update()` | Query the npm registry for the latest blitzstrike version. |
|
|
229
232
|
|
|
230
233
|
The intelligence layer (WAF signatures, tech/CVE/port correlations, fuzzer
|
|
231
234
|
data, vuln ontology, exploit payloads, nuclei detection templates) is sourced
|
package/dist/index.js
CHANGED
|
@@ -14820,11 +14820,11 @@ var require_src = __commonJS(function(exports, module) {
|
|
|
14820
14820
|
});
|
|
14821
14821
|
|
|
14822
14822
|
// src/sync.ts
|
|
14823
|
-
import { mkdirSync as mkdirSync3, writeFileSync as
|
|
14824
|
-
import { join as
|
|
14823
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync12, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync2 } from "node:fs";
|
|
14824
|
+
import { join as join10 } from "node:path";
|
|
14825
14825
|
import { homedir as homedir4 } from "node:os";
|
|
14826
14826
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
14827
|
-
import { spawnSync as
|
|
14827
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
14828
14828
|
function dirExists(p) {
|
|
14829
14829
|
try {
|
|
14830
14830
|
return statSync3(p).isDirectory();
|
|
@@ -14838,7 +14838,7 @@ function countFiles(dir) {
|
|
|
14838
14838
|
const walk = (d) => {
|
|
14839
14839
|
for (const e of readdirSync5(d, { withFileTypes: true })) {
|
|
14840
14840
|
if (e.isDirectory())
|
|
14841
|
-
walk(
|
|
14841
|
+
walk(join10(d, e.name));
|
|
14842
14842
|
else
|
|
14843
14843
|
n++;
|
|
14844
14844
|
}
|
|
@@ -14852,8 +14852,8 @@ function countFiles(dir) {
|
|
|
14852
14852
|
function syncData() {
|
|
14853
14853
|
if (!dirExists(DATA_ROOT2))
|
|
14854
14854
|
mkdirSync3(DATA_ROOT2, { recursive: true });
|
|
14855
|
-
const payloadsPath =
|
|
14856
|
-
const templatesPath =
|
|
14855
|
+
const payloadsPath = join10(DATA_ROOT2, "payloads");
|
|
14856
|
+
const templatesPath = join10(DATA_ROOT2, "templates");
|
|
14857
14857
|
const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
|
|
14858
14858
|
const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
|
|
14859
14859
|
if (hasPayloads && hasTemplates) {
|
|
@@ -14863,8 +14863,8 @@ function syncData() {
|
|
|
14863
14863
|
data_root: DATA_ROOT2
|
|
14864
14864
|
};
|
|
14865
14865
|
}
|
|
14866
|
-
const localPayloads =
|
|
14867
|
-
const localTemplates =
|
|
14866
|
+
const localPayloads = join10(ROOT5, "payloads");
|
|
14867
|
+
const localTemplates = join10(ROOT5, "templates");
|
|
14868
14868
|
let payloadsSynced = false;
|
|
14869
14869
|
let templatesSynced = false;
|
|
14870
14870
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -14875,21 +14875,21 @@ function syncData() {
|
|
|
14875
14875
|
copyDir(localTemplates, templatesPath);
|
|
14876
14876
|
templatesSynced = true;
|
|
14877
14877
|
}
|
|
14878
|
-
const gitOk =
|
|
14878
|
+
const gitOk = spawnSync2("git", ["--version"]).status === 0;
|
|
14879
14879
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
14880
|
-
const tmp =
|
|
14880
|
+
const tmp = join10(DATA_ROOT2, ".tmp-clone");
|
|
14881
14881
|
if (dirExists(tmp))
|
|
14882
14882
|
rmSync2(tmp, { recursive: true, force: true });
|
|
14883
|
-
const r =
|
|
14883
|
+
const r = spawnSync2("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
14884
14884
|
if (r.status === 0) {
|
|
14885
|
-
const sparse =
|
|
14885
|
+
const sparse = spawnSync2("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
14886
14886
|
if (sparse.status === 0) {
|
|
14887
14887
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
14888
|
-
copyDir(
|
|
14888
|
+
copyDir(join10(tmp, "payloads"), payloadsPath);
|
|
14889
14889
|
payloadsSynced = true;
|
|
14890
14890
|
}
|
|
14891
14891
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
14892
|
-
copyDir(
|
|
14892
|
+
copyDir(join10(tmp, "templates"), templatesPath);
|
|
14893
14893
|
templatesSynced = true;
|
|
14894
14894
|
}
|
|
14895
14895
|
}
|
|
@@ -14903,7 +14903,7 @@ function syncData() {
|
|
|
14903
14903
|
};
|
|
14904
14904
|
}
|
|
14905
14905
|
function copyDir(src, dst) {
|
|
14906
|
-
const r =
|
|
14906
|
+
const r = spawnSync2("cp", ["-r", src, dst], { timeout: 120000 });
|
|
14907
14907
|
if (r.status !== 0) {
|
|
14908
14908
|
manualCopy(src, dst);
|
|
14909
14909
|
}
|
|
@@ -14911,18 +14911,59 @@ function copyDir(src, dst) {
|
|
|
14911
14911
|
function manualCopy(src, dst) {
|
|
14912
14912
|
mkdirSync3(dst, { recursive: true });
|
|
14913
14913
|
for (const e of readdirSync5(src, { withFileTypes: true })) {
|
|
14914
|
-
const s =
|
|
14915
|
-
const d =
|
|
14914
|
+
const s = join10(src, e.name);
|
|
14915
|
+
const d = join10(dst, e.name);
|
|
14916
14916
|
if (e.isDirectory())
|
|
14917
14917
|
manualCopy(s, d);
|
|
14918
14918
|
else
|
|
14919
|
-
|
|
14919
|
+
writeFileSync3(d, readFileSync12(s));
|
|
14920
14920
|
}
|
|
14921
14921
|
}
|
|
14922
|
-
var
|
|
14922
|
+
var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
14923
14923
|
var init_sync = __esm(() => {
|
|
14924
|
-
|
|
14925
|
-
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ??
|
|
14924
|
+
ROOT5 = join10(fileURLToPath7(new URL(".", import.meta.url)), "..");
|
|
14925
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join10(homedir4(), ".blitzstrike", "data");
|
|
14926
|
+
});
|
|
14927
|
+
|
|
14928
|
+
// src/update.ts
|
|
14929
|
+
async function checkLatestVersion(timeoutMs = 1e4) {
|
|
14930
|
+
try {
|
|
14931
|
+
const controller = new AbortController;
|
|
14932
|
+
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
14933
|
+
const res = await fetch("https://registry.npmjs.org/blitzstrike/latest", { signal: controller.signal });
|
|
14934
|
+
clearTimeout(t);
|
|
14935
|
+
if (!res.ok)
|
|
14936
|
+
return null;
|
|
14937
|
+
const data = await res.json();
|
|
14938
|
+
return data.version ?? null;
|
|
14939
|
+
} catch {
|
|
14940
|
+
return null;
|
|
14941
|
+
}
|
|
14942
|
+
}
|
|
14943
|
+
function semverCompare(a, b) {
|
|
14944
|
+
const pa = a.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
14945
|
+
const pb = b.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
14946
|
+
for (let i = 0;i < 3; i++) {
|
|
14947
|
+
const x = pa[i] ?? 0;
|
|
14948
|
+
const y = pb[i] ?? 0;
|
|
14949
|
+
if (x !== y)
|
|
14950
|
+
return x < y ? -1 : 1;
|
|
14951
|
+
}
|
|
14952
|
+
return 0;
|
|
14953
|
+
}
|
|
14954
|
+
async function runUpdate(currentVersion) {
|
|
14955
|
+
const latest = await checkLatestVersion();
|
|
14956
|
+
const outdated = latest !== null && semverCompare(currentVersion, latest) < 0;
|
|
14957
|
+
return {
|
|
14958
|
+
current: currentVersion,
|
|
14959
|
+
latest,
|
|
14960
|
+
outdated,
|
|
14961
|
+
update_command: "npm install -g blitzstrike@latest # or: bun add -g blitzstrike@latest",
|
|
14962
|
+
data_sync: syncData()
|
|
14963
|
+
};
|
|
14964
|
+
}
|
|
14965
|
+
var init_update = __esm(() => {
|
|
14966
|
+
init_sync();
|
|
14926
14967
|
});
|
|
14927
14968
|
|
|
14928
14969
|
// node_modules/zod/v3/helpers/util.js
|
|
@@ -29642,9 +29683,9 @@ async function nvdLookup(cveId) {
|
|
|
29642
29683
|
|
|
29643
29684
|
// src/orchestrator.ts
|
|
29644
29685
|
init_scanner();
|
|
29645
|
-
import { readFileSync as
|
|
29646
|
-
import { join as
|
|
29647
|
-
import { fileURLToPath as
|
|
29686
|
+
import { readFileSync as readFileSync6, existsSync as existsSync4, statSync as statSync2 } from "node:fs";
|
|
29687
|
+
import { join as join6 } from "node:path";
|
|
29688
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
29648
29689
|
|
|
29649
29690
|
// src/memory.ts
|
|
29650
29691
|
import { readFileSync as readFileSync2, appendFileSync, mkdirSync, existsSync, writeFileSync } from "node:fs";
|
|
@@ -29876,6 +29917,47 @@ function manualForTool(toolName) {
|
|
|
29876
29917
|
return null;
|
|
29877
29918
|
}
|
|
29878
29919
|
|
|
29920
|
+
// src/audit.ts
|
|
29921
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
29922
|
+
import { join as join5 } from "node:path";
|
|
29923
|
+
import { homedir as homedir2 } from "node:os";
|
|
29924
|
+
|
|
29925
|
+
// src/version.ts
|
|
29926
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
29927
|
+
import { join as join4, dirname } from "node:path";
|
|
29928
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
29929
|
+
var VERSION = (() => {
|
|
29930
|
+
try {
|
|
29931
|
+
const root = join4(dirname(fileURLToPath2(import.meta.url)), "..");
|
|
29932
|
+
return JSON.parse(readFileSync4(join4(root, "package.json"), "utf8")).version ?? "unknown";
|
|
29933
|
+
} catch {
|
|
29934
|
+
return "unknown";
|
|
29935
|
+
}
|
|
29936
|
+
})();
|
|
29937
|
+
|
|
29938
|
+
// src/audit.ts
|
|
29939
|
+
var HOME_DIR2 = process.env.BLITZSTRIKE_HOME ?? join5(homedir2(), ".blitzstrike");
|
|
29940
|
+
var AUDIT_PATH = join5(HOME_DIR2, "audit.jsonl");
|
|
29941
|
+
function recordAudit(event, opts = {}) {
|
|
29942
|
+
const record = {
|
|
29943
|
+
timestamp: new Date().toISOString(),
|
|
29944
|
+
event,
|
|
29945
|
+
actor: opts.actor ?? "system",
|
|
29946
|
+
tool: opts.tool,
|
|
29947
|
+
tool_version: VERSION,
|
|
29948
|
+
target: opts.target,
|
|
29949
|
+
result: opts.result,
|
|
29950
|
+
correlationId: opts.correlationId
|
|
29951
|
+
};
|
|
29952
|
+
try {
|
|
29953
|
+
if (!existsSync3(HOME_DIR2))
|
|
29954
|
+
mkdirSync2(HOME_DIR2, { recursive: true });
|
|
29955
|
+
appendFileSync2(AUDIT_PATH, JSON.stringify(record) + `
|
|
29956
|
+
`);
|
|
29957
|
+
} catch {}
|
|
29958
|
+
return record;
|
|
29959
|
+
}
|
|
29960
|
+
|
|
29879
29961
|
// src/evidence.ts
|
|
29880
29962
|
import { createHash as createHash2 } from "node:crypto";
|
|
29881
29963
|
var REDACTION_PATTERNS = [
|
|
@@ -29902,6 +29984,13 @@ function redactSecrets(text) {
|
|
|
29902
29984
|
}
|
|
29903
29985
|
return out;
|
|
29904
29986
|
}
|
|
29987
|
+
function hasSecrets(text) {
|
|
29988
|
+
for (const [re] of REDACTION_PATTERNS) {
|
|
29989
|
+
if (re.test(text))
|
|
29990
|
+
return true;
|
|
29991
|
+
}
|
|
29992
|
+
return false;
|
|
29993
|
+
}
|
|
29905
29994
|
function redactObject(value) {
|
|
29906
29995
|
if (typeof value === "string")
|
|
29907
29996
|
return redactSecrets(value);
|
|
@@ -29936,9 +30025,61 @@ function makeEvidence(input) {
|
|
|
29936
30025
|
source: input.source ? redactObject(input.source) : undefined,
|
|
29937
30026
|
sink: input.sink ? redactObject(input.sink) : undefined,
|
|
29938
30027
|
artifacts,
|
|
29939
|
-
recorded: new Date().toISOString()
|
|
30028
|
+
recorded: new Date().toISOString(),
|
|
30029
|
+
provenance: {
|
|
30030
|
+
tool: input.provenance?.tool ?? "blitzstrike",
|
|
30031
|
+
version: input.provenance?.version ?? VERSION,
|
|
30032
|
+
...input.provenance?.target ? { target: input.provenance.target } : {},
|
|
30033
|
+
...input.provenance?.scope ? { scope: input.provenance.scope } : {},
|
|
30034
|
+
...input.provenance?.parentEvidence ? { parentEvidence: input.provenance.parentEvidence } : {}
|
|
30035
|
+
}
|
|
29940
30036
|
};
|
|
29941
30037
|
}
|
|
30038
|
+
function verifyArtifact(artifact) {
|
|
30039
|
+
return sha256(artifact.content) === artifact.sha256;
|
|
30040
|
+
}
|
|
30041
|
+
function verifyEvidence(evidence) {
|
|
30042
|
+
const failed = [];
|
|
30043
|
+
for (const a of evidence.artifacts) {
|
|
30044
|
+
if (!verifyArtifact(a))
|
|
30045
|
+
failed.push(a.name);
|
|
30046
|
+
}
|
|
30047
|
+
return { ok: failed.length === 0, failed };
|
|
30048
|
+
}
|
|
30049
|
+
|
|
30050
|
+
// src/invariants.ts
|
|
30051
|
+
function checkFindingInvariants(finding) {
|
|
30052
|
+
const violations = [];
|
|
30053
|
+
if (finding.status === "confirmed" && finding.evidence.length === 0) {
|
|
30054
|
+
violations.push({ invariant: "confirmed-requires-evidence", detail: `finding ${finding.id} is confirmed with no evidence` });
|
|
30055
|
+
}
|
|
30056
|
+
if (finding.status === "confirmed" && finding.validation.negative_control === false) {
|
|
30057
|
+
violations.push({ invariant: "confirmed-requires-clean-negative-control", detail: `finding ${finding.id} confirmed with a failed negative control` });
|
|
30058
|
+
}
|
|
30059
|
+
if (finding.status === "confirmed" && finding.validation.scope_allowed === false) {
|
|
30060
|
+
violations.push({ invariant: "confirmed-requires-scope-allowed", detail: `finding ${finding.id} confirmed out of scope` });
|
|
30061
|
+
}
|
|
30062
|
+
for (const e of finding.evidence) {
|
|
30063
|
+
for (const a of e.artifacts) {
|
|
30064
|
+
if (hasSecrets(a.content)) {
|
|
30065
|
+
violations.push({ invariant: "no-secret-in-evidence", detail: `evidence ${e.evidence_id} artifact ${a.name} contains a raw secret` });
|
|
30066
|
+
}
|
|
30067
|
+
}
|
|
30068
|
+
}
|
|
30069
|
+
for (const e of finding.evidence) {
|
|
30070
|
+
const v = verifyEvidence(e);
|
|
30071
|
+
if (!v.ok) {
|
|
30072
|
+
violations.push({ invariant: "evidence-hash-stable", detail: `evidence ${e.evidence_id} artifacts ${v.failed.join(",")} failed integrity check` });
|
|
30073
|
+
}
|
|
30074
|
+
}
|
|
30075
|
+
return violations;
|
|
30076
|
+
}
|
|
30077
|
+
function assertFindingInvariants(finding) {
|
|
30078
|
+
const violations = checkFindingInvariants(finding);
|
|
30079
|
+
if (violations.length > 0) {
|
|
30080
|
+
throw new Error(`invariant violation: ${violations.map((v) => v.invariant).join(", ")}`);
|
|
30081
|
+
}
|
|
30082
|
+
}
|
|
29942
30083
|
|
|
29943
30084
|
// src/finding.ts
|
|
29944
30085
|
var LIFECYCLE_TRANSITIONS = {
|
|
@@ -30000,7 +30141,7 @@ function makeFinding(input) {
|
|
|
30000
30141
|
const status = input.status ?? "detected";
|
|
30001
30142
|
const factors = confidenceFactorsFor(input);
|
|
30002
30143
|
const confidence = computeConfidence(factors);
|
|
30003
|
-
|
|
30144
|
+
const finding = {
|
|
30004
30145
|
id: nextFindingId(),
|
|
30005
30146
|
status,
|
|
30006
30147
|
title: input.title,
|
|
@@ -30023,6 +30164,8 @@ function makeFinding(input) {
|
|
|
30023
30164
|
remediation: {},
|
|
30024
30165
|
timestamps: { created: now, updated: now }
|
|
30025
30166
|
};
|
|
30167
|
+
recordAudit("finding_created", { tool: "eagle-eye", target: input.target.host ?? input.target.endpoint, result: status, correlationId: finding.id });
|
|
30168
|
+
return finding;
|
|
30026
30169
|
}
|
|
30027
30170
|
function confidenceFactorsFor(_input) {
|
|
30028
30171
|
return {
|
|
@@ -30042,6 +30185,7 @@ function transition(finding, to) {
|
|
|
30042
30185
|
return updated;
|
|
30043
30186
|
}
|
|
30044
30187
|
function attachEvidence(finding, evidence) {
|
|
30188
|
+
recordAudit("evidence_created", { tool: "blitzstrike", target: finding.target.host ?? finding.target.endpoint, result: evidence.evidence_id, correlationId: finding.id });
|
|
30045
30189
|
return {
|
|
30046
30190
|
...finding,
|
|
30047
30191
|
evidence: [...finding.evidence, evidence],
|
|
@@ -30049,8 +30193,12 @@ function attachEvidence(finding, evidence) {
|
|
|
30049
30193
|
};
|
|
30050
30194
|
}
|
|
30051
30195
|
function confirmFinding(finding, opts) {
|
|
30196
|
+
if (opts.evidence.length === 0) {
|
|
30197
|
+
throw new Error("invariant: cannot confirm a finding without evidence");
|
|
30198
|
+
}
|
|
30052
30199
|
const evidence = opts.evidence.map((e) => makeEvidence(e));
|
|
30053
30200
|
const negativeControl = opts.negative_control ?? true;
|
|
30201
|
+
const scopeAllowed = opts.scope_allowed ?? true;
|
|
30054
30202
|
const confidence = computeConfidence({
|
|
30055
30203
|
static_analysis: true,
|
|
30056
30204
|
data_flow: true,
|
|
@@ -30061,7 +30209,7 @@ function confirmFinding(finding, opts) {
|
|
|
30061
30209
|
});
|
|
30062
30210
|
const level = confidenceLevel(confidence);
|
|
30063
30211
|
const status = negativeControl ? "confirmed" : "hypothesis";
|
|
30064
|
-
|
|
30212
|
+
const confirmed = {
|
|
30065
30213
|
...finding,
|
|
30066
30214
|
status,
|
|
30067
30215
|
confidence,
|
|
@@ -30070,11 +30218,15 @@ function confirmFinding(finding, opts) {
|
|
|
30070
30218
|
performed: true,
|
|
30071
30219
|
status: negativeControl ? "confirmed" : "unconfirmed",
|
|
30072
30220
|
baseline: opts.baseline ?? true,
|
|
30073
|
-
negative_control: negativeControl
|
|
30221
|
+
negative_control: negativeControl,
|
|
30222
|
+
scope_allowed: scopeAllowed
|
|
30074
30223
|
},
|
|
30075
30224
|
evidence: [...finding.evidence, ...evidence],
|
|
30076
30225
|
timestamps: { ...finding.timestamps, updated: new Date().toISOString() }
|
|
30077
30226
|
};
|
|
30227
|
+
assertFindingInvariants(confirmed);
|
|
30228
|
+
recordAudit("validation_finished", { tool: "strike", target: finding.target.host ?? finding.target.endpoint, result: status, correlationId: finding.id });
|
|
30229
|
+
return confirmed;
|
|
30078
30230
|
}
|
|
30079
30231
|
function rejectFinding(finding, status, reason, evidence) {
|
|
30080
30232
|
const ev = evidence?.map((e) => makeEvidence(e)) ?? [];
|
|
@@ -30094,9 +30246,9 @@ function loadChains() {
|
|
|
30094
30246
|
if (_chainsCache)
|
|
30095
30247
|
return _chainsCache;
|
|
30096
30248
|
try {
|
|
30097
|
-
const dir =
|
|
30098
|
-
const path =
|
|
30099
|
-
const raw = JSON.parse(
|
|
30249
|
+
const dir = join6(fileURLToPath3(new URL(".", import.meta.url)), "..");
|
|
30250
|
+
const path = join6(dir, "chains.json");
|
|
30251
|
+
const raw = JSON.parse(readFileSync6(path, "utf8"));
|
|
30100
30252
|
_chainsCache = raw.chains ?? [];
|
|
30101
30253
|
return _chainsCache;
|
|
30102
30254
|
} catch {
|
|
@@ -30105,40 +30257,50 @@ function loadChains() {
|
|
|
30105
30257
|
}
|
|
30106
30258
|
}
|
|
30107
30259
|
function scopeCheck(target, scope = "", mode = "bug-bounty") {
|
|
30260
|
+
let result;
|
|
30108
30261
|
if (mode === "ctf" || mode === "reverse-engineering" || mode === "offensive") {
|
|
30109
|
-
|
|
30110
|
-
}
|
|
30111
|
-
|
|
30112
|
-
|
|
30113
|
-
|
|
30114
|
-
|
|
30115
|
-
|
|
30116
|
-
|
|
30117
|
-
|
|
30118
|
-
|
|
30119
|
-
|
|
30120
|
-
|
|
30121
|
-
|
|
30122
|
-
|
|
30123
|
-
|
|
30124
|
-
|
|
30125
|
-
|
|
30126
|
-
|
|
30127
|
-
|
|
30128
|
-
|
|
30129
|
-
|
|
30130
|
-
|
|
30262
|
+
result = { target, allowed: true, enforcement: "off", reason: `mode=${mode} disables scope enforcement` };
|
|
30263
|
+
} else if (!scope.trim()) {
|
|
30264
|
+
result = { target, allowed: false, enforcement: "strict", reason: "no scope provided; refusing active testing" };
|
|
30265
|
+
} else {
|
|
30266
|
+
const inScope = [];
|
|
30267
|
+
const outScope = [];
|
|
30268
|
+
for (const raw of scope.split(/[,\n]/)) {
|
|
30269
|
+
const line = raw.trim();
|
|
30270
|
+
if (!line)
|
|
30271
|
+
continue;
|
|
30272
|
+
if (line.startsWith("-"))
|
|
30273
|
+
outScope.push(line.slice(1).trim());
|
|
30274
|
+
else
|
|
30275
|
+
inScope.push(line);
|
|
30276
|
+
}
|
|
30277
|
+
let host = target;
|
|
30278
|
+
host = host.replace(/^https?:\/\//, "");
|
|
30279
|
+
host = host.split("/")[0].split(":")[0];
|
|
30280
|
+
let decided = false;
|
|
30281
|
+
result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' not in scope` };
|
|
30282
|
+
for (const exc of outScope) {
|
|
30283
|
+
if (exc && (host.includes(exc) || host.endsWith("." + exc.replace(/^\./, "")))) {
|
|
30284
|
+
result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' is explicitly out of scope (${exc})` };
|
|
30285
|
+
decided = true;
|
|
30286
|
+
break;
|
|
30287
|
+
}
|
|
30131
30288
|
}
|
|
30132
|
-
|
|
30133
|
-
|
|
30134
|
-
|
|
30135
|
-
|
|
30136
|
-
|
|
30137
|
-
|
|
30138
|
-
|
|
30289
|
+
if (!decided) {
|
|
30290
|
+
for (const allowed of inScope) {
|
|
30291
|
+
const a = allowed.replace(/\*/g, "");
|
|
30292
|
+
if (!a)
|
|
30293
|
+
continue;
|
|
30294
|
+
if (host.includes(a) || host.endsWith("." + a.replace(/^\./, ""))) {
|
|
30295
|
+
result = { target, allowed: true, enforcement: "strict", reason: `matched in-scope pattern '${allowed}'` };
|
|
30296
|
+
decided = true;
|
|
30297
|
+
break;
|
|
30298
|
+
}
|
|
30299
|
+
}
|
|
30139
30300
|
}
|
|
30140
30301
|
}
|
|
30141
|
-
|
|
30302
|
+
recordAudit("scope_checked", { tool: "scope", target, result: result.allowed ? "allowed" : "denied" });
|
|
30303
|
+
return result;
|
|
30142
30304
|
}
|
|
30143
30305
|
function classifySink(sink) {
|
|
30144
30306
|
if (/(move_uploaded_file|file_put_contents|fwrite)/.test(sink))
|
|
@@ -30202,7 +30364,7 @@ function runEngagement(target, scope = "", mode = "bug-bounty", maxFiles = 2000,
|
|
|
30202
30364
|
};
|
|
30203
30365
|
const isPath = (() => {
|
|
30204
30366
|
try {
|
|
30205
|
-
return
|
|
30367
|
+
return existsSync4(target) && (statSync2(target).isDirectory() || statSync2(target).isFile());
|
|
30206
30368
|
} catch {
|
|
30207
30369
|
return false;
|
|
30208
30370
|
}
|
|
@@ -30319,18 +30481,18 @@ function listChains() {
|
|
|
30319
30481
|
}
|
|
30320
30482
|
|
|
30321
30483
|
// src/catalog.ts
|
|
30322
|
-
import { readFileSync as
|
|
30323
|
-
import { join as
|
|
30324
|
-
import { fileURLToPath as
|
|
30484
|
+
import { readFileSync as readFileSync7, readdirSync as readdirSync2 } from "node:fs";
|
|
30485
|
+
import { join as join7 } from "node:path";
|
|
30486
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
30325
30487
|
import { spawnSync } from "node:child_process";
|
|
30326
|
-
var ROOT2 =
|
|
30488
|
+
var ROOT2 = join7(fileURLToPath4(new URL(".", import.meta.url)), "..");
|
|
30327
30489
|
var _toolsCache = null;
|
|
30328
30490
|
var _skillsCache = null;
|
|
30329
30491
|
function loadTools() {
|
|
30330
30492
|
if (_toolsCache)
|
|
30331
30493
|
return _toolsCache;
|
|
30332
30494
|
try {
|
|
30333
|
-
const raw = JSON.parse(
|
|
30495
|
+
const raw = JSON.parse(readFileSync7(join7(ROOT2, "tools-catalog.json"), "utf8"));
|
|
30334
30496
|
_toolsCache = raw.tools ?? [];
|
|
30335
30497
|
} catch {
|
|
30336
30498
|
_toolsCache = [];
|
|
@@ -30340,14 +30502,14 @@ function loadTools() {
|
|
|
30340
30502
|
function loadSkills() {
|
|
30341
30503
|
if (_skillsCache)
|
|
30342
30504
|
return _skillsCache;
|
|
30343
|
-
const dir =
|
|
30505
|
+
const dir = join7(ROOT2, "skills");
|
|
30344
30506
|
const out = [];
|
|
30345
30507
|
try {
|
|
30346
30508
|
for (const f of readdirSync2(dir)) {
|
|
30347
30509
|
if (!f.endsWith(".md"))
|
|
30348
30510
|
continue;
|
|
30349
|
-
const p =
|
|
30350
|
-
const text =
|
|
30511
|
+
const p = join7(dir, f);
|
|
30512
|
+
const text = readFileSync7(p, "utf8");
|
|
30351
30513
|
const descMatch = text.match(/^description:\s*(.+)$/m);
|
|
30352
30514
|
out.push({
|
|
30353
30515
|
name: f.replace(/\.md$/, ""),
|
|
@@ -30420,7 +30582,7 @@ function readSkill(name) {
|
|
|
30420
30582
|
if (!s)
|
|
30421
30583
|
return { found: false, query: name };
|
|
30422
30584
|
try {
|
|
30423
|
-
const text =
|
|
30585
|
+
const text = readFileSync7(join7(ROOT2, "skills", s.file), "utf8");
|
|
30424
30586
|
return { found: true, name: s.name, content: text.slice(0, 12000) };
|
|
30425
30587
|
} catch {
|
|
30426
30588
|
return { found: false, query: name, error: "unreadable" };
|
|
@@ -30453,19 +30615,19 @@ function ensureTool(name) {
|
|
|
30453
30615
|
}
|
|
30454
30616
|
|
|
30455
30617
|
// src/intel.ts
|
|
30456
|
-
import { readFileSync as
|
|
30457
|
-
import { join as
|
|
30458
|
-
import { fileURLToPath as
|
|
30618
|
+
import { readFileSync as readFileSync8, existsSync as existsSync5 } from "node:fs";
|
|
30619
|
+
import { join as join8 } from "node:path";
|
|
30620
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
30459
30621
|
import { readdirSync as readdirSync3 } from "node:fs";
|
|
30460
|
-
import { homedir as
|
|
30461
|
-
var ROOT3 =
|
|
30462
|
-
var INTEL =
|
|
30622
|
+
import { homedir as homedir3 } from "node:os";
|
|
30623
|
+
var ROOT3 = join8(fileURLToPath5(new URL(".", import.meta.url)), "..");
|
|
30624
|
+
var INTEL = join8(ROOT3, "intelligence");
|
|
30463
30625
|
function loadJson(name) {
|
|
30464
|
-
const p =
|
|
30465
|
-
if (!
|
|
30626
|
+
const p = join8(INTEL, `${name}.json`);
|
|
30627
|
+
if (!existsSync5(p))
|
|
30466
30628
|
return null;
|
|
30467
30629
|
try {
|
|
30468
|
-
return JSON.parse(
|
|
30630
|
+
return JSON.parse(readFileSync8(p, "utf8"));
|
|
30469
30631
|
} catch {
|
|
30470
30632
|
return null;
|
|
30471
30633
|
}
|
|
@@ -30597,18 +30759,18 @@ function attackVectors(category) {
|
|
|
30597
30759
|
}
|
|
30598
30760
|
return { found: true, id: found.id, title: found.title, count: found.vectors.length, vectors: found.vectors };
|
|
30599
30761
|
}
|
|
30600
|
-
var PACKAGE_PAYLOADS =
|
|
30601
|
-
var PACKAGE_TEMPLATES =
|
|
30602
|
-
var DATA_ROOT = process.env.BLITZSTRIKE_DATA ??
|
|
30762
|
+
var PACKAGE_PAYLOADS = join8(ROOT3, "payloads");
|
|
30763
|
+
var PACKAGE_TEMPLATES = join8(ROOT3, "templates");
|
|
30764
|
+
var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join8(homedir3(), ".blitzstrike", "data");
|
|
30603
30765
|
function payloadsDir() {
|
|
30604
|
-
if (
|
|
30766
|
+
if (existsSync5(PACKAGE_PAYLOADS))
|
|
30605
30767
|
return PACKAGE_PAYLOADS;
|
|
30606
|
-
return
|
|
30768
|
+
return join8(DATA_ROOT, "payloads");
|
|
30607
30769
|
}
|
|
30608
30770
|
function templatesDir() {
|
|
30609
|
-
if (
|
|
30771
|
+
if (existsSync5(PACKAGE_TEMPLATES))
|
|
30610
30772
|
return PACKAGE_TEMPLATES;
|
|
30611
|
-
return
|
|
30773
|
+
return join8(DATA_ROOT, "templates");
|
|
30612
30774
|
}
|
|
30613
30775
|
var PAYLOADS = payloadsDir();
|
|
30614
30776
|
var TEMPLATES = templatesDir();
|
|
@@ -30644,7 +30806,7 @@ function payloadLookup(topic) {
|
|
|
30644
30806
|
const normalized = ALIASES[key] ?? key;
|
|
30645
30807
|
const exact = cats.find((c) => c.toLowerCase() === normalized);
|
|
30646
30808
|
if (exact) {
|
|
30647
|
-
return { found: true, category: exact, files: listFiles(
|
|
30809
|
+
return { found: true, category: exact, files: listFiles(join8(PAYLOADS, exact)) };
|
|
30648
30810
|
}
|
|
30649
30811
|
const keyTokens = normalized.split(/[^a-z0-9]+/).filter((t) => t.length >= 3);
|
|
30650
30812
|
const matches = cats.filter((c) => {
|
|
@@ -30654,7 +30816,7 @@ function payloadLookup(topic) {
|
|
|
30654
30816
|
return keyTokens.some((tok) => new RegExp(`(^|[^a-z0-9])${tok}`).test(cl));
|
|
30655
30817
|
});
|
|
30656
30818
|
if (matches.length > 0) {
|
|
30657
|
-
return { found: true, category: matches[0], files: listFiles(
|
|
30819
|
+
return { found: true, category: matches[0], files: listFiles(join8(PAYLOADS, matches[0])), alternatives: matches.slice(0, 10) };
|
|
30658
30820
|
}
|
|
30659
30821
|
return { found: false, topic, suggestions: cats.slice(0, 20) };
|
|
30660
30822
|
}
|
|
@@ -30663,19 +30825,19 @@ function readPayload(category, file) {
|
|
|
30663
30825
|
const exact = cats.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
30664
30826
|
if (!exact)
|
|
30665
30827
|
return { found: false, category, suggestions: cats.slice(0, 20) };
|
|
30666
|
-
const dir =
|
|
30828
|
+
const dir = join8(PAYLOADS, exact);
|
|
30667
30829
|
const files = listFiles(dir);
|
|
30668
30830
|
if (!file) {
|
|
30669
30831
|
const readme = files.find((f) => f.toLowerCase() === "readme.md");
|
|
30670
30832
|
if (readme) {
|
|
30671
|
-
return { found: true, category: exact, file: readme, content:
|
|
30833
|
+
return { found: true, category: exact, file: readme, content: readFileSync8(join8(dir, readme), "utf8").slice(0, 20000) };
|
|
30672
30834
|
}
|
|
30673
30835
|
return { found: true, category: exact, files };
|
|
30674
30836
|
}
|
|
30675
30837
|
const f = files.find((x) => x.toLowerCase() === file.toLowerCase());
|
|
30676
30838
|
if (!f)
|
|
30677
30839
|
return { found: false, category: exact, file, suggestions: files };
|
|
30678
|
-
return { found: true, category: exact, file: f, content:
|
|
30840
|
+
return { found: true, category: exact, file: f, content: readFileSync8(join8(dir, f), "utf8").slice(0, 20000) };
|
|
30679
30841
|
}
|
|
30680
30842
|
function listFiles(dir) {
|
|
30681
30843
|
try {
|
|
@@ -30690,7 +30852,7 @@ function countTemplates() {
|
|
|
30690
30852
|
const walk = (dir) => {
|
|
30691
30853
|
for (const e of readdirSync3(dir, { withFileTypes: true })) {
|
|
30692
30854
|
if (e.isDirectory())
|
|
30693
|
-
walk(
|
|
30855
|
+
walk(join8(dir, e.name));
|
|
30694
30856
|
else if (e.name.endsWith(".yaml"))
|
|
30695
30857
|
n++;
|
|
30696
30858
|
}
|
|
@@ -30715,14 +30877,14 @@ function templateLookup(topic, limit = 10) {
|
|
|
30715
30877
|
}
|
|
30716
30878
|
for (const e of entries) {
|
|
30717
30879
|
if (e.isDirectory())
|
|
30718
|
-
walk(
|
|
30880
|
+
walk(join8(dir, e.name), depth + 1);
|
|
30719
30881
|
else if (e.name.endsWith(".yaml")) {
|
|
30720
30882
|
const name = e.name.toLowerCase();
|
|
30721
30883
|
if (name.includes(key)) {
|
|
30722
|
-
const full =
|
|
30884
|
+
const full = join8(dir, e.name);
|
|
30723
30885
|
let id = "";
|
|
30724
30886
|
try {
|
|
30725
|
-
const content =
|
|
30887
|
+
const content = readFileSync8(full, "utf8");
|
|
30726
30888
|
const m = content.match(/^\s*id:\s*(.+)$/m);
|
|
30727
30889
|
if (m)
|
|
30728
30890
|
id = m[1].trim();
|
|
@@ -30882,7 +31044,7 @@ async function activeScan(target, scope = "", mode = "bug-bounty", allowed = und
|
|
|
30882
31044
|
|
|
30883
31045
|
// src/dataflow.ts
|
|
30884
31046
|
init_scanner();
|
|
30885
|
-
import { readFileSync as
|
|
31047
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
30886
31048
|
var SOURCES = {
|
|
30887
31049
|
$_GET: { id: "http_get", label: "HTTP GET parameter", attacker_controlled: true },
|
|
30888
31050
|
$_POST: { id: "http_post", label: "HTTP POST body", attacker_controlled: true },
|
|
@@ -30964,7 +31126,7 @@ function traceDataFlow(path) {
|
|
|
30964
31126
|
return result;
|
|
30965
31127
|
let text;
|
|
30966
31128
|
try {
|
|
30967
|
-
text =
|
|
31129
|
+
text = readFileSync9(path, "utf8");
|
|
30968
31130
|
} catch {
|
|
30969
31131
|
return result;
|
|
30970
31132
|
}
|
|
@@ -31043,7 +31205,7 @@ function groupVariants(results) {
|
|
|
31043
31205
|
// src/taint.ts
|
|
31044
31206
|
init_scanner();
|
|
31045
31207
|
var import_php_parser = __toESM(require_src(), 1);
|
|
31046
|
-
import { readFileSync as
|
|
31208
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
31047
31209
|
var parser = new import_php_parser.Engine({
|
|
31048
31210
|
parser: { extractDoc: false, php7: true },
|
|
31049
31211
|
ast: { withPositions: true, withSource: false }
|
|
@@ -31341,7 +31503,7 @@ function analyzeTaint(path) {
|
|
|
31341
31503
|
const result = { file: path, findings: [], suppressed: 0 };
|
|
31342
31504
|
let code;
|
|
31343
31505
|
try {
|
|
31344
|
-
code =
|
|
31506
|
+
code = readFileSync10(path, "utf8");
|
|
31345
31507
|
} catch {
|
|
31346
31508
|
return result;
|
|
31347
31509
|
}
|
|
@@ -50615,16 +50777,16 @@ function reportJson(findings, opts = {}) {
|
|
|
50615
50777
|
}
|
|
50616
50778
|
|
|
50617
50779
|
// src/benchmark.ts
|
|
50618
|
-
import { readFileSync as
|
|
50619
|
-
import { join as
|
|
50620
|
-
import { fileURLToPath as
|
|
50780
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync4, writeFileSync as writeFileSync2, rmSync } from "node:fs";
|
|
50781
|
+
import { join as join9 } from "node:path";
|
|
50782
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
50621
50783
|
import { tmpdir } from "node:os";
|
|
50622
|
-
var ROOT4 =
|
|
50623
|
-
var CORPUS =
|
|
50784
|
+
var ROOT4 = join9(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
50785
|
+
var CORPUS = join9(ROOT4, "bench", "corpus");
|
|
50624
50786
|
function parseCorpusFile(path) {
|
|
50625
50787
|
let text;
|
|
50626
50788
|
try {
|
|
50627
|
-
text =
|
|
50789
|
+
text = readFileSync11(path, "utf8");
|
|
50628
50790
|
} catch {
|
|
50629
50791
|
return null;
|
|
50630
50792
|
}
|
|
@@ -50674,7 +50836,7 @@ function loadCorpus() {
|
|
|
50674
50836
|
for (const f of readdirSync4(CORPUS)) {
|
|
50675
50837
|
if (!f.endsWith(".md"))
|
|
50676
50838
|
continue;
|
|
50677
|
-
const e = parseCorpusFile(
|
|
50839
|
+
const e = parseCorpusFile(join9(CORPUS, f));
|
|
50678
50840
|
if (e)
|
|
50679
50841
|
entries.push(e);
|
|
50680
50842
|
}
|
|
@@ -50686,7 +50848,7 @@ function loadCorpus() {
|
|
|
50686
50848
|
function detect(entry) {
|
|
50687
50849
|
const lang = entry.language;
|
|
50688
50850
|
if (lang === "php") {
|
|
50689
|
-
const tmp =
|
|
50851
|
+
const tmp = join9(tmpdir(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
50690
50852
|
writeFileSync2(tmp, entry.code);
|
|
50691
50853
|
const r = analyzeTaint(tmp);
|
|
50692
50854
|
rmSync(tmp, { force: true });
|
|
@@ -50833,6 +50995,7 @@ function coverageMatrix() {
|
|
|
50833
50995
|
}
|
|
50834
50996
|
|
|
50835
50997
|
// src/server.ts
|
|
50998
|
+
init_update();
|
|
50836
50999
|
function createServer() {
|
|
50837
51000
|
const server = new McpServer({
|
|
50838
51001
|
name: "blitzstrike",
|
|
@@ -51589,7 +51752,7 @@ function createServer() {
|
|
|
51589
51752
|
} catch {
|
|
51590
51753
|
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid JSON" }) }] };
|
|
51591
51754
|
}
|
|
51592
|
-
const opts = { title, scope, version:
|
|
51755
|
+
const opts = { title, scope, version: VERSION };
|
|
51593
51756
|
const out = (format ?? "markdown") === "json" ? reportJson(list, opts) : reportMarkdown(list, opts);
|
|
51594
51757
|
return { content: [{ type: "text", text: out }] };
|
|
51595
51758
|
});
|
|
@@ -51607,6 +51770,14 @@ function createServer() {
|
|
|
51607
51770
|
}, async () => {
|
|
51608
51771
|
return { content: [{ type: "text", text: JSON.stringify(coverageMatrix()) }] };
|
|
51609
51772
|
});
|
|
51773
|
+
server.registerTool("check_update", {
|
|
51774
|
+
title: "Check for a newer blitzstrike version",
|
|
51775
|
+
description: "UPDATE: query the npm registry for the latest blitzstrike version. Tells a driving agent whether the package or the data cache is stale.",
|
|
51776
|
+
inputSchema: {}
|
|
51777
|
+
}, async () => {
|
|
51778
|
+
const latest = await checkLatestVersion();
|
|
51779
|
+
return { content: [{ type: "text", text: JSON.stringify({ latest, note: "package updates via `npx blitzstrike` (always-latest) or `npm i -g blitzstrike@latest`; heavy data via `blitzstrike sync-data`" }) }] };
|
|
51780
|
+
});
|
|
51610
51781
|
return server;
|
|
51611
51782
|
}
|
|
51612
51783
|
async function serve() {
|
|
@@ -51616,14 +51787,14 @@ async function serve() {
|
|
|
51616
51787
|
}
|
|
51617
51788
|
|
|
51618
51789
|
// src/cli.ts
|
|
51619
|
-
import { readFileSync as
|
|
51620
|
-
import { join as
|
|
51621
|
-
import { homedir as
|
|
51622
|
-
import { fileURLToPath as
|
|
51623
|
-
import { spawnSync as
|
|
51624
|
-
var
|
|
51790
|
+
import { readFileSync as readFileSync13, existsSync as existsSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "node:fs";
|
|
51791
|
+
import { join as join11, dirname as dirname2 } from "node:path";
|
|
51792
|
+
import { homedir as homedir5 } from "node:os";
|
|
51793
|
+
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
51794
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
51795
|
+
var ROOT6 = join11(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
51625
51796
|
function which(cmd) {
|
|
51626
|
-
return
|
|
51797
|
+
return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
|
|
51627
51798
|
}
|
|
51628
51799
|
function installedCount() {
|
|
51629
51800
|
const tools = loadTools();
|
|
@@ -51656,7 +51827,7 @@ function runDoctor() {
|
|
|
51656
51827
|
detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
|
|
51657
51828
|
fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
|
|
51658
51829
|
});
|
|
51659
|
-
const dataOk =
|
|
51830
|
+
const dataOk = existsSync6(join11(ROOT6, "chains.json")) && existsSync6(join11(ROOT6, "tools-catalog.json"));
|
|
51660
51831
|
issues.push({
|
|
51661
51832
|
name: "Data layers (chains + tools-catalog)",
|
|
51662
51833
|
status: dataOk ? "ok" : "fail",
|
|
@@ -51684,73 +51855,73 @@ BlitzStrike — ${verdict}`);
|
|
|
51684
51855
|
function resolveCommand() {
|
|
51685
51856
|
if (which("blitzstrike"))
|
|
51686
51857
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
51687
|
-
const src =
|
|
51688
|
-
if (which("bun") &&
|
|
51858
|
+
const src = join11(ROOT6, "src", "index.ts");
|
|
51859
|
+
if (which("bun") && existsSync6(src))
|
|
51689
51860
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
51690
51861
|
if (which("npx"))
|
|
51691
51862
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
51692
51863
|
if (which("bunx"))
|
|
51693
51864
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
51694
|
-
const dist =
|
|
51695
|
-
if (
|
|
51865
|
+
const dist = join11(ROOT6, "dist", "index.js");
|
|
51866
|
+
if (existsSync6(dist))
|
|
51696
51867
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
51697
51868
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
51698
51869
|
}
|
|
51699
51870
|
function readJson(p) {
|
|
51700
51871
|
try {
|
|
51701
|
-
return JSON.parse(
|
|
51872
|
+
return JSON.parse(readFileSync13(p, "utf8"));
|
|
51702
51873
|
} catch {
|
|
51703
51874
|
return null;
|
|
51704
51875
|
}
|
|
51705
51876
|
}
|
|
51706
51877
|
function jsonMcpServersWrite(p) {
|
|
51707
51878
|
return (command, args) => {
|
|
51708
|
-
const dir =
|
|
51709
|
-
if (!
|
|
51710
|
-
|
|
51879
|
+
const dir = dirname2(p);
|
|
51880
|
+
if (!existsSync6(dir))
|
|
51881
|
+
mkdirSync4(dir, { recursive: true });
|
|
51711
51882
|
const existing = readJson(p) ?? {};
|
|
51712
51883
|
existing.mcpServers = {
|
|
51713
51884
|
...existing.mcpServers ?? {},
|
|
51714
51885
|
blitzstrike: { command, args }
|
|
51715
51886
|
};
|
|
51716
|
-
|
|
51887
|
+
writeFileSync4(p, JSON.stringify(existing, null, 2));
|
|
51717
51888
|
};
|
|
51718
51889
|
}
|
|
51719
51890
|
function opencodeWrite(p) {
|
|
51720
51891
|
return (_command, _args) => {
|
|
51721
|
-
const dir =
|
|
51722
|
-
if (!
|
|
51723
|
-
|
|
51892
|
+
const dir = dirname2(p);
|
|
51893
|
+
if (!existsSync6(dir))
|
|
51894
|
+
mkdirSync4(dir, { recursive: true });
|
|
51724
51895
|
const existing = readJson(p) ?? {};
|
|
51725
51896
|
existing.mcp = {
|
|
51726
51897
|
...existing.mcp ?? {},
|
|
51727
51898
|
blitzstrike: { type: "local", command: ["npx", "-y", "blitzstrike", "serve", "--mcp"], enabled: true }
|
|
51728
51899
|
};
|
|
51729
|
-
|
|
51900
|
+
writeFileSync4(p, JSON.stringify(existing, null, 2));
|
|
51730
51901
|
copyOpenCodeAgents();
|
|
51731
51902
|
};
|
|
51732
51903
|
}
|
|
51733
51904
|
function copyOpenCodeAgents() {
|
|
51734
|
-
const srcDir =
|
|
51735
|
-
const dstDir =
|
|
51736
|
-
if (!
|
|
51905
|
+
const srcDir = join11(ROOT6, "opencode-agents");
|
|
51906
|
+
const dstDir = join11(homedir5(), ".config", "opencode", "agents");
|
|
51907
|
+
if (!existsSync6(srcDir))
|
|
51737
51908
|
return;
|
|
51738
|
-
if (!
|
|
51739
|
-
|
|
51909
|
+
if (!existsSync6(dstDir))
|
|
51910
|
+
mkdirSync4(dstDir, { recursive: true });
|
|
51740
51911
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
51741
|
-
const src =
|
|
51742
|
-
const dst =
|
|
51743
|
-
if (
|
|
51744
|
-
|
|
51912
|
+
const src = join11(srcDir, f);
|
|
51913
|
+
const dst = join11(dstDir, f);
|
|
51914
|
+
if (existsSync6(src)) {
|
|
51915
|
+
writeFileSync4(dst, readFileSync13(src, "utf8"));
|
|
51745
51916
|
}
|
|
51746
51917
|
}
|
|
51747
51918
|
}
|
|
51748
51919
|
function codexWrite(p) {
|
|
51749
51920
|
return (command, args) => {
|
|
51750
|
-
const dir =
|
|
51751
|
-
if (!
|
|
51752
|
-
|
|
51753
|
-
let existing =
|
|
51921
|
+
const dir = dirname2(p);
|
|
51922
|
+
if (!existsSync6(dir))
|
|
51923
|
+
mkdirSync4(dir, { recursive: true });
|
|
51924
|
+
let existing = existsSync6(p) ? readFileSync13(p, "utf8") : "";
|
|
51754
51925
|
if (!existing.trimEnd().endsWith(`
|
|
51755
51926
|
`))
|
|
51756
51927
|
existing += `
|
|
@@ -51761,15 +51932,15 @@ function codexWrite(p) {
|
|
|
51761
51932
|
command = "${command}"
|
|
51762
51933
|
args = ${JSON.stringify(args)}
|
|
51763
51934
|
`;
|
|
51764
|
-
|
|
51935
|
+
writeFileSync4(p, existing);
|
|
51765
51936
|
};
|
|
51766
51937
|
}
|
|
51767
51938
|
function hermesWrite(p) {
|
|
51768
51939
|
return (command, args) => {
|
|
51769
|
-
const dir =
|
|
51770
|
-
if (!
|
|
51771
|
-
|
|
51772
|
-
let existing =
|
|
51940
|
+
const dir = dirname2(p);
|
|
51941
|
+
if (!existsSync6(dir))
|
|
51942
|
+
mkdirSync4(dir, { recursive: true });
|
|
51943
|
+
let existing = existsSync6(p) ? readFileSync13(p, "utf8") : "";
|
|
51773
51944
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
51774
51945
|
if (!existing.trimEnd().endsWith(`
|
|
51775
51946
|
`))
|
|
@@ -51790,80 +51961,80 @@ mcp_servers:
|
|
|
51790
51961
|
args: ${JSON.stringify(args)}
|
|
51791
51962
|
`;
|
|
51792
51963
|
}
|
|
51793
|
-
|
|
51964
|
+
writeFileSync4(p, existing);
|
|
51794
51965
|
};
|
|
51795
51966
|
}
|
|
51796
51967
|
function detectAgents() {
|
|
51797
|
-
const home =
|
|
51968
|
+
const home = homedir5();
|
|
51798
51969
|
const agents = [];
|
|
51799
|
-
const claudeJson =
|
|
51970
|
+
const claudeJson = join11(home, ".claude.json");
|
|
51800
51971
|
agents.push({
|
|
51801
51972
|
name: "Claude Code",
|
|
51802
51973
|
detectPath: claudeJson,
|
|
51803
|
-
installed: () =>
|
|
51974
|
+
installed: () => existsSync6(claudeJson) || which("claude"),
|
|
51804
51975
|
write: jsonMcpServersWrite(claudeJson)
|
|
51805
51976
|
});
|
|
51806
|
-
const claudeDesktop =
|
|
51977
|
+
const claudeDesktop = join11(home, ".config", "Claude", "claude_desktop_config.json");
|
|
51807
51978
|
agents.push({
|
|
51808
51979
|
name: "Claude Desktop",
|
|
51809
51980
|
detectPath: claudeDesktop,
|
|
51810
|
-
installed: () =>
|
|
51981
|
+
installed: () => existsSync6(claudeDesktop),
|
|
51811
51982
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
51812
51983
|
});
|
|
51813
|
-
const cursor =
|
|
51984
|
+
const cursor = join11(home, ".cursor", "mcp.json");
|
|
51814
51985
|
agents.push({
|
|
51815
51986
|
name: "Cursor",
|
|
51816
51987
|
detectPath: cursor,
|
|
51817
|
-
installed: () =>
|
|
51988
|
+
installed: () => existsSync6(join11(home, ".cursor")) || which("cursor"),
|
|
51818
51989
|
write: jsonMcpServersWrite(cursor)
|
|
51819
51990
|
});
|
|
51820
|
-
const opencode =
|
|
51991
|
+
const opencode = join11(home, ".config", "opencode", "opencode.jsonc");
|
|
51821
51992
|
agents.push({
|
|
51822
51993
|
name: "OpenCode",
|
|
51823
51994
|
detectPath: opencode,
|
|
51824
|
-
installed: () =>
|
|
51995
|
+
installed: () => existsSync6(join11(home, ".config", "opencode")) || which("opencode"),
|
|
51825
51996
|
write: opencodeWrite(opencode)
|
|
51826
51997
|
});
|
|
51827
|
-
const codex =
|
|
51998
|
+
const codex = join11(home, ".codex", "config.toml");
|
|
51828
51999
|
agents.push({
|
|
51829
52000
|
name: "Codex",
|
|
51830
52001
|
detectPath: codex,
|
|
51831
|
-
installed: () =>
|
|
52002
|
+
installed: () => existsSync6(join11(home, ".codex")) || which("codex"),
|
|
51832
52003
|
write: codexWrite(codex)
|
|
51833
52004
|
});
|
|
51834
|
-
const hermes =
|
|
52005
|
+
const hermes = join11(home, ".hermes", "config.yaml");
|
|
51835
52006
|
agents.push({
|
|
51836
52007
|
name: "Hermes",
|
|
51837
52008
|
detectPath: hermes,
|
|
51838
|
-
installed: () =>
|
|
52009
|
+
installed: () => existsSync6(join11(home, ".hermes")) || which("hermes"),
|
|
51839
52010
|
write: hermesWrite(hermes)
|
|
51840
52011
|
});
|
|
51841
|
-
const gemini =
|
|
52012
|
+
const gemini = join11(home, ".gemini", "settings.json");
|
|
51842
52013
|
agents.push({
|
|
51843
52014
|
name: "Gemini CLI",
|
|
51844
52015
|
detectPath: gemini,
|
|
51845
|
-
installed: () =>
|
|
52016
|
+
installed: () => existsSync6(join11(home, ".gemini")) || which("gemini"),
|
|
51846
52017
|
write: jsonMcpServersWrite(gemini)
|
|
51847
52018
|
});
|
|
51848
|
-
const windsurf =
|
|
52019
|
+
const windsurf = join11(home, ".codeium", "windsurf", "mcp_config.json");
|
|
51849
52020
|
agents.push({
|
|
51850
52021
|
name: "Windsurf",
|
|
51851
52022
|
detectPath: windsurf,
|
|
51852
|
-
installed: () =>
|
|
52023
|
+
installed: () => existsSync6(join11(home, ".codeium")) || which("windsurf"),
|
|
51853
52024
|
write: jsonMcpServersWrite(windsurf)
|
|
51854
52025
|
});
|
|
51855
|
-
const copilot =
|
|
52026
|
+
const copilot = join11(home, ".copilot", "mcp.json");
|
|
51856
52027
|
agents.push({
|
|
51857
52028
|
name: "Copilot",
|
|
51858
52029
|
detectPath: copilot,
|
|
51859
|
-
installed: () =>
|
|
52030
|
+
installed: () => existsSync6(join11(home, ".copilot")),
|
|
51860
52031
|
write: jsonMcpServersWrite(copilot)
|
|
51861
52032
|
});
|
|
51862
|
-
const cline =
|
|
52033
|
+
const cline = join11(home, ".cline", "mcp_settings.json");
|
|
51863
52034
|
agents.push({
|
|
51864
52035
|
name: "Cline",
|
|
51865
52036
|
detectPath: cline,
|
|
51866
|
-
installed: () =>
|
|
52037
|
+
installed: () => existsSync6(join11(home, ".cline")),
|
|
51867
52038
|
write: jsonMcpServersWrite(cline)
|
|
51868
52039
|
});
|
|
51869
52040
|
return agents;
|
|
@@ -51907,29 +52078,31 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
51907
52078
|
}
|
|
51908
52079
|
|
|
51909
52080
|
// src/index.ts
|
|
51910
|
-
import { readFileSync as
|
|
51911
|
-
import { join as
|
|
51912
|
-
import { fileURLToPath as
|
|
51913
|
-
var ROOT7 =
|
|
52081
|
+
import { readFileSync as readFileSync14, existsSync as existsSync7 } from "node:fs";
|
|
52082
|
+
import { join as join12 } from "node:path";
|
|
52083
|
+
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
52084
|
+
var ROOT7 = join12(fileURLToPath9(new URL(".", import.meta.url)), "..");
|
|
51914
52085
|
function readVersion() {
|
|
51915
52086
|
try {
|
|
51916
|
-
const p =
|
|
51917
|
-
if (
|
|
51918
|
-
return JSON.parse(
|
|
52087
|
+
const p = join12(ROOT7, "package.json");
|
|
52088
|
+
if (existsSync7(p))
|
|
52089
|
+
return JSON.parse(readFileSync14(p, "utf8")).version ?? "1.0.0";
|
|
51919
52090
|
} catch {}
|
|
51920
52091
|
return "1.0.0";
|
|
51921
52092
|
}
|
|
51922
|
-
var
|
|
52093
|
+
var VERSION2 = readVersion();
|
|
51923
52094
|
function usage() {
|
|
51924
|
-
return `BlitzStrike ${
|
|
52095
|
+
return `BlitzStrike ${VERSION2} — MCP security-audit toolbelt
|
|
51925
52096
|
|
|
51926
52097
|
Usage:
|
|
51927
52098
|
blitzstrike serve --mcp Start the MCP server over stdio (default transport)
|
|
51928
52099
|
blitzstrike doctor Environment health check (runtime + tools + creds)
|
|
51929
52100
|
blitzstrike install Write MCP client config to detected clients
|
|
51930
52101
|
blitzstrike install --dry-run Print the config without writing
|
|
51931
|
-
blitzstrike
|
|
51932
|
-
blitzstrike
|
|
52102
|
+
blitzstrike sync-data Fetch heavy datasets (payloads + templates) on-demand
|
|
52103
|
+
blitzstrike update Check for a newer version + refresh the data cache
|
|
52104
|
+
blitzstrike version Print version
|
|
52105
|
+
blitzstrike --help This help`;
|
|
51933
52106
|
}
|
|
51934
52107
|
async function main() {
|
|
51935
52108
|
const args = process.argv.slice(2);
|
|
@@ -51938,7 +52111,7 @@ async function main() {
|
|
|
51938
52111
|
return;
|
|
51939
52112
|
}
|
|
51940
52113
|
if (args[0] === "version" || args[0] === "--version" || args[0] === "-v") {
|
|
51941
|
-
console.log(`blitzstrike ${
|
|
52114
|
+
console.log(`blitzstrike ${VERSION2}`);
|
|
51942
52115
|
return;
|
|
51943
52116
|
}
|
|
51944
52117
|
if (args[0] === "doctor") {
|
|
@@ -51958,6 +52131,22 @@ async function main() {
|
|
|
51958
52131
|
console.log(` templates: ${r.templates.files} files ${r.templates.synced ? "(synced)" : "(already present)"}`);
|
|
51959
52132
|
return;
|
|
51960
52133
|
}
|
|
52134
|
+
if (args[0] === "update") {
|
|
52135
|
+
await Promise.resolve().then(() => init_update());
|
|
52136
|
+
const r = await runUpdate(VERSION2);
|
|
52137
|
+
console.log("Blitz Strike self-update");
|
|
52138
|
+
console.log(` current: ${r.current}`);
|
|
52139
|
+
console.log(` latest: ${r.latest ?? "unknown (registry unreachable)"}`);
|
|
52140
|
+
if (r.outdated) {
|
|
52141
|
+
console.log(` status: OUTDATED — update with: ${r.update_command}`);
|
|
52142
|
+
} else if (r.latest === null) {
|
|
52143
|
+
console.log(` status: could not reach npm registry`);
|
|
52144
|
+
} else {
|
|
52145
|
+
console.log(` status: up to date`);
|
|
52146
|
+
}
|
|
52147
|
+
console.log(` data: payloads ${r.data_sync.payloads.files} files, templates ${r.data_sync.templates.files} files ${r.data_sync.payloads.synced ? "(refreshed)" : ""}`);
|
|
52148
|
+
return;
|
|
52149
|
+
}
|
|
51961
52150
|
if (args[0] === "serve") {
|
|
51962
52151
|
await serve();
|
|
51963
52152
|
return;
|
package/docs/findings.md
CHANGED
|
@@ -76,12 +76,33 @@ validation + evidence.
|
|
|
76
76
|
|
|
77
77
|
`src/evidence.ts`:
|
|
78
78
|
|
|
79
|
-
- **Schema** — `evidence_id`, `type`, `description`, `source`, `sink`, `artifacts[]`, `recorded`.
|
|
79
|
+
- **Schema** — `evidence_id`, `type`, `description`, `source`, `sink`, `artifacts[]`, `recorded`, `provenance`.
|
|
80
|
+
- **Provenance (§16)** — every record carries chain-of-custody metadata: `tool`,
|
|
81
|
+
`version`, `target`, `scope`, `parentEvidence`.
|
|
80
82
|
- **Integrity** — every artifact carries a SHA-256 (`sha256()`), verified by `verifyEvidence()`.
|
|
81
83
|
- **Secret redaction** — `redactSecrets()` strips passwords, API keys, tokens,
|
|
82
84
|
cookies, `Authorization` headers, private keys before persistence/report/log/context.
|
|
83
85
|
- **Append-only** — confirmed evidence is never silently rewritten.
|
|
84
86
|
|
|
87
|
+
## Finding invariants (§53)
|
|
88
|
+
|
|
89
|
+
`src/invariants.ts` enforces hard, machine-checkable rules:
|
|
90
|
+
|
|
91
|
+
| Invariant | Enforcement |
|
|
92
|
+
| :--- | :--- |
|
|
93
|
+
| CONFIRMED requires evidence | `confirmFinding` throws without evidence |
|
|
94
|
+
| CONFIRMED requires clean negative control | `checkFindingInvariants` |
|
|
95
|
+
| CONFIRMED requires in-scope target | `checkFindingInvariants` |
|
|
96
|
+
| No raw secret in persisted evidence | `checkFindingInvariants` |
|
|
97
|
+
| Evidence hash must stay stable | `checkFindingInvariants` |
|
|
98
|
+
|
|
99
|
+
## Audit log (§45)
|
|
100
|
+
|
|
101
|
+
`src/audit.ts` emits structured append-only audit events to
|
|
102
|
+
`~/.blitzstrike/audit.jsonl`: `scope_checked`, `finding_created`,
|
|
103
|
+
`finding_updated`, `validation_finished`, `evidence_created`, … each carrying
|
|
104
|
+
timestamp, actor, tool, target, result, and correlationId.
|
|
105
|
+
|
|
85
106
|
## MCP tools
|
|
86
107
|
|
|
87
108
|
| Tool | Purpose |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.33",
|
|
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": {
|