blitzstrike 1.0.32 → 1.0.34

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