blitzstrike 1.0.32 → 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 +26 -0
- package/dist/index.js +283 -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,13 @@ function redactSecrets(text) {
|
|
|
29943
29984
|
}
|
|
29944
29985
|
return out;
|
|
29945
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
|
+
}
|
|
29946
29994
|
function redactObject(value) {
|
|
29947
29995
|
if (typeof value === "string")
|
|
29948
29996
|
return redactSecrets(value);
|
|
@@ -29977,9 +30025,61 @@ function makeEvidence(input) {
|
|
|
29977
30025
|
source: input.source ? redactObject(input.source) : undefined,
|
|
29978
30026
|
sink: input.sink ? redactObject(input.sink) : undefined,
|
|
29979
30027
|
artifacts,
|
|
29980
|
-
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
|
+
}
|
|
29981
30036
|
};
|
|
29982
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
|
+
}
|
|
29983
30083
|
|
|
29984
30084
|
// src/finding.ts
|
|
29985
30085
|
var LIFECYCLE_TRANSITIONS = {
|
|
@@ -30041,7 +30141,7 @@ function makeFinding(input) {
|
|
|
30041
30141
|
const status = input.status ?? "detected";
|
|
30042
30142
|
const factors = confidenceFactorsFor(input);
|
|
30043
30143
|
const confidence = computeConfidence(factors);
|
|
30044
|
-
|
|
30144
|
+
const finding = {
|
|
30045
30145
|
id: nextFindingId(),
|
|
30046
30146
|
status,
|
|
30047
30147
|
title: input.title,
|
|
@@ -30064,6 +30164,8 @@ function makeFinding(input) {
|
|
|
30064
30164
|
remediation: {},
|
|
30065
30165
|
timestamps: { created: now, updated: now }
|
|
30066
30166
|
};
|
|
30167
|
+
recordAudit("finding_created", { tool: "eagle-eye", target: input.target.host ?? input.target.endpoint, result: status, correlationId: finding.id });
|
|
30168
|
+
return finding;
|
|
30067
30169
|
}
|
|
30068
30170
|
function confidenceFactorsFor(_input) {
|
|
30069
30171
|
return {
|
|
@@ -30083,6 +30185,7 @@ function transition(finding, to) {
|
|
|
30083
30185
|
return updated;
|
|
30084
30186
|
}
|
|
30085
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 });
|
|
30086
30189
|
return {
|
|
30087
30190
|
...finding,
|
|
30088
30191
|
evidence: [...finding.evidence, evidence],
|
|
@@ -30090,8 +30193,12 @@ function attachEvidence(finding, evidence) {
|
|
|
30090
30193
|
};
|
|
30091
30194
|
}
|
|
30092
30195
|
function confirmFinding(finding, opts) {
|
|
30196
|
+
if (opts.evidence.length === 0) {
|
|
30197
|
+
throw new Error("invariant: cannot confirm a finding without evidence");
|
|
30198
|
+
}
|
|
30093
30199
|
const evidence = opts.evidence.map((e) => makeEvidence(e));
|
|
30094
30200
|
const negativeControl = opts.negative_control ?? true;
|
|
30201
|
+
const scopeAllowed = opts.scope_allowed ?? true;
|
|
30095
30202
|
const confidence = computeConfidence({
|
|
30096
30203
|
static_analysis: true,
|
|
30097
30204
|
data_flow: true,
|
|
@@ -30102,7 +30209,7 @@ function confirmFinding(finding, opts) {
|
|
|
30102
30209
|
});
|
|
30103
30210
|
const level = confidenceLevel(confidence);
|
|
30104
30211
|
const status = negativeControl ? "confirmed" : "hypothesis";
|
|
30105
|
-
|
|
30212
|
+
const confirmed = {
|
|
30106
30213
|
...finding,
|
|
30107
30214
|
status,
|
|
30108
30215
|
confidence,
|
|
@@ -30111,11 +30218,15 @@ function confirmFinding(finding, opts) {
|
|
|
30111
30218
|
performed: true,
|
|
30112
30219
|
status: negativeControl ? "confirmed" : "unconfirmed",
|
|
30113
30220
|
baseline: opts.baseline ?? true,
|
|
30114
|
-
negative_control: negativeControl
|
|
30221
|
+
negative_control: negativeControl,
|
|
30222
|
+
scope_allowed: scopeAllowed
|
|
30115
30223
|
},
|
|
30116
30224
|
evidence: [...finding.evidence, ...evidence],
|
|
30117
30225
|
timestamps: { ...finding.timestamps, updated: new Date().toISOString() }
|
|
30118
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;
|
|
30119
30230
|
}
|
|
30120
30231
|
function rejectFinding(finding, status, reason, evidence) {
|
|
30121
30232
|
const ev = evidence?.map((e) => makeEvidence(e)) ?? [];
|
|
@@ -30135,9 +30246,9 @@ function loadChains() {
|
|
|
30135
30246
|
if (_chainsCache)
|
|
30136
30247
|
return _chainsCache;
|
|
30137
30248
|
try {
|
|
30138
|
-
const dir =
|
|
30139
|
-
const path =
|
|
30140
|
-
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"));
|
|
30141
30252
|
_chainsCache = raw.chains ?? [];
|
|
30142
30253
|
return _chainsCache;
|
|
30143
30254
|
} catch {
|
|
@@ -30146,40 +30257,50 @@ function loadChains() {
|
|
|
30146
30257
|
}
|
|
30147
30258
|
}
|
|
30148
30259
|
function scopeCheck(target, scope = "", mode = "bug-bounty") {
|
|
30260
|
+
let result;
|
|
30149
30261
|
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
|
-
|
|
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
|
+
}
|
|
30172
30288
|
}
|
|
30173
|
-
|
|
30174
|
-
|
|
30175
|
-
|
|
30176
|
-
|
|
30177
|
-
|
|
30178
|
-
|
|
30179
|
-
|
|
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
|
+
}
|
|
30180
30300
|
}
|
|
30181
30301
|
}
|
|
30182
|
-
|
|
30302
|
+
recordAudit("scope_checked", { tool: "scope", target, result: result.allowed ? "allowed" : "denied" });
|
|
30303
|
+
return result;
|
|
30183
30304
|
}
|
|
30184
30305
|
function classifySink(sink) {
|
|
30185
30306
|
if (/(move_uploaded_file|file_put_contents|fwrite)/.test(sink))
|
|
@@ -30243,7 +30364,7 @@ function runEngagement(target, scope = "", mode = "bug-bounty", maxFiles = 2000,
|
|
|
30243
30364
|
};
|
|
30244
30365
|
const isPath = (() => {
|
|
30245
30366
|
try {
|
|
30246
|
-
return
|
|
30367
|
+
return existsSync4(target) && (statSync2(target).isDirectory() || statSync2(target).isFile());
|
|
30247
30368
|
} catch {
|
|
30248
30369
|
return false;
|
|
30249
30370
|
}
|
|
@@ -30360,18 +30481,18 @@ function listChains() {
|
|
|
30360
30481
|
}
|
|
30361
30482
|
|
|
30362
30483
|
// src/catalog.ts
|
|
30363
|
-
import { readFileSync as
|
|
30364
|
-
import { join as
|
|
30365
|
-
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";
|
|
30366
30487
|
import { spawnSync } from "node:child_process";
|
|
30367
|
-
var ROOT2 =
|
|
30488
|
+
var ROOT2 = join7(fileURLToPath4(new URL(".", import.meta.url)), "..");
|
|
30368
30489
|
var _toolsCache = null;
|
|
30369
30490
|
var _skillsCache = null;
|
|
30370
30491
|
function loadTools() {
|
|
30371
30492
|
if (_toolsCache)
|
|
30372
30493
|
return _toolsCache;
|
|
30373
30494
|
try {
|
|
30374
|
-
const raw = JSON.parse(
|
|
30495
|
+
const raw = JSON.parse(readFileSync7(join7(ROOT2, "tools-catalog.json"), "utf8"));
|
|
30375
30496
|
_toolsCache = raw.tools ?? [];
|
|
30376
30497
|
} catch {
|
|
30377
30498
|
_toolsCache = [];
|
|
@@ -30381,14 +30502,14 @@ function loadTools() {
|
|
|
30381
30502
|
function loadSkills() {
|
|
30382
30503
|
if (_skillsCache)
|
|
30383
30504
|
return _skillsCache;
|
|
30384
|
-
const dir =
|
|
30505
|
+
const dir = join7(ROOT2, "skills");
|
|
30385
30506
|
const out = [];
|
|
30386
30507
|
try {
|
|
30387
30508
|
for (const f of readdirSync2(dir)) {
|
|
30388
30509
|
if (!f.endsWith(".md"))
|
|
30389
30510
|
continue;
|
|
30390
|
-
const p =
|
|
30391
|
-
const text =
|
|
30511
|
+
const p = join7(dir, f);
|
|
30512
|
+
const text = readFileSync7(p, "utf8");
|
|
30392
30513
|
const descMatch = text.match(/^description:\s*(.+)$/m);
|
|
30393
30514
|
out.push({
|
|
30394
30515
|
name: f.replace(/\.md$/, ""),
|
|
@@ -30461,7 +30582,7 @@ function readSkill(name) {
|
|
|
30461
30582
|
if (!s)
|
|
30462
30583
|
return { found: false, query: name };
|
|
30463
30584
|
try {
|
|
30464
|
-
const text =
|
|
30585
|
+
const text = readFileSync7(join7(ROOT2, "skills", s.file), "utf8");
|
|
30465
30586
|
return { found: true, name: s.name, content: text.slice(0, 12000) };
|
|
30466
30587
|
} catch {
|
|
30467
30588
|
return { found: false, query: name, error: "unreadable" };
|
|
@@ -30494,19 +30615,19 @@ function ensureTool(name) {
|
|
|
30494
30615
|
}
|
|
30495
30616
|
|
|
30496
30617
|
// src/intel.ts
|
|
30497
|
-
import { readFileSync as
|
|
30498
|
-
import { join as
|
|
30499
|
-
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";
|
|
30500
30621
|
import { readdirSync as readdirSync3 } from "node:fs";
|
|
30501
|
-
import { homedir as
|
|
30502
|
-
var ROOT3 =
|
|
30503
|
-
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");
|
|
30504
30625
|
function loadJson(name) {
|
|
30505
|
-
const p =
|
|
30506
|
-
if (!
|
|
30626
|
+
const p = join8(INTEL, `${name}.json`);
|
|
30627
|
+
if (!existsSync5(p))
|
|
30507
30628
|
return null;
|
|
30508
30629
|
try {
|
|
30509
|
-
return JSON.parse(
|
|
30630
|
+
return JSON.parse(readFileSync8(p, "utf8"));
|
|
30510
30631
|
} catch {
|
|
30511
30632
|
return null;
|
|
30512
30633
|
}
|
|
@@ -30638,18 +30759,18 @@ function attackVectors(category) {
|
|
|
30638
30759
|
}
|
|
30639
30760
|
return { found: true, id: found.id, title: found.title, count: found.vectors.length, vectors: found.vectors };
|
|
30640
30761
|
}
|
|
30641
|
-
var PACKAGE_PAYLOADS =
|
|
30642
|
-
var PACKAGE_TEMPLATES =
|
|
30643
|
-
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");
|
|
30644
30765
|
function payloadsDir() {
|
|
30645
|
-
if (
|
|
30766
|
+
if (existsSync5(PACKAGE_PAYLOADS))
|
|
30646
30767
|
return PACKAGE_PAYLOADS;
|
|
30647
|
-
return
|
|
30768
|
+
return join8(DATA_ROOT, "payloads");
|
|
30648
30769
|
}
|
|
30649
30770
|
function templatesDir() {
|
|
30650
|
-
if (
|
|
30771
|
+
if (existsSync5(PACKAGE_TEMPLATES))
|
|
30651
30772
|
return PACKAGE_TEMPLATES;
|
|
30652
|
-
return
|
|
30773
|
+
return join8(DATA_ROOT, "templates");
|
|
30653
30774
|
}
|
|
30654
30775
|
var PAYLOADS = payloadsDir();
|
|
30655
30776
|
var TEMPLATES = templatesDir();
|
|
@@ -30685,7 +30806,7 @@ function payloadLookup(topic) {
|
|
|
30685
30806
|
const normalized = ALIASES[key] ?? key;
|
|
30686
30807
|
const exact = cats.find((c) => c.toLowerCase() === normalized);
|
|
30687
30808
|
if (exact) {
|
|
30688
|
-
return { found: true, category: exact, files: listFiles(
|
|
30809
|
+
return { found: true, category: exact, files: listFiles(join8(PAYLOADS, exact)) };
|
|
30689
30810
|
}
|
|
30690
30811
|
const keyTokens = normalized.split(/[^a-z0-9]+/).filter((t) => t.length >= 3);
|
|
30691
30812
|
const matches = cats.filter((c) => {
|
|
@@ -30695,7 +30816,7 @@ function payloadLookup(topic) {
|
|
|
30695
30816
|
return keyTokens.some((tok) => new RegExp(`(^|[^a-z0-9])${tok}`).test(cl));
|
|
30696
30817
|
});
|
|
30697
30818
|
if (matches.length > 0) {
|
|
30698
|
-
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) };
|
|
30699
30820
|
}
|
|
30700
30821
|
return { found: false, topic, suggestions: cats.slice(0, 20) };
|
|
30701
30822
|
}
|
|
@@ -30704,19 +30825,19 @@ function readPayload(category, file) {
|
|
|
30704
30825
|
const exact = cats.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
30705
30826
|
if (!exact)
|
|
30706
30827
|
return { found: false, category, suggestions: cats.slice(0, 20) };
|
|
30707
|
-
const dir =
|
|
30828
|
+
const dir = join8(PAYLOADS, exact);
|
|
30708
30829
|
const files = listFiles(dir);
|
|
30709
30830
|
if (!file) {
|
|
30710
30831
|
const readme = files.find((f) => f.toLowerCase() === "readme.md");
|
|
30711
30832
|
if (readme) {
|
|
30712
|
-
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) };
|
|
30713
30834
|
}
|
|
30714
30835
|
return { found: true, category: exact, files };
|
|
30715
30836
|
}
|
|
30716
30837
|
const f = files.find((x) => x.toLowerCase() === file.toLowerCase());
|
|
30717
30838
|
if (!f)
|
|
30718
30839
|
return { found: false, category: exact, file, suggestions: files };
|
|
30719
|
-
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) };
|
|
30720
30841
|
}
|
|
30721
30842
|
function listFiles(dir) {
|
|
30722
30843
|
try {
|
|
@@ -30731,7 +30852,7 @@ function countTemplates() {
|
|
|
30731
30852
|
const walk = (dir) => {
|
|
30732
30853
|
for (const e of readdirSync3(dir, { withFileTypes: true })) {
|
|
30733
30854
|
if (e.isDirectory())
|
|
30734
|
-
walk(
|
|
30855
|
+
walk(join8(dir, e.name));
|
|
30735
30856
|
else if (e.name.endsWith(".yaml"))
|
|
30736
30857
|
n++;
|
|
30737
30858
|
}
|
|
@@ -30756,14 +30877,14 @@ function templateLookup(topic, limit = 10) {
|
|
|
30756
30877
|
}
|
|
30757
30878
|
for (const e of entries) {
|
|
30758
30879
|
if (e.isDirectory())
|
|
30759
|
-
walk(
|
|
30880
|
+
walk(join8(dir, e.name), depth + 1);
|
|
30760
30881
|
else if (e.name.endsWith(".yaml")) {
|
|
30761
30882
|
const name = e.name.toLowerCase();
|
|
30762
30883
|
if (name.includes(key)) {
|
|
30763
|
-
const full =
|
|
30884
|
+
const full = join8(dir, e.name);
|
|
30764
30885
|
let id = "";
|
|
30765
30886
|
try {
|
|
30766
|
-
const content =
|
|
30887
|
+
const content = readFileSync8(full, "utf8");
|
|
30767
30888
|
const m = content.match(/^\s*id:\s*(.+)$/m);
|
|
30768
30889
|
if (m)
|
|
30769
30890
|
id = m[1].trim();
|
|
@@ -30923,7 +31044,7 @@ async function activeScan(target, scope = "", mode = "bug-bounty", allowed = und
|
|
|
30923
31044
|
|
|
30924
31045
|
// src/dataflow.ts
|
|
30925
31046
|
init_scanner();
|
|
30926
|
-
import { readFileSync as
|
|
31047
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
30927
31048
|
var SOURCES = {
|
|
30928
31049
|
$_GET: { id: "http_get", label: "HTTP GET parameter", attacker_controlled: true },
|
|
30929
31050
|
$_POST: { id: "http_post", label: "HTTP POST body", attacker_controlled: true },
|
|
@@ -31005,7 +31126,7 @@ function traceDataFlow(path) {
|
|
|
31005
31126
|
return result;
|
|
31006
31127
|
let text;
|
|
31007
31128
|
try {
|
|
31008
|
-
text =
|
|
31129
|
+
text = readFileSync9(path, "utf8");
|
|
31009
31130
|
} catch {
|
|
31010
31131
|
return result;
|
|
31011
31132
|
}
|
|
@@ -31084,7 +31205,7 @@ function groupVariants(results) {
|
|
|
31084
31205
|
// src/taint.ts
|
|
31085
31206
|
init_scanner();
|
|
31086
31207
|
var import_php_parser = __toESM(require_src(), 1);
|
|
31087
|
-
import { readFileSync as
|
|
31208
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
31088
31209
|
var parser = new import_php_parser.Engine({
|
|
31089
31210
|
parser: { extractDoc: false, php7: true },
|
|
31090
31211
|
ast: { withPositions: true, withSource: false }
|
|
@@ -31382,7 +31503,7 @@ function analyzeTaint(path) {
|
|
|
31382
31503
|
const result = { file: path, findings: [], suppressed: 0 };
|
|
31383
31504
|
let code;
|
|
31384
31505
|
try {
|
|
31385
|
-
code =
|
|
31506
|
+
code = readFileSync10(path, "utf8");
|
|
31386
31507
|
} catch {
|
|
31387
31508
|
return result;
|
|
31388
31509
|
}
|
|
@@ -50656,16 +50777,16 @@ function reportJson(findings, opts = {}) {
|
|
|
50656
50777
|
}
|
|
50657
50778
|
|
|
50658
50779
|
// src/benchmark.ts
|
|
50659
|
-
import { readFileSync as
|
|
50660
|
-
import { join as
|
|
50661
|
-
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";
|
|
50662
50783
|
import { tmpdir } from "node:os";
|
|
50663
|
-
var ROOT4 =
|
|
50664
|
-
var CORPUS =
|
|
50784
|
+
var ROOT4 = join9(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
50785
|
+
var CORPUS = join9(ROOT4, "bench", "corpus");
|
|
50665
50786
|
function parseCorpusFile(path) {
|
|
50666
50787
|
let text;
|
|
50667
50788
|
try {
|
|
50668
|
-
text =
|
|
50789
|
+
text = readFileSync11(path, "utf8");
|
|
50669
50790
|
} catch {
|
|
50670
50791
|
return null;
|
|
50671
50792
|
}
|
|
@@ -50715,7 +50836,7 @@ function loadCorpus() {
|
|
|
50715
50836
|
for (const f of readdirSync4(CORPUS)) {
|
|
50716
50837
|
if (!f.endsWith(".md"))
|
|
50717
50838
|
continue;
|
|
50718
|
-
const e = parseCorpusFile(
|
|
50839
|
+
const e = parseCorpusFile(join9(CORPUS, f));
|
|
50719
50840
|
if (e)
|
|
50720
50841
|
entries.push(e);
|
|
50721
50842
|
}
|
|
@@ -50727,7 +50848,7 @@ function loadCorpus() {
|
|
|
50727
50848
|
function detect(entry) {
|
|
50728
50849
|
const lang = entry.language;
|
|
50729
50850
|
if (lang === "php") {
|
|
50730
|
-
const tmp =
|
|
50851
|
+
const tmp = join9(tmpdir(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
50731
50852
|
writeFileSync2(tmp, entry.code);
|
|
50732
50853
|
const r = analyzeTaint(tmp);
|
|
50733
50854
|
rmSync(tmp, { force: true });
|
|
@@ -50875,17 +50996,6 @@ function coverageMatrix() {
|
|
|
50875
50996
|
|
|
50876
50997
|
// src/server.ts
|
|
50877
50998
|
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
50999
|
function createServer() {
|
|
50890
51000
|
const server = new McpServer({
|
|
50891
51001
|
name: "blitzstrike",
|
|
@@ -51677,12 +51787,12 @@ async function serve() {
|
|
|
51677
51787
|
}
|
|
51678
51788
|
|
|
51679
51789
|
// src/cli.ts
|
|
51680
|
-
import { readFileSync as
|
|
51681
|
-
import { join as
|
|
51682
|
-
import { homedir as
|
|
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";
|
|
51683
51793
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
51684
51794
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
51685
|
-
var ROOT6 =
|
|
51795
|
+
var ROOT6 = join11(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
51686
51796
|
function which(cmd) {
|
|
51687
51797
|
return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
|
|
51688
51798
|
}
|
|
@@ -51717,7 +51827,7 @@ function runDoctor() {
|
|
|
51717
51827
|
detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
|
|
51718
51828
|
fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
|
|
51719
51829
|
});
|
|
51720
|
-
const dataOk =
|
|
51830
|
+
const dataOk = existsSync6(join11(ROOT6, "chains.json")) && existsSync6(join11(ROOT6, "tools-catalog.json"));
|
|
51721
51831
|
issues.push({
|
|
51722
51832
|
name: "Data layers (chains + tools-catalog)",
|
|
51723
51833
|
status: dataOk ? "ok" : "fail",
|
|
@@ -51745,21 +51855,21 @@ BlitzStrike — ${verdict}`);
|
|
|
51745
51855
|
function resolveCommand() {
|
|
51746
51856
|
if (which("blitzstrike"))
|
|
51747
51857
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
51748
|
-
const src =
|
|
51749
|
-
if (which("bun") &&
|
|
51858
|
+
const src = join11(ROOT6, "src", "index.ts");
|
|
51859
|
+
if (which("bun") && existsSync6(src))
|
|
51750
51860
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
51751
51861
|
if (which("npx"))
|
|
51752
51862
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
51753
51863
|
if (which("bunx"))
|
|
51754
51864
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
51755
|
-
const dist =
|
|
51756
|
-
if (
|
|
51865
|
+
const dist = join11(ROOT6, "dist", "index.js");
|
|
51866
|
+
if (existsSync6(dist))
|
|
51757
51867
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
51758
51868
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
51759
51869
|
}
|
|
51760
51870
|
function readJson(p) {
|
|
51761
51871
|
try {
|
|
51762
|
-
return JSON.parse(
|
|
51872
|
+
return JSON.parse(readFileSync13(p, "utf8"));
|
|
51763
51873
|
} catch {
|
|
51764
51874
|
return null;
|
|
51765
51875
|
}
|
|
@@ -51767,8 +51877,8 @@ function readJson(p) {
|
|
|
51767
51877
|
function jsonMcpServersWrite(p) {
|
|
51768
51878
|
return (command, args) => {
|
|
51769
51879
|
const dir = dirname2(p);
|
|
51770
|
-
if (!
|
|
51771
|
-
|
|
51880
|
+
if (!existsSync6(dir))
|
|
51881
|
+
mkdirSync4(dir, { recursive: true });
|
|
51772
51882
|
const existing = readJson(p) ?? {};
|
|
51773
51883
|
existing.mcpServers = {
|
|
51774
51884
|
...existing.mcpServers ?? {},
|
|
@@ -51780,8 +51890,8 @@ function jsonMcpServersWrite(p) {
|
|
|
51780
51890
|
function opencodeWrite(p) {
|
|
51781
51891
|
return (_command, _args) => {
|
|
51782
51892
|
const dir = dirname2(p);
|
|
51783
|
-
if (!
|
|
51784
|
-
|
|
51893
|
+
if (!existsSync6(dir))
|
|
51894
|
+
mkdirSync4(dir, { recursive: true });
|
|
51785
51895
|
const existing = readJson(p) ?? {};
|
|
51786
51896
|
existing.mcp = {
|
|
51787
51897
|
...existing.mcp ?? {},
|
|
@@ -51792,26 +51902,26 @@ function opencodeWrite(p) {
|
|
|
51792
51902
|
};
|
|
51793
51903
|
}
|
|
51794
51904
|
function copyOpenCodeAgents() {
|
|
51795
|
-
const srcDir =
|
|
51796
|
-
const dstDir =
|
|
51797
|
-
if (!
|
|
51905
|
+
const srcDir = join11(ROOT6, "opencode-agents");
|
|
51906
|
+
const dstDir = join11(homedir5(), ".config", "opencode", "agents");
|
|
51907
|
+
if (!existsSync6(srcDir))
|
|
51798
51908
|
return;
|
|
51799
|
-
if (!
|
|
51800
|
-
|
|
51909
|
+
if (!existsSync6(dstDir))
|
|
51910
|
+
mkdirSync4(dstDir, { recursive: true });
|
|
51801
51911
|
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,
|
|
51912
|
+
const src = join11(srcDir, f);
|
|
51913
|
+
const dst = join11(dstDir, f);
|
|
51914
|
+
if (existsSync6(src)) {
|
|
51915
|
+
writeFileSync4(dst, readFileSync13(src, "utf8"));
|
|
51806
51916
|
}
|
|
51807
51917
|
}
|
|
51808
51918
|
}
|
|
51809
51919
|
function codexWrite(p) {
|
|
51810
51920
|
return (command, args) => {
|
|
51811
51921
|
const dir = dirname2(p);
|
|
51812
|
-
if (!
|
|
51813
|
-
|
|
51814
|
-
let existing =
|
|
51922
|
+
if (!existsSync6(dir))
|
|
51923
|
+
mkdirSync4(dir, { recursive: true });
|
|
51924
|
+
let existing = existsSync6(p) ? readFileSync13(p, "utf8") : "";
|
|
51815
51925
|
if (!existing.trimEnd().endsWith(`
|
|
51816
51926
|
`))
|
|
51817
51927
|
existing += `
|
|
@@ -51828,9 +51938,9 @@ args = ${JSON.stringify(args)}
|
|
|
51828
51938
|
function hermesWrite(p) {
|
|
51829
51939
|
return (command, args) => {
|
|
51830
51940
|
const dir = dirname2(p);
|
|
51831
|
-
if (!
|
|
51832
|
-
|
|
51833
|
-
let existing =
|
|
51941
|
+
if (!existsSync6(dir))
|
|
51942
|
+
mkdirSync4(dir, { recursive: true });
|
|
51943
|
+
let existing = existsSync6(p) ? readFileSync13(p, "utf8") : "";
|
|
51834
51944
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
51835
51945
|
if (!existing.trimEnd().endsWith(`
|
|
51836
51946
|
`))
|
|
@@ -51855,76 +51965,76 @@ mcp_servers:
|
|
|
51855
51965
|
};
|
|
51856
51966
|
}
|
|
51857
51967
|
function detectAgents() {
|
|
51858
|
-
const home =
|
|
51968
|
+
const home = homedir5();
|
|
51859
51969
|
const agents = [];
|
|
51860
|
-
const claudeJson =
|
|
51970
|
+
const claudeJson = join11(home, ".claude.json");
|
|
51861
51971
|
agents.push({
|
|
51862
51972
|
name: "Claude Code",
|
|
51863
51973
|
detectPath: claudeJson,
|
|
51864
|
-
installed: () =>
|
|
51974
|
+
installed: () => existsSync6(claudeJson) || which("claude"),
|
|
51865
51975
|
write: jsonMcpServersWrite(claudeJson)
|
|
51866
51976
|
});
|
|
51867
|
-
const claudeDesktop =
|
|
51977
|
+
const claudeDesktop = join11(home, ".config", "Claude", "claude_desktop_config.json");
|
|
51868
51978
|
agents.push({
|
|
51869
51979
|
name: "Claude Desktop",
|
|
51870
51980
|
detectPath: claudeDesktop,
|
|
51871
|
-
installed: () =>
|
|
51981
|
+
installed: () => existsSync6(claudeDesktop),
|
|
51872
51982
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
51873
51983
|
});
|
|
51874
|
-
const cursor =
|
|
51984
|
+
const cursor = join11(home, ".cursor", "mcp.json");
|
|
51875
51985
|
agents.push({
|
|
51876
51986
|
name: "Cursor",
|
|
51877
51987
|
detectPath: cursor,
|
|
51878
|
-
installed: () =>
|
|
51988
|
+
installed: () => existsSync6(join11(home, ".cursor")) || which("cursor"),
|
|
51879
51989
|
write: jsonMcpServersWrite(cursor)
|
|
51880
51990
|
});
|
|
51881
|
-
const opencode =
|
|
51991
|
+
const opencode = join11(home, ".config", "opencode", "opencode.jsonc");
|
|
51882
51992
|
agents.push({
|
|
51883
51993
|
name: "OpenCode",
|
|
51884
51994
|
detectPath: opencode,
|
|
51885
|
-
installed: () =>
|
|
51995
|
+
installed: () => existsSync6(join11(home, ".config", "opencode")) || which("opencode"),
|
|
51886
51996
|
write: opencodeWrite(opencode)
|
|
51887
51997
|
});
|
|
51888
|
-
const codex =
|
|
51998
|
+
const codex = join11(home, ".codex", "config.toml");
|
|
51889
51999
|
agents.push({
|
|
51890
52000
|
name: "Codex",
|
|
51891
52001
|
detectPath: codex,
|
|
51892
|
-
installed: () =>
|
|
52002
|
+
installed: () => existsSync6(join11(home, ".codex")) || which("codex"),
|
|
51893
52003
|
write: codexWrite(codex)
|
|
51894
52004
|
});
|
|
51895
|
-
const hermes =
|
|
52005
|
+
const hermes = join11(home, ".hermes", "config.yaml");
|
|
51896
52006
|
agents.push({
|
|
51897
52007
|
name: "Hermes",
|
|
51898
52008
|
detectPath: hermes,
|
|
51899
|
-
installed: () =>
|
|
52009
|
+
installed: () => existsSync6(join11(home, ".hermes")) || which("hermes"),
|
|
51900
52010
|
write: hermesWrite(hermes)
|
|
51901
52011
|
});
|
|
51902
|
-
const gemini =
|
|
52012
|
+
const gemini = join11(home, ".gemini", "settings.json");
|
|
51903
52013
|
agents.push({
|
|
51904
52014
|
name: "Gemini CLI",
|
|
51905
52015
|
detectPath: gemini,
|
|
51906
|
-
installed: () =>
|
|
52016
|
+
installed: () => existsSync6(join11(home, ".gemini")) || which("gemini"),
|
|
51907
52017
|
write: jsonMcpServersWrite(gemini)
|
|
51908
52018
|
});
|
|
51909
|
-
const windsurf =
|
|
52019
|
+
const windsurf = join11(home, ".codeium", "windsurf", "mcp_config.json");
|
|
51910
52020
|
agents.push({
|
|
51911
52021
|
name: "Windsurf",
|
|
51912
52022
|
detectPath: windsurf,
|
|
51913
|
-
installed: () =>
|
|
52023
|
+
installed: () => existsSync6(join11(home, ".codeium")) || which("windsurf"),
|
|
51914
52024
|
write: jsonMcpServersWrite(windsurf)
|
|
51915
52025
|
});
|
|
51916
|
-
const copilot =
|
|
52026
|
+
const copilot = join11(home, ".copilot", "mcp.json");
|
|
51917
52027
|
agents.push({
|
|
51918
52028
|
name: "Copilot",
|
|
51919
52029
|
detectPath: copilot,
|
|
51920
|
-
installed: () =>
|
|
52030
|
+
installed: () => existsSync6(join11(home, ".copilot")),
|
|
51921
52031
|
write: jsonMcpServersWrite(copilot)
|
|
51922
52032
|
});
|
|
51923
|
-
const cline =
|
|
52033
|
+
const cline = join11(home, ".cline", "mcp_settings.json");
|
|
51924
52034
|
agents.push({
|
|
51925
52035
|
name: "Cline",
|
|
51926
52036
|
detectPath: cline,
|
|
51927
|
-
installed: () =>
|
|
52037
|
+
installed: () => existsSync6(join11(home, ".cline")),
|
|
51928
52038
|
write: jsonMcpServersWrite(cline)
|
|
51929
52039
|
});
|
|
51930
52040
|
return agents;
|
|
@@ -51968,15 +52078,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
51968
52078
|
}
|
|
51969
52079
|
|
|
51970
52080
|
// src/index.ts
|
|
51971
|
-
import { readFileSync as
|
|
51972
|
-
import { join as
|
|
52081
|
+
import { readFileSync as readFileSync14, existsSync as existsSync7 } from "node:fs";
|
|
52082
|
+
import { join as join12 } from "node:path";
|
|
51973
52083
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
51974
|
-
var ROOT7 =
|
|
52084
|
+
var ROOT7 = join12(fileURLToPath9(new URL(".", import.meta.url)), "..");
|
|
51975
52085
|
function readVersion() {
|
|
51976
52086
|
try {
|
|
51977
|
-
const p =
|
|
51978
|
-
if (
|
|
51979
|
-
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";
|
|
51980
52090
|
} catch {}
|
|
51981
52091
|
return "1.0.0";
|
|
51982
52092
|
}
|
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": {
|