blitzstrike 1.0.32 → 1.0.34
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 +26 -0
- package/dist/index.js +280 -173
- 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
|
package/dist/index.js
CHANGED
|
@@ -14820,10 +14820,10 @@ var require_src = __commonJS(function(exports, module) {
|
|
|
14820
14820
|
});
|
|
14821
14821
|
|
|
14822
14822
|
// src/sync.ts
|
|
14823
|
-
import { mkdirSync as
|
|
14824
|
-
import { join as
|
|
14825
|
-
import { homedir as
|
|
14826
|
-
import { fileURLToPath 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
|
+
import { homedir as homedir4 } from "node:os";
|
|
14826
|
+
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
14827
14827
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
14828
14828
|
function dirExists(p) {
|
|
14829
14829
|
try {
|
|
@@ -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
|
}
|
|
@@ -14851,9 +14851,9 @@ function countFiles(dir) {
|
|
|
14851
14851
|
}
|
|
14852
14852
|
function syncData() {
|
|
14853
14853
|
if (!dirExists(DATA_ROOT2))
|
|
14854
|
-
|
|
14855
|
-
const payloadsPath =
|
|
14856
|
-
const templatesPath =
|
|
14854
|
+
mkdirSync3(DATA_ROOT2, { recursive: true });
|
|
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)) {
|
|
@@ -14877,7 +14877,7 @@ function syncData() {
|
|
|
14877
14877
|
}
|
|
14878
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
14883
|
const r = spawnSync2("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
@@ -14885,11 +14885,11 @@ function syncData() {
|
|
|
14885
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
|
}
|
|
@@ -14909,20 +14909,20 @@ function copyDir(src, dst) {
|
|
|
14909
14909
|
}
|
|
14910
14910
|
}
|
|
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
|
-
writeFileSync3(d,
|
|
14919
|
+
writeFileSync3(d, readFileSync12(s));
|
|
14920
14920
|
}
|
|
14921
14921
|
}
|
|
14922
14922
|
var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
14923
14923
|
var init_sync = __esm(() => {
|
|
14924
|
-
ROOT5 =
|
|
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
14926
|
});
|
|
14927
14927
|
|
|
14928
14928
|
// src/update.ts
|
|
@@ -29683,9 +29683,9 @@ async function nvdLookup(cveId) {
|
|
|
29683
29683
|
|
|
29684
29684
|
// src/orchestrator.ts
|
|
29685
29685
|
init_scanner();
|
|
29686
|
-
import { readFileSync as
|
|
29687
|
-
import { join as
|
|
29688
|
-
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";
|
|
29689
29689
|
|
|
29690
29690
|
// src/memory.ts
|
|
29691
29691
|
import { readFileSync as readFileSync2, appendFileSync, mkdirSync, existsSync, writeFileSync } from "node:fs";
|
|
@@ -29917,6 +29917,47 @@ function manualForTool(toolName) {
|
|
|
29917
29917
|
return null;
|
|
29918
29918
|
}
|
|
29919
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
|
+
|
|
29920
29961
|
// src/evidence.ts
|
|
29921
29962
|
import { createHash as createHash2 } from "node:crypto";
|
|
29922
29963
|
var REDACTION_PATTERNS = [
|
|
@@ -29943,6 +29984,9 @@ function redactSecrets(text) {
|
|
|
29943
29984
|
}
|
|
29944
29985
|
return out;
|
|
29945
29986
|
}
|
|
29987
|
+
function hasSecrets(text) {
|
|
29988
|
+
return redactSecrets(text) !== text;
|
|
29989
|
+
}
|
|
29946
29990
|
function redactObject(value) {
|
|
29947
29991
|
if (typeof value === "string")
|
|
29948
29992
|
return redactSecrets(value);
|
|
@@ -29977,9 +30021,61 @@ function makeEvidence(input) {
|
|
|
29977
30021
|
source: input.source ? redactObject(input.source) : undefined,
|
|
29978
30022
|
sink: input.sink ? redactObject(input.sink) : undefined,
|
|
29979
30023
|
artifacts,
|
|
29980
|
-
recorded: new Date().toISOString()
|
|
30024
|
+
recorded: new Date().toISOString(),
|
|
30025
|
+
provenance: {
|
|
30026
|
+
tool: input.provenance?.tool ?? "blitzstrike",
|
|
30027
|
+
version: input.provenance?.version ?? VERSION,
|
|
30028
|
+
...input.provenance?.target ? { target: input.provenance.target } : {},
|
|
30029
|
+
...input.provenance?.scope ? { scope: input.provenance.scope } : {},
|
|
30030
|
+
...input.provenance?.parentEvidence ? { parentEvidence: input.provenance.parentEvidence } : {}
|
|
30031
|
+
}
|
|
29981
30032
|
};
|
|
29982
30033
|
}
|
|
30034
|
+
function verifyArtifact(artifact) {
|
|
30035
|
+
return sha256(artifact.content) === artifact.sha256;
|
|
30036
|
+
}
|
|
30037
|
+
function verifyEvidence(evidence) {
|
|
30038
|
+
const failed = [];
|
|
30039
|
+
for (const a of evidence.artifacts) {
|
|
30040
|
+
if (!verifyArtifact(a))
|
|
30041
|
+
failed.push(a.name);
|
|
30042
|
+
}
|
|
30043
|
+
return { ok: failed.length === 0, failed };
|
|
30044
|
+
}
|
|
30045
|
+
|
|
30046
|
+
// src/invariants.ts
|
|
30047
|
+
function checkFindingInvariants(finding) {
|
|
30048
|
+
const violations = [];
|
|
30049
|
+
if (finding.status === "confirmed" && finding.evidence.length === 0) {
|
|
30050
|
+
violations.push({ invariant: "confirmed-requires-evidence", detail: `finding ${finding.id} is confirmed with no evidence` });
|
|
30051
|
+
}
|
|
30052
|
+
if (finding.status === "confirmed" && finding.validation.negative_control === false) {
|
|
30053
|
+
violations.push({ invariant: "confirmed-requires-clean-negative-control", detail: `finding ${finding.id} confirmed with a failed negative control` });
|
|
30054
|
+
}
|
|
30055
|
+
if (finding.status === "confirmed" && finding.validation.scope_allowed === false) {
|
|
30056
|
+
violations.push({ invariant: "confirmed-requires-scope-allowed", detail: `finding ${finding.id} confirmed out of scope` });
|
|
30057
|
+
}
|
|
30058
|
+
for (const e of finding.evidence) {
|
|
30059
|
+
for (const a of e.artifacts) {
|
|
30060
|
+
if (hasSecrets(a.content)) {
|
|
30061
|
+
violations.push({ invariant: "no-secret-in-evidence", detail: `evidence ${e.evidence_id} artifact ${a.name} contains a raw secret` });
|
|
30062
|
+
}
|
|
30063
|
+
}
|
|
30064
|
+
}
|
|
30065
|
+
for (const e of finding.evidence) {
|
|
30066
|
+
const v = verifyEvidence(e);
|
|
30067
|
+
if (!v.ok) {
|
|
30068
|
+
violations.push({ invariant: "evidence-hash-stable", detail: `evidence ${e.evidence_id} artifacts ${v.failed.join(",")} failed integrity check` });
|
|
30069
|
+
}
|
|
30070
|
+
}
|
|
30071
|
+
return violations;
|
|
30072
|
+
}
|
|
30073
|
+
function assertFindingInvariants(finding) {
|
|
30074
|
+
const violations = checkFindingInvariants(finding);
|
|
30075
|
+
if (violations.length > 0) {
|
|
30076
|
+
throw new Error(`invariant violation: ${violations.map((v) => v.invariant).join(", ")}`);
|
|
30077
|
+
}
|
|
30078
|
+
}
|
|
29983
30079
|
|
|
29984
30080
|
// src/finding.ts
|
|
29985
30081
|
var LIFECYCLE_TRANSITIONS = {
|
|
@@ -30041,7 +30137,7 @@ function makeFinding(input) {
|
|
|
30041
30137
|
const status = input.status ?? "detected";
|
|
30042
30138
|
const factors = confidenceFactorsFor(input);
|
|
30043
30139
|
const confidence = computeConfidence(factors);
|
|
30044
|
-
|
|
30140
|
+
const finding = {
|
|
30045
30141
|
id: nextFindingId(),
|
|
30046
30142
|
status,
|
|
30047
30143
|
title: input.title,
|
|
@@ -30064,6 +30160,8 @@ function makeFinding(input) {
|
|
|
30064
30160
|
remediation: {},
|
|
30065
30161
|
timestamps: { created: now, updated: now }
|
|
30066
30162
|
};
|
|
30163
|
+
recordAudit("finding_created", { tool: "eagle-eye", target: input.target.host ?? input.target.endpoint, result: status, correlationId: finding.id });
|
|
30164
|
+
return finding;
|
|
30067
30165
|
}
|
|
30068
30166
|
function confidenceFactorsFor(_input) {
|
|
30069
30167
|
return {
|
|
@@ -30079,10 +30177,12 @@ function transition(finding, to) {
|
|
|
30079
30177
|
if (!canTransition(finding.status, to)) {
|
|
30080
30178
|
throw new Error(`illegal transition ${finding.status} -> ${to}`);
|
|
30081
30179
|
}
|
|
30180
|
+
recordAudit("finding_updated", { tool: "blitzstrike", target: finding.target.host ?? finding.target.endpoint, result: `${finding.status} -> ${to}`, correlationId: finding.id });
|
|
30082
30181
|
const updated = { ...finding, status: to, timestamps: { ...finding.timestamps, updated: new Date().toISOString() } };
|
|
30083
30182
|
return updated;
|
|
30084
30183
|
}
|
|
30085
30184
|
function attachEvidence(finding, evidence) {
|
|
30185
|
+
recordAudit("evidence_created", { tool: "blitzstrike", target: finding.target.host ?? finding.target.endpoint, result: evidence.evidence_id, correlationId: finding.id });
|
|
30086
30186
|
return {
|
|
30087
30187
|
...finding,
|
|
30088
30188
|
evidence: [...finding.evidence, evidence],
|
|
@@ -30090,8 +30190,12 @@ function attachEvidence(finding, evidence) {
|
|
|
30090
30190
|
};
|
|
30091
30191
|
}
|
|
30092
30192
|
function confirmFinding(finding, opts) {
|
|
30193
|
+
if (opts.evidence.length === 0) {
|
|
30194
|
+
throw new Error("invariant: cannot confirm a finding without evidence");
|
|
30195
|
+
}
|
|
30093
30196
|
const evidence = opts.evidence.map((e) => makeEvidence(e));
|
|
30094
30197
|
const negativeControl = opts.negative_control ?? true;
|
|
30198
|
+
const scopeAllowed = opts.scope_allowed ?? true;
|
|
30095
30199
|
const confidence = computeConfidence({
|
|
30096
30200
|
static_analysis: true,
|
|
30097
30201
|
data_flow: true,
|
|
@@ -30102,7 +30206,7 @@ function confirmFinding(finding, opts) {
|
|
|
30102
30206
|
});
|
|
30103
30207
|
const level = confidenceLevel(confidence);
|
|
30104
30208
|
const status = negativeControl ? "confirmed" : "hypothesis";
|
|
30105
|
-
|
|
30209
|
+
const confirmed = {
|
|
30106
30210
|
...finding,
|
|
30107
30211
|
status,
|
|
30108
30212
|
confidence,
|
|
@@ -30111,11 +30215,15 @@ function confirmFinding(finding, opts) {
|
|
|
30111
30215
|
performed: true,
|
|
30112
30216
|
status: negativeControl ? "confirmed" : "unconfirmed",
|
|
30113
30217
|
baseline: opts.baseline ?? true,
|
|
30114
|
-
negative_control: negativeControl
|
|
30218
|
+
negative_control: negativeControl,
|
|
30219
|
+
scope_allowed: scopeAllowed
|
|
30115
30220
|
},
|
|
30116
30221
|
evidence: [...finding.evidence, ...evidence],
|
|
30117
30222
|
timestamps: { ...finding.timestamps, updated: new Date().toISOString() }
|
|
30118
30223
|
};
|
|
30224
|
+
assertFindingInvariants(confirmed);
|
|
30225
|
+
recordAudit("validation_finished", { tool: "strike", target: finding.target.host ?? finding.target.endpoint, result: status, correlationId: finding.id });
|
|
30226
|
+
return confirmed;
|
|
30119
30227
|
}
|
|
30120
30228
|
function rejectFinding(finding, status, reason, evidence) {
|
|
30121
30229
|
const ev = evidence?.map((e) => makeEvidence(e)) ?? [];
|
|
@@ -30135,9 +30243,9 @@ function loadChains() {
|
|
|
30135
30243
|
if (_chainsCache)
|
|
30136
30244
|
return _chainsCache;
|
|
30137
30245
|
try {
|
|
30138
|
-
const dir =
|
|
30139
|
-
const path =
|
|
30140
|
-
const raw = JSON.parse(
|
|
30246
|
+
const dir = join6(fileURLToPath3(new URL(".", import.meta.url)), "..");
|
|
30247
|
+
const path = join6(dir, "chains.json");
|
|
30248
|
+
const raw = JSON.parse(readFileSync6(path, "utf8"));
|
|
30141
30249
|
_chainsCache = raw.chains ?? [];
|
|
30142
30250
|
return _chainsCache;
|
|
30143
30251
|
} catch {
|
|
@@ -30146,40 +30254,50 @@ function loadChains() {
|
|
|
30146
30254
|
}
|
|
30147
30255
|
}
|
|
30148
30256
|
function scopeCheck(target, scope = "", mode = "bug-bounty") {
|
|
30257
|
+
let result;
|
|
30149
30258
|
if (mode === "ctf" || mode === "reverse-engineering" || mode === "offensive") {
|
|
30150
|
-
|
|
30151
|
-
}
|
|
30152
|
-
|
|
30153
|
-
|
|
30154
|
-
|
|
30155
|
-
|
|
30156
|
-
|
|
30157
|
-
|
|
30158
|
-
|
|
30159
|
-
|
|
30160
|
-
|
|
30161
|
-
|
|
30162
|
-
|
|
30163
|
-
|
|
30164
|
-
|
|
30165
|
-
|
|
30166
|
-
|
|
30167
|
-
|
|
30168
|
-
|
|
30169
|
-
|
|
30170
|
-
|
|
30171
|
-
|
|
30259
|
+
result = { target, allowed: true, enforcement: "off", reason: `mode=${mode} disables scope enforcement` };
|
|
30260
|
+
} else if (!scope.trim()) {
|
|
30261
|
+
result = { target, allowed: false, enforcement: "strict", reason: "no scope provided; refusing active testing" };
|
|
30262
|
+
} else {
|
|
30263
|
+
const inScope = [];
|
|
30264
|
+
const outScope = [];
|
|
30265
|
+
for (const raw of scope.split(/[,\n]/)) {
|
|
30266
|
+
const line = raw.trim();
|
|
30267
|
+
if (!line)
|
|
30268
|
+
continue;
|
|
30269
|
+
if (line.startsWith("-"))
|
|
30270
|
+
outScope.push(line.slice(1).trim());
|
|
30271
|
+
else
|
|
30272
|
+
inScope.push(line);
|
|
30273
|
+
}
|
|
30274
|
+
let host = target;
|
|
30275
|
+
host = host.replace(/^https?:\/\//, "");
|
|
30276
|
+
host = host.split("/")[0].split(":")[0];
|
|
30277
|
+
let decided = false;
|
|
30278
|
+
result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' not in scope` };
|
|
30279
|
+
for (const exc of outScope) {
|
|
30280
|
+
if (exc && (host.includes(exc) || host.endsWith("." + exc.replace(/^\./, "")))) {
|
|
30281
|
+
result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' is explicitly out of scope (${exc})` };
|
|
30282
|
+
decided = true;
|
|
30283
|
+
break;
|
|
30284
|
+
}
|
|
30172
30285
|
}
|
|
30173
|
-
|
|
30174
|
-
|
|
30175
|
-
|
|
30176
|
-
|
|
30177
|
-
|
|
30178
|
-
|
|
30179
|
-
|
|
30286
|
+
if (!decided) {
|
|
30287
|
+
for (const allowed of inScope) {
|
|
30288
|
+
const a = allowed.replace(/\*/g, "");
|
|
30289
|
+
if (!a)
|
|
30290
|
+
continue;
|
|
30291
|
+
if (host.includes(a) || host.endsWith("." + a.replace(/^\./, ""))) {
|
|
30292
|
+
result = { target, allowed: true, enforcement: "strict", reason: `matched in-scope pattern '${allowed}'` };
|
|
30293
|
+
decided = true;
|
|
30294
|
+
break;
|
|
30295
|
+
}
|
|
30296
|
+
}
|
|
30180
30297
|
}
|
|
30181
30298
|
}
|
|
30182
|
-
|
|
30299
|
+
recordAudit("scope_checked", { tool: "scope", target, result: result.allowed ? "allowed" : "denied" });
|
|
30300
|
+
return result;
|
|
30183
30301
|
}
|
|
30184
30302
|
function classifySink(sink) {
|
|
30185
30303
|
if (/(move_uploaded_file|file_put_contents|fwrite)/.test(sink))
|
|
@@ -30243,7 +30361,7 @@ function runEngagement(target, scope = "", mode = "bug-bounty", maxFiles = 2000,
|
|
|
30243
30361
|
};
|
|
30244
30362
|
const isPath = (() => {
|
|
30245
30363
|
try {
|
|
30246
|
-
return
|
|
30364
|
+
return existsSync4(target) && (statSync2(target).isDirectory() || statSync2(target).isFile());
|
|
30247
30365
|
} catch {
|
|
30248
30366
|
return false;
|
|
30249
30367
|
}
|
|
@@ -30360,18 +30478,18 @@ function listChains() {
|
|
|
30360
30478
|
}
|
|
30361
30479
|
|
|
30362
30480
|
// src/catalog.ts
|
|
30363
|
-
import { readFileSync as
|
|
30364
|
-
import { join as
|
|
30365
|
-
import { fileURLToPath as
|
|
30481
|
+
import { readFileSync as readFileSync7, readdirSync as readdirSync2 } from "node:fs";
|
|
30482
|
+
import { join as join7 } from "node:path";
|
|
30483
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
30366
30484
|
import { spawnSync } from "node:child_process";
|
|
30367
|
-
var ROOT2 =
|
|
30485
|
+
var ROOT2 = join7(fileURLToPath4(new URL(".", import.meta.url)), "..");
|
|
30368
30486
|
var _toolsCache = null;
|
|
30369
30487
|
var _skillsCache = null;
|
|
30370
30488
|
function loadTools() {
|
|
30371
30489
|
if (_toolsCache)
|
|
30372
30490
|
return _toolsCache;
|
|
30373
30491
|
try {
|
|
30374
|
-
const raw = JSON.parse(
|
|
30492
|
+
const raw = JSON.parse(readFileSync7(join7(ROOT2, "tools-catalog.json"), "utf8"));
|
|
30375
30493
|
_toolsCache = raw.tools ?? [];
|
|
30376
30494
|
} catch {
|
|
30377
30495
|
_toolsCache = [];
|
|
@@ -30381,14 +30499,14 @@ function loadTools() {
|
|
|
30381
30499
|
function loadSkills() {
|
|
30382
30500
|
if (_skillsCache)
|
|
30383
30501
|
return _skillsCache;
|
|
30384
|
-
const dir =
|
|
30502
|
+
const dir = join7(ROOT2, "skills");
|
|
30385
30503
|
const out = [];
|
|
30386
30504
|
try {
|
|
30387
30505
|
for (const f of readdirSync2(dir)) {
|
|
30388
30506
|
if (!f.endsWith(".md"))
|
|
30389
30507
|
continue;
|
|
30390
|
-
const p =
|
|
30391
|
-
const text =
|
|
30508
|
+
const p = join7(dir, f);
|
|
30509
|
+
const text = readFileSync7(p, "utf8");
|
|
30392
30510
|
const descMatch = text.match(/^description:\s*(.+)$/m);
|
|
30393
30511
|
out.push({
|
|
30394
30512
|
name: f.replace(/\.md$/, ""),
|
|
@@ -30461,7 +30579,7 @@ function readSkill(name) {
|
|
|
30461
30579
|
if (!s)
|
|
30462
30580
|
return { found: false, query: name };
|
|
30463
30581
|
try {
|
|
30464
|
-
const text =
|
|
30582
|
+
const text = readFileSync7(join7(ROOT2, "skills", s.file), "utf8");
|
|
30465
30583
|
return { found: true, name: s.name, content: text.slice(0, 12000) };
|
|
30466
30584
|
} catch {
|
|
30467
30585
|
return { found: false, query: name, error: "unreadable" };
|
|
@@ -30494,19 +30612,19 @@ function ensureTool(name) {
|
|
|
30494
30612
|
}
|
|
30495
30613
|
|
|
30496
30614
|
// src/intel.ts
|
|
30497
|
-
import { readFileSync as
|
|
30498
|
-
import { join as
|
|
30499
|
-
import { fileURLToPath as
|
|
30615
|
+
import { readFileSync as readFileSync8, existsSync as existsSync5 } from "node:fs";
|
|
30616
|
+
import { join as join8 } from "node:path";
|
|
30617
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
30500
30618
|
import { readdirSync as readdirSync3 } from "node:fs";
|
|
30501
|
-
import { homedir as
|
|
30502
|
-
var ROOT3 =
|
|
30503
|
-
var INTEL =
|
|
30619
|
+
import { homedir as homedir3 } from "node:os";
|
|
30620
|
+
var ROOT3 = join8(fileURLToPath5(new URL(".", import.meta.url)), "..");
|
|
30621
|
+
var INTEL = join8(ROOT3, "intelligence");
|
|
30504
30622
|
function loadJson(name) {
|
|
30505
|
-
const p =
|
|
30506
|
-
if (!
|
|
30623
|
+
const p = join8(INTEL, `${name}.json`);
|
|
30624
|
+
if (!existsSync5(p))
|
|
30507
30625
|
return null;
|
|
30508
30626
|
try {
|
|
30509
|
-
return JSON.parse(
|
|
30627
|
+
return JSON.parse(readFileSync8(p, "utf8"));
|
|
30510
30628
|
} catch {
|
|
30511
30629
|
return null;
|
|
30512
30630
|
}
|
|
@@ -30638,18 +30756,18 @@ function attackVectors(category) {
|
|
|
30638
30756
|
}
|
|
30639
30757
|
return { found: true, id: found.id, title: found.title, count: found.vectors.length, vectors: found.vectors };
|
|
30640
30758
|
}
|
|
30641
|
-
var PACKAGE_PAYLOADS =
|
|
30642
|
-
var PACKAGE_TEMPLATES =
|
|
30643
|
-
var DATA_ROOT = process.env.BLITZSTRIKE_DATA ??
|
|
30759
|
+
var PACKAGE_PAYLOADS = join8(ROOT3, "payloads");
|
|
30760
|
+
var PACKAGE_TEMPLATES = join8(ROOT3, "templates");
|
|
30761
|
+
var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join8(homedir3(), ".blitzstrike", "data");
|
|
30644
30762
|
function payloadsDir() {
|
|
30645
|
-
if (
|
|
30763
|
+
if (existsSync5(PACKAGE_PAYLOADS))
|
|
30646
30764
|
return PACKAGE_PAYLOADS;
|
|
30647
|
-
return
|
|
30765
|
+
return join8(DATA_ROOT, "payloads");
|
|
30648
30766
|
}
|
|
30649
30767
|
function templatesDir() {
|
|
30650
|
-
if (
|
|
30768
|
+
if (existsSync5(PACKAGE_TEMPLATES))
|
|
30651
30769
|
return PACKAGE_TEMPLATES;
|
|
30652
|
-
return
|
|
30770
|
+
return join8(DATA_ROOT, "templates");
|
|
30653
30771
|
}
|
|
30654
30772
|
var PAYLOADS = payloadsDir();
|
|
30655
30773
|
var TEMPLATES = templatesDir();
|
|
@@ -30685,7 +30803,7 @@ function payloadLookup(topic) {
|
|
|
30685
30803
|
const normalized = ALIASES[key] ?? key;
|
|
30686
30804
|
const exact = cats.find((c) => c.toLowerCase() === normalized);
|
|
30687
30805
|
if (exact) {
|
|
30688
|
-
return { found: true, category: exact, files: listFiles(
|
|
30806
|
+
return { found: true, category: exact, files: listFiles(join8(PAYLOADS, exact)) };
|
|
30689
30807
|
}
|
|
30690
30808
|
const keyTokens = normalized.split(/[^a-z0-9]+/).filter((t) => t.length >= 3);
|
|
30691
30809
|
const matches = cats.filter((c) => {
|
|
@@ -30695,7 +30813,7 @@ function payloadLookup(topic) {
|
|
|
30695
30813
|
return keyTokens.some((tok) => new RegExp(`(^|[^a-z0-9])${tok}`).test(cl));
|
|
30696
30814
|
});
|
|
30697
30815
|
if (matches.length > 0) {
|
|
30698
|
-
return { found: true, category: matches[0], files: listFiles(
|
|
30816
|
+
return { found: true, category: matches[0], files: listFiles(join8(PAYLOADS, matches[0])), alternatives: matches.slice(0, 10) };
|
|
30699
30817
|
}
|
|
30700
30818
|
return { found: false, topic, suggestions: cats.slice(0, 20) };
|
|
30701
30819
|
}
|
|
@@ -30704,19 +30822,19 @@ function readPayload(category, file) {
|
|
|
30704
30822
|
const exact = cats.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
30705
30823
|
if (!exact)
|
|
30706
30824
|
return { found: false, category, suggestions: cats.slice(0, 20) };
|
|
30707
|
-
const dir =
|
|
30825
|
+
const dir = join8(PAYLOADS, exact);
|
|
30708
30826
|
const files = listFiles(dir);
|
|
30709
30827
|
if (!file) {
|
|
30710
30828
|
const readme = files.find((f) => f.toLowerCase() === "readme.md");
|
|
30711
30829
|
if (readme) {
|
|
30712
|
-
return { found: true, category: exact, file: readme, content:
|
|
30830
|
+
return { found: true, category: exact, file: readme, content: readFileSync8(join8(dir, readme), "utf8").slice(0, 20000) };
|
|
30713
30831
|
}
|
|
30714
30832
|
return { found: true, category: exact, files };
|
|
30715
30833
|
}
|
|
30716
30834
|
const f = files.find((x) => x.toLowerCase() === file.toLowerCase());
|
|
30717
30835
|
if (!f)
|
|
30718
30836
|
return { found: false, category: exact, file, suggestions: files };
|
|
30719
|
-
return { found: true, category: exact, file: f, content:
|
|
30837
|
+
return { found: true, category: exact, file: f, content: readFileSync8(join8(dir, f), "utf8").slice(0, 20000) };
|
|
30720
30838
|
}
|
|
30721
30839
|
function listFiles(dir) {
|
|
30722
30840
|
try {
|
|
@@ -30731,7 +30849,7 @@ function countTemplates() {
|
|
|
30731
30849
|
const walk = (dir) => {
|
|
30732
30850
|
for (const e of readdirSync3(dir, { withFileTypes: true })) {
|
|
30733
30851
|
if (e.isDirectory())
|
|
30734
|
-
walk(
|
|
30852
|
+
walk(join8(dir, e.name));
|
|
30735
30853
|
else if (e.name.endsWith(".yaml"))
|
|
30736
30854
|
n++;
|
|
30737
30855
|
}
|
|
@@ -30756,14 +30874,14 @@ function templateLookup(topic, limit = 10) {
|
|
|
30756
30874
|
}
|
|
30757
30875
|
for (const e of entries) {
|
|
30758
30876
|
if (e.isDirectory())
|
|
30759
|
-
walk(
|
|
30877
|
+
walk(join8(dir, e.name), depth + 1);
|
|
30760
30878
|
else if (e.name.endsWith(".yaml")) {
|
|
30761
30879
|
const name = e.name.toLowerCase();
|
|
30762
30880
|
if (name.includes(key)) {
|
|
30763
|
-
const full =
|
|
30881
|
+
const full = join8(dir, e.name);
|
|
30764
30882
|
let id = "";
|
|
30765
30883
|
try {
|
|
30766
|
-
const content =
|
|
30884
|
+
const content = readFileSync8(full, "utf8");
|
|
30767
30885
|
const m = content.match(/^\s*id:\s*(.+)$/m);
|
|
30768
30886
|
if (m)
|
|
30769
30887
|
id = m[1].trim();
|
|
@@ -30923,7 +31041,7 @@ async function activeScan(target, scope = "", mode = "bug-bounty", allowed = und
|
|
|
30923
31041
|
|
|
30924
31042
|
// src/dataflow.ts
|
|
30925
31043
|
init_scanner();
|
|
30926
|
-
import { readFileSync as
|
|
31044
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
30927
31045
|
var SOURCES = {
|
|
30928
31046
|
$_GET: { id: "http_get", label: "HTTP GET parameter", attacker_controlled: true },
|
|
30929
31047
|
$_POST: { id: "http_post", label: "HTTP POST body", attacker_controlled: true },
|
|
@@ -31005,7 +31123,7 @@ function traceDataFlow(path) {
|
|
|
31005
31123
|
return result;
|
|
31006
31124
|
let text;
|
|
31007
31125
|
try {
|
|
31008
|
-
text =
|
|
31126
|
+
text = readFileSync9(path, "utf8");
|
|
31009
31127
|
} catch {
|
|
31010
31128
|
return result;
|
|
31011
31129
|
}
|
|
@@ -31084,7 +31202,7 @@ function groupVariants(results) {
|
|
|
31084
31202
|
// src/taint.ts
|
|
31085
31203
|
init_scanner();
|
|
31086
31204
|
var import_php_parser = __toESM(require_src(), 1);
|
|
31087
|
-
import { readFileSync as
|
|
31205
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
31088
31206
|
var parser = new import_php_parser.Engine({
|
|
31089
31207
|
parser: { extractDoc: false, php7: true },
|
|
31090
31208
|
ast: { withPositions: true, withSource: false }
|
|
@@ -31382,7 +31500,7 @@ function analyzeTaint(path) {
|
|
|
31382
31500
|
const result = { file: path, findings: [], suppressed: 0 };
|
|
31383
31501
|
let code;
|
|
31384
31502
|
try {
|
|
31385
|
-
code =
|
|
31503
|
+
code = readFileSync10(path, "utf8");
|
|
31386
31504
|
} catch {
|
|
31387
31505
|
return result;
|
|
31388
31506
|
}
|
|
@@ -50656,16 +50774,16 @@ function reportJson(findings, opts = {}) {
|
|
|
50656
50774
|
}
|
|
50657
50775
|
|
|
50658
50776
|
// src/benchmark.ts
|
|
50659
|
-
import { readFileSync as
|
|
50660
|
-
import { join as
|
|
50661
|
-
import { fileURLToPath as
|
|
50777
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync4, writeFileSync as writeFileSync2, rmSync } from "node:fs";
|
|
50778
|
+
import { join as join9 } from "node:path";
|
|
50779
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
50662
50780
|
import { tmpdir } from "node:os";
|
|
50663
|
-
var ROOT4 =
|
|
50664
|
-
var CORPUS =
|
|
50781
|
+
var ROOT4 = join9(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
50782
|
+
var CORPUS = join9(ROOT4, "bench", "corpus");
|
|
50665
50783
|
function parseCorpusFile(path) {
|
|
50666
50784
|
let text;
|
|
50667
50785
|
try {
|
|
50668
|
-
text =
|
|
50786
|
+
text = readFileSync11(path, "utf8");
|
|
50669
50787
|
} catch {
|
|
50670
50788
|
return null;
|
|
50671
50789
|
}
|
|
@@ -50715,7 +50833,7 @@ function loadCorpus() {
|
|
|
50715
50833
|
for (const f of readdirSync4(CORPUS)) {
|
|
50716
50834
|
if (!f.endsWith(".md"))
|
|
50717
50835
|
continue;
|
|
50718
|
-
const e = parseCorpusFile(
|
|
50836
|
+
const e = parseCorpusFile(join9(CORPUS, f));
|
|
50719
50837
|
if (e)
|
|
50720
50838
|
entries.push(e);
|
|
50721
50839
|
}
|
|
@@ -50727,7 +50845,7 @@ function loadCorpus() {
|
|
|
50727
50845
|
function detect(entry) {
|
|
50728
50846
|
const lang = entry.language;
|
|
50729
50847
|
if (lang === "php") {
|
|
50730
|
-
const tmp =
|
|
50848
|
+
const tmp = join9(tmpdir(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
50731
50849
|
writeFileSync2(tmp, entry.code);
|
|
50732
50850
|
const r = analyzeTaint(tmp);
|
|
50733
50851
|
rmSync(tmp, { force: true });
|
|
@@ -50875,17 +50993,6 @@ function coverageMatrix() {
|
|
|
50875
50993
|
|
|
50876
50994
|
// src/server.ts
|
|
50877
50995
|
init_update();
|
|
50878
|
-
import { readFileSync as readFileSync11 } from "node:fs";
|
|
50879
|
-
import { join as join9, dirname } from "node:path";
|
|
50880
|
-
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
50881
|
-
var VERSION = (() => {
|
|
50882
|
-
try {
|
|
50883
|
-
const root = join9(dirname(fileURLToPath7(import.meta.url)), "..");
|
|
50884
|
-
return JSON.parse(readFileSync11(join9(root, "package.json"), "utf8")).version ?? "unknown";
|
|
50885
|
-
} catch {
|
|
50886
|
-
return "unknown";
|
|
50887
|
-
}
|
|
50888
|
-
})();
|
|
50889
50996
|
function createServer() {
|
|
50890
50997
|
const server = new McpServer({
|
|
50891
50998
|
name: "blitzstrike",
|
|
@@ -51677,12 +51784,12 @@ async function serve() {
|
|
|
51677
51784
|
}
|
|
51678
51785
|
|
|
51679
51786
|
// src/cli.ts
|
|
51680
|
-
import { readFileSync as
|
|
51681
|
-
import { join as
|
|
51682
|
-
import { homedir as
|
|
51787
|
+
import { readFileSync as readFileSync13, existsSync as existsSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "node:fs";
|
|
51788
|
+
import { join as join11, dirname as dirname2 } from "node:path";
|
|
51789
|
+
import { homedir as homedir5 } from "node:os";
|
|
51683
51790
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
51684
51791
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
51685
|
-
var ROOT6 =
|
|
51792
|
+
var ROOT6 = join11(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
51686
51793
|
function which(cmd) {
|
|
51687
51794
|
return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
|
|
51688
51795
|
}
|
|
@@ -51717,7 +51824,7 @@ function runDoctor() {
|
|
|
51717
51824
|
detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
|
|
51718
51825
|
fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
|
|
51719
51826
|
});
|
|
51720
|
-
const dataOk =
|
|
51827
|
+
const dataOk = existsSync6(join11(ROOT6, "chains.json")) && existsSync6(join11(ROOT6, "tools-catalog.json"));
|
|
51721
51828
|
issues.push({
|
|
51722
51829
|
name: "Data layers (chains + tools-catalog)",
|
|
51723
51830
|
status: dataOk ? "ok" : "fail",
|
|
@@ -51745,21 +51852,21 @@ BlitzStrike — ${verdict}`);
|
|
|
51745
51852
|
function resolveCommand() {
|
|
51746
51853
|
if (which("blitzstrike"))
|
|
51747
51854
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
51748
|
-
const src =
|
|
51749
|
-
if (which("bun") &&
|
|
51855
|
+
const src = join11(ROOT6, "src", "index.ts");
|
|
51856
|
+
if (which("bun") && existsSync6(src))
|
|
51750
51857
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
51751
51858
|
if (which("npx"))
|
|
51752
51859
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
51753
51860
|
if (which("bunx"))
|
|
51754
51861
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
51755
|
-
const dist =
|
|
51756
|
-
if (
|
|
51862
|
+
const dist = join11(ROOT6, "dist", "index.js");
|
|
51863
|
+
if (existsSync6(dist))
|
|
51757
51864
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
51758
51865
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
51759
51866
|
}
|
|
51760
51867
|
function readJson(p) {
|
|
51761
51868
|
try {
|
|
51762
|
-
return JSON.parse(
|
|
51869
|
+
return JSON.parse(readFileSync13(p, "utf8"));
|
|
51763
51870
|
} catch {
|
|
51764
51871
|
return null;
|
|
51765
51872
|
}
|
|
@@ -51767,8 +51874,8 @@ function readJson(p) {
|
|
|
51767
51874
|
function jsonMcpServersWrite(p) {
|
|
51768
51875
|
return (command, args) => {
|
|
51769
51876
|
const dir = dirname2(p);
|
|
51770
|
-
if (!
|
|
51771
|
-
|
|
51877
|
+
if (!existsSync6(dir))
|
|
51878
|
+
mkdirSync4(dir, { recursive: true });
|
|
51772
51879
|
const existing = readJson(p) ?? {};
|
|
51773
51880
|
existing.mcpServers = {
|
|
51774
51881
|
...existing.mcpServers ?? {},
|
|
@@ -51780,8 +51887,8 @@ function jsonMcpServersWrite(p) {
|
|
|
51780
51887
|
function opencodeWrite(p) {
|
|
51781
51888
|
return (_command, _args) => {
|
|
51782
51889
|
const dir = dirname2(p);
|
|
51783
|
-
if (!
|
|
51784
|
-
|
|
51890
|
+
if (!existsSync6(dir))
|
|
51891
|
+
mkdirSync4(dir, { recursive: true });
|
|
51785
51892
|
const existing = readJson(p) ?? {};
|
|
51786
51893
|
existing.mcp = {
|
|
51787
51894
|
...existing.mcp ?? {},
|
|
@@ -51792,26 +51899,26 @@ function opencodeWrite(p) {
|
|
|
51792
51899
|
};
|
|
51793
51900
|
}
|
|
51794
51901
|
function copyOpenCodeAgents() {
|
|
51795
|
-
const srcDir =
|
|
51796
|
-
const dstDir =
|
|
51797
|
-
if (!
|
|
51902
|
+
const srcDir = join11(ROOT6, "opencode-agents");
|
|
51903
|
+
const dstDir = join11(homedir5(), ".config", "opencode", "agents");
|
|
51904
|
+
if (!existsSync6(srcDir))
|
|
51798
51905
|
return;
|
|
51799
|
-
if (!
|
|
51800
|
-
|
|
51906
|
+
if (!existsSync6(dstDir))
|
|
51907
|
+
mkdirSync4(dstDir, { recursive: true });
|
|
51801
51908
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
51802
|
-
const src =
|
|
51803
|
-
const dst =
|
|
51804
|
-
if (
|
|
51805
|
-
writeFileSync4(dst,
|
|
51909
|
+
const src = join11(srcDir, f);
|
|
51910
|
+
const dst = join11(dstDir, f);
|
|
51911
|
+
if (existsSync6(src)) {
|
|
51912
|
+
writeFileSync4(dst, readFileSync13(src, "utf8"));
|
|
51806
51913
|
}
|
|
51807
51914
|
}
|
|
51808
51915
|
}
|
|
51809
51916
|
function codexWrite(p) {
|
|
51810
51917
|
return (command, args) => {
|
|
51811
51918
|
const dir = dirname2(p);
|
|
51812
|
-
if (!
|
|
51813
|
-
|
|
51814
|
-
let existing =
|
|
51919
|
+
if (!existsSync6(dir))
|
|
51920
|
+
mkdirSync4(dir, { recursive: true });
|
|
51921
|
+
let existing = existsSync6(p) ? readFileSync13(p, "utf8") : "";
|
|
51815
51922
|
if (!existing.trimEnd().endsWith(`
|
|
51816
51923
|
`))
|
|
51817
51924
|
existing += `
|
|
@@ -51828,9 +51935,9 @@ args = ${JSON.stringify(args)}
|
|
|
51828
51935
|
function hermesWrite(p) {
|
|
51829
51936
|
return (command, args) => {
|
|
51830
51937
|
const dir = dirname2(p);
|
|
51831
|
-
if (!
|
|
51832
|
-
|
|
51833
|
-
let existing =
|
|
51938
|
+
if (!existsSync6(dir))
|
|
51939
|
+
mkdirSync4(dir, { recursive: true });
|
|
51940
|
+
let existing = existsSync6(p) ? readFileSync13(p, "utf8") : "";
|
|
51834
51941
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
51835
51942
|
if (!existing.trimEnd().endsWith(`
|
|
51836
51943
|
`))
|
|
@@ -51855,76 +51962,76 @@ mcp_servers:
|
|
|
51855
51962
|
};
|
|
51856
51963
|
}
|
|
51857
51964
|
function detectAgents() {
|
|
51858
|
-
const home =
|
|
51965
|
+
const home = homedir5();
|
|
51859
51966
|
const agents = [];
|
|
51860
|
-
const claudeJson =
|
|
51967
|
+
const claudeJson = join11(home, ".claude.json");
|
|
51861
51968
|
agents.push({
|
|
51862
51969
|
name: "Claude Code",
|
|
51863
51970
|
detectPath: claudeJson,
|
|
51864
|
-
installed: () =>
|
|
51971
|
+
installed: () => existsSync6(claudeJson) || which("claude"),
|
|
51865
51972
|
write: jsonMcpServersWrite(claudeJson)
|
|
51866
51973
|
});
|
|
51867
|
-
const claudeDesktop =
|
|
51974
|
+
const claudeDesktop = join11(home, ".config", "Claude", "claude_desktop_config.json");
|
|
51868
51975
|
agents.push({
|
|
51869
51976
|
name: "Claude Desktop",
|
|
51870
51977
|
detectPath: claudeDesktop,
|
|
51871
|
-
installed: () =>
|
|
51978
|
+
installed: () => existsSync6(claudeDesktop),
|
|
51872
51979
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
51873
51980
|
});
|
|
51874
|
-
const cursor =
|
|
51981
|
+
const cursor = join11(home, ".cursor", "mcp.json");
|
|
51875
51982
|
agents.push({
|
|
51876
51983
|
name: "Cursor",
|
|
51877
51984
|
detectPath: cursor,
|
|
51878
|
-
installed: () =>
|
|
51985
|
+
installed: () => existsSync6(join11(home, ".cursor")) || which("cursor"),
|
|
51879
51986
|
write: jsonMcpServersWrite(cursor)
|
|
51880
51987
|
});
|
|
51881
|
-
const opencode =
|
|
51988
|
+
const opencode = join11(home, ".config", "opencode", "opencode.jsonc");
|
|
51882
51989
|
agents.push({
|
|
51883
51990
|
name: "OpenCode",
|
|
51884
51991
|
detectPath: opencode,
|
|
51885
|
-
installed: () =>
|
|
51992
|
+
installed: () => existsSync6(join11(home, ".config", "opencode")) || which("opencode"),
|
|
51886
51993
|
write: opencodeWrite(opencode)
|
|
51887
51994
|
});
|
|
51888
|
-
const codex =
|
|
51995
|
+
const codex = join11(home, ".codex", "config.toml");
|
|
51889
51996
|
agents.push({
|
|
51890
51997
|
name: "Codex",
|
|
51891
51998
|
detectPath: codex,
|
|
51892
|
-
installed: () =>
|
|
51999
|
+
installed: () => existsSync6(join11(home, ".codex")) || which("codex"),
|
|
51893
52000
|
write: codexWrite(codex)
|
|
51894
52001
|
});
|
|
51895
|
-
const hermes =
|
|
52002
|
+
const hermes = join11(home, ".hermes", "config.yaml");
|
|
51896
52003
|
agents.push({
|
|
51897
52004
|
name: "Hermes",
|
|
51898
52005
|
detectPath: hermes,
|
|
51899
|
-
installed: () =>
|
|
52006
|
+
installed: () => existsSync6(join11(home, ".hermes")) || which("hermes"),
|
|
51900
52007
|
write: hermesWrite(hermes)
|
|
51901
52008
|
});
|
|
51902
|
-
const gemini =
|
|
52009
|
+
const gemini = join11(home, ".gemini", "settings.json");
|
|
51903
52010
|
agents.push({
|
|
51904
52011
|
name: "Gemini CLI",
|
|
51905
52012
|
detectPath: gemini,
|
|
51906
|
-
installed: () =>
|
|
52013
|
+
installed: () => existsSync6(join11(home, ".gemini")) || which("gemini"),
|
|
51907
52014
|
write: jsonMcpServersWrite(gemini)
|
|
51908
52015
|
});
|
|
51909
|
-
const windsurf =
|
|
52016
|
+
const windsurf = join11(home, ".codeium", "windsurf", "mcp_config.json");
|
|
51910
52017
|
agents.push({
|
|
51911
52018
|
name: "Windsurf",
|
|
51912
52019
|
detectPath: windsurf,
|
|
51913
|
-
installed: () =>
|
|
52020
|
+
installed: () => existsSync6(join11(home, ".codeium")) || which("windsurf"),
|
|
51914
52021
|
write: jsonMcpServersWrite(windsurf)
|
|
51915
52022
|
});
|
|
51916
|
-
const copilot =
|
|
52023
|
+
const copilot = join11(home, ".copilot", "mcp.json");
|
|
51917
52024
|
agents.push({
|
|
51918
52025
|
name: "Copilot",
|
|
51919
52026
|
detectPath: copilot,
|
|
51920
|
-
installed: () =>
|
|
52027
|
+
installed: () => existsSync6(join11(home, ".copilot")),
|
|
51921
52028
|
write: jsonMcpServersWrite(copilot)
|
|
51922
52029
|
});
|
|
51923
|
-
const cline =
|
|
52030
|
+
const cline = join11(home, ".cline", "mcp_settings.json");
|
|
51924
52031
|
agents.push({
|
|
51925
52032
|
name: "Cline",
|
|
51926
52033
|
detectPath: cline,
|
|
51927
|
-
installed: () =>
|
|
52034
|
+
installed: () => existsSync6(join11(home, ".cline")),
|
|
51928
52035
|
write: jsonMcpServersWrite(cline)
|
|
51929
52036
|
});
|
|
51930
52037
|
return agents;
|
|
@@ -51968,15 +52075,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
51968
52075
|
}
|
|
51969
52076
|
|
|
51970
52077
|
// src/index.ts
|
|
51971
|
-
import { readFileSync as
|
|
51972
|
-
import { join as
|
|
52078
|
+
import { readFileSync as readFileSync14, existsSync as existsSync7 } from "node:fs";
|
|
52079
|
+
import { join as join12 } from "node:path";
|
|
51973
52080
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
51974
|
-
var ROOT7 =
|
|
52081
|
+
var ROOT7 = join12(fileURLToPath9(new URL(".", import.meta.url)), "..");
|
|
51975
52082
|
function readVersion() {
|
|
51976
52083
|
try {
|
|
51977
|
-
const p =
|
|
51978
|
-
if (
|
|
51979
|
-
return JSON.parse(
|
|
52084
|
+
const p = join12(ROOT7, "package.json");
|
|
52085
|
+
if (existsSync7(p))
|
|
52086
|
+
return JSON.parse(readFileSync14(p, "utf8")).version ?? "1.0.0";
|
|
51980
52087
|
} catch {}
|
|
51981
52088
|
return "1.0.0";
|
|
51982
52089
|
}
|
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.34",
|
|
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": {
|