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 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 mkdirSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync2 } from "node:fs";
14824
- import { join as join8 } from "node:path";
14825
- import { homedir as homedir3 } from "node:os";
14826
- import { fileURLToPath as fileURLToPath6 } from "node:url";
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(join8(d, e.name));
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
- mkdirSync2(DATA_ROOT2, { recursive: true });
14855
- const payloadsPath = join8(DATA_ROOT2, "payloads");
14856
- const templatesPath = join8(DATA_ROOT2, "templates");
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 = join8(ROOT5, "payloads");
14867
- const localTemplates = join8(ROOT5, "templates");
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 = join8(DATA_ROOT2, ".tmp-clone");
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(join8(tmp, "payloads"), payloadsPath);
14888
+ copyDir(join10(tmp, "payloads"), payloadsPath);
14889
14889
  payloadsSynced = true;
14890
14890
  }
14891
14891
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
14892
- copyDir(join8(tmp, "templates"), templatesPath);
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
- mkdirSync2(dst, { recursive: true });
14912
+ mkdirSync3(dst, { recursive: true });
14913
14913
  for (const e of readdirSync5(src, { withFileTypes: true })) {
14914
- const s = join8(src, e.name);
14915
- const d = join8(dst, e.name);
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, readFileSync10(s));
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 = join8(fileURLToPath6(new URL(".", import.meta.url)), "..");
14925
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join8(homedir3(), ".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 readFileSync4, existsSync as existsSync3, statSync as statSync2 } from "node:fs";
29687
- import { join as join4 } from "node:path";
29688
- import { fileURLToPath as fileURLToPath2 } from "node:url";
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
- return {
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
- return {
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 = join4(fileURLToPath2(new URL(".", import.meta.url)), "..");
30139
- const path = join4(dir, "chains.json");
30140
- const raw = JSON.parse(readFileSync4(path, "utf8"));
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
- return { target, allowed: true, enforcement: "off", reason: `mode=${mode} disables scope enforcement` };
30151
- }
30152
- if (!scope.trim()) {
30153
- return { target, allowed: false, enforcement: "strict", reason: "no scope provided; refusing active testing" };
30154
- }
30155
- const inScope = [];
30156
- const outScope = [];
30157
- for (const raw of scope.split(/[,\n]/)) {
30158
- const line = raw.trim();
30159
- if (!line)
30160
- continue;
30161
- if (line.startsWith("-"))
30162
- outScope.push(line.slice(1).trim());
30163
- else
30164
- inScope.push(line);
30165
- }
30166
- let host = target;
30167
- host = host.replace(/^https?:\/\//, "");
30168
- host = host.split("/")[0].split(":")[0];
30169
- for (const exc of outScope) {
30170
- if (exc && (host.includes(exc) || host.endsWith("." + exc.replace(/^\./, "")))) {
30171
- return { target, allowed: false, enforcement: "strict", reason: `target '${host}' is explicitly out of scope (${exc})` };
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
- for (const allowed of inScope) {
30175
- const a = allowed.replace(/\*/g, "");
30176
- if (!a)
30177
- continue;
30178
- if (host.includes(a) || host.endsWith("." + a.replace(/^\./, ""))) {
30179
- return { target, allowed: true, enforcement: "strict", reason: `matched in-scope pattern '${allowed}'` };
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
- return { target, allowed: false, enforcement: "strict", reason: `target '${host}' not in scope` };
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 existsSync3(target) && (statSync2(target).isDirectory() || statSync2(target).isFile());
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 readFileSync5, readdirSync as readdirSync2 } from "node:fs";
30364
- import { join as join5 } from "node:path";
30365
- import { fileURLToPath as fileURLToPath3 } from "node:url";
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 = join5(fileURLToPath3(new URL(".", import.meta.url)), "..");
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(readFileSync5(join5(ROOT2, "tools-catalog.json"), "utf8"));
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 = join5(ROOT2, "skills");
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 = join5(dir, f);
30391
- const text = readFileSync5(p, "utf8");
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 = readFileSync5(join5(ROOT2, "skills", s.file), "utf8");
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 readFileSync6, existsSync as existsSync4 } from "node:fs";
30498
- import { join as join6 } from "node:path";
30499
- import { fileURLToPath as fileURLToPath4 } from "node:url";
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 homedir2 } from "node:os";
30502
- var ROOT3 = join6(fileURLToPath4(new URL(".", import.meta.url)), "..");
30503
- var INTEL = join6(ROOT3, "intelligence");
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 = join6(INTEL, `${name}.json`);
30506
- if (!existsSync4(p))
30626
+ const p = join8(INTEL, `${name}.json`);
30627
+ if (!existsSync5(p))
30507
30628
  return null;
30508
30629
  try {
30509
- return JSON.parse(readFileSync6(p, "utf8"));
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 = join6(ROOT3, "payloads");
30642
- var PACKAGE_TEMPLATES = join6(ROOT3, "templates");
30643
- var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join6(homedir2(), ".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 (existsSync4(PACKAGE_PAYLOADS))
30766
+ if (existsSync5(PACKAGE_PAYLOADS))
30646
30767
  return PACKAGE_PAYLOADS;
30647
- return join6(DATA_ROOT, "payloads");
30768
+ return join8(DATA_ROOT, "payloads");
30648
30769
  }
30649
30770
  function templatesDir() {
30650
- if (existsSync4(PACKAGE_TEMPLATES))
30771
+ if (existsSync5(PACKAGE_TEMPLATES))
30651
30772
  return PACKAGE_TEMPLATES;
30652
- return join6(DATA_ROOT, "templates");
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(join6(PAYLOADS, exact)) };
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(join6(PAYLOADS, matches[0])), alternatives: matches.slice(0, 10) };
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 = join6(PAYLOADS, exact);
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: readFileSync6(join6(dir, readme), "utf8").slice(0, 20000) };
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: readFileSync6(join6(dir, f), "utf8").slice(0, 20000) };
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(join6(dir, e.name));
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(join6(dir, e.name), depth + 1);
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 = join6(dir, e.name);
30884
+ const full = join8(dir, e.name);
30764
30885
  let id = "";
30765
30886
  try {
30766
- const content = readFileSync6(full, "utf8");
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 readFileSync7 } from "node:fs";
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 = readFileSync7(path, "utf8");
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 readFileSync8 } from "node:fs";
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 = readFileSync8(path, "utf8");
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 readFileSync9, readdirSync as readdirSync4, writeFileSync as writeFileSync2, rmSync } from "node:fs";
50660
- import { join as join7 } from "node:path";
50661
- import { fileURLToPath as fileURLToPath5 } from "node:url";
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 = join7(fileURLToPath5(new URL(".", import.meta.url)), "..");
50664
- var CORPUS = join7(ROOT4, "bench", "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 = readFileSync9(path, "utf8");
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(join7(CORPUS, f));
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 = join7(tmpdir(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
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 readFileSync12, existsSync as existsSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "node:fs";
51681
- import { join as join10, dirname as dirname2 } from "node:path";
51682
- import { homedir as homedir4 } from "node:os";
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 = join10(fileURLToPath8(new URL(".", import.meta.url)), "..");
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 = existsSync5(join10(ROOT6, "chains.json")) && existsSync5(join10(ROOT6, "tools-catalog.json"));
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 = join10(ROOT6, "src", "index.ts");
51749
- if (which("bun") && existsSync5(src))
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 = join10(ROOT6, "dist", "index.js");
51756
- if (existsSync5(dist))
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(readFileSync12(p, "utf8"));
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 (!existsSync5(dir))
51771
- mkdirSync3(dir, { recursive: true });
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 (!existsSync5(dir))
51784
- mkdirSync3(dir, { recursive: true });
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 = join10(ROOT6, "opencode-agents");
51796
- const dstDir = join10(homedir4(), ".config", "opencode", "agents");
51797
- if (!existsSync5(srcDir))
51905
+ const srcDir = join11(ROOT6, "opencode-agents");
51906
+ const dstDir = join11(homedir5(), ".config", "opencode", "agents");
51907
+ if (!existsSync6(srcDir))
51798
51908
  return;
51799
- if (!existsSync5(dstDir))
51800
- mkdirSync3(dstDir, { recursive: true });
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 = join10(srcDir, f);
51803
- const dst = join10(dstDir, f);
51804
- if (existsSync5(src)) {
51805
- writeFileSync4(dst, readFileSync12(src, "utf8"));
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 (!existsSync5(dir))
51813
- mkdirSync3(dir, { recursive: true });
51814
- let existing = existsSync5(p) ? readFileSync12(p, "utf8") : "";
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 (!existsSync5(dir))
51832
- mkdirSync3(dir, { recursive: true });
51833
- let existing = existsSync5(p) ? readFileSync12(p, "utf8") : "";
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 = homedir4();
51968
+ const home = homedir5();
51859
51969
  const agents = [];
51860
- const claudeJson = join10(home, ".claude.json");
51970
+ const claudeJson = join11(home, ".claude.json");
51861
51971
  agents.push({
51862
51972
  name: "Claude Code",
51863
51973
  detectPath: claudeJson,
51864
- installed: () => existsSync5(claudeJson) || which("claude"),
51974
+ installed: () => existsSync6(claudeJson) || which("claude"),
51865
51975
  write: jsonMcpServersWrite(claudeJson)
51866
51976
  });
51867
- const claudeDesktop = join10(home, ".config", "Claude", "claude_desktop_config.json");
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: () => existsSync5(claudeDesktop),
51981
+ installed: () => existsSync6(claudeDesktop),
51872
51982
  write: jsonMcpServersWrite(claudeDesktop)
51873
51983
  });
51874
- const cursor = join10(home, ".cursor", "mcp.json");
51984
+ const cursor = join11(home, ".cursor", "mcp.json");
51875
51985
  agents.push({
51876
51986
  name: "Cursor",
51877
51987
  detectPath: cursor,
51878
- installed: () => existsSync5(join10(home, ".cursor")) || which("cursor"),
51988
+ installed: () => existsSync6(join11(home, ".cursor")) || which("cursor"),
51879
51989
  write: jsonMcpServersWrite(cursor)
51880
51990
  });
51881
- const opencode = join10(home, ".config", "opencode", "opencode.jsonc");
51991
+ const opencode = join11(home, ".config", "opencode", "opencode.jsonc");
51882
51992
  agents.push({
51883
51993
  name: "OpenCode",
51884
51994
  detectPath: opencode,
51885
- installed: () => existsSync5(join10(home, ".config", "opencode")) || which("opencode"),
51995
+ installed: () => existsSync6(join11(home, ".config", "opencode")) || which("opencode"),
51886
51996
  write: opencodeWrite(opencode)
51887
51997
  });
51888
- const codex = join10(home, ".codex", "config.toml");
51998
+ const codex = join11(home, ".codex", "config.toml");
51889
51999
  agents.push({
51890
52000
  name: "Codex",
51891
52001
  detectPath: codex,
51892
- installed: () => existsSync5(join10(home, ".codex")) || which("codex"),
52002
+ installed: () => existsSync6(join11(home, ".codex")) || which("codex"),
51893
52003
  write: codexWrite(codex)
51894
52004
  });
51895
- const hermes = join10(home, ".hermes", "config.yaml");
52005
+ const hermes = join11(home, ".hermes", "config.yaml");
51896
52006
  agents.push({
51897
52007
  name: "Hermes",
51898
52008
  detectPath: hermes,
51899
- installed: () => existsSync5(join10(home, ".hermes")) || which("hermes"),
52009
+ installed: () => existsSync6(join11(home, ".hermes")) || which("hermes"),
51900
52010
  write: hermesWrite(hermes)
51901
52011
  });
51902
- const gemini = join10(home, ".gemini", "settings.json");
52012
+ const gemini = join11(home, ".gemini", "settings.json");
51903
52013
  agents.push({
51904
52014
  name: "Gemini CLI",
51905
52015
  detectPath: gemini,
51906
- installed: () => existsSync5(join10(home, ".gemini")) || which("gemini"),
52016
+ installed: () => existsSync6(join11(home, ".gemini")) || which("gemini"),
51907
52017
  write: jsonMcpServersWrite(gemini)
51908
52018
  });
51909
- const windsurf = join10(home, ".codeium", "windsurf", "mcp_config.json");
52019
+ const windsurf = join11(home, ".codeium", "windsurf", "mcp_config.json");
51910
52020
  agents.push({
51911
52021
  name: "Windsurf",
51912
52022
  detectPath: windsurf,
51913
- installed: () => existsSync5(join10(home, ".codeium")) || which("windsurf"),
52023
+ installed: () => existsSync6(join11(home, ".codeium")) || which("windsurf"),
51914
52024
  write: jsonMcpServersWrite(windsurf)
51915
52025
  });
51916
- const copilot = join10(home, ".copilot", "mcp.json");
52026
+ const copilot = join11(home, ".copilot", "mcp.json");
51917
52027
  agents.push({
51918
52028
  name: "Copilot",
51919
52029
  detectPath: copilot,
51920
- installed: () => existsSync5(join10(home, ".copilot")),
52030
+ installed: () => existsSync6(join11(home, ".copilot")),
51921
52031
  write: jsonMcpServersWrite(copilot)
51922
52032
  });
51923
- const cline = join10(home, ".cline", "mcp_settings.json");
52033
+ const cline = join11(home, ".cline", "mcp_settings.json");
51924
52034
  agents.push({
51925
52035
  name: "Cline",
51926
52036
  detectPath: cline,
51927
- installed: () => existsSync5(join10(home, ".cline")),
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 readFileSync13, existsSync as existsSync6 } from "node:fs";
51972
- import { join as join11 } from "node:path";
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 = join11(fileURLToPath9(new URL(".", import.meta.url)), "..");
52084
+ var ROOT7 = join12(fileURLToPath9(new URL(".", import.meta.url)), "..");
51975
52085
  function readVersion() {
51976
52086
  try {
51977
- const p = join11(ROOT7, "package.json");
51978
- if (existsSync6(p))
51979
- return JSON.parse(readFileSync13(p, "utf8")).version ?? "1.0.0";
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.32",
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": {