blitzstrike 1.0.64 → 1.0.65

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,21 @@ 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.65] — 2026-09-13
9
+
10
+ ### Fixed — breadth-scanner false positives (live MCP verification)
11
+
12
+ Found via an LLM-driven verification run (agent → `blitz_scan`/`eagle_eye2`):
13
+
14
+ - **Sink tokens in comments/strings no longer reported.** `scanFile`/
15
+ `traceFunction`/`grepInFunctions` now match against a comment- and
16
+ string-literal-stripped view of the source (positions + line numbers
17
+ preserved). A `// use extract($_REQUEST)` comment or a
18
+ `"https://…/system("` string is not a sink.
19
+ - Rebuilt `dist/` so the fix ships in the published bundle (a long-running MCP
20
+ server must be restarted to pick up new `dist/` code — skills load fresh from
21
+ `skills/`, but compiled engine code is loaded once at startup).
22
+
8
23
  ## [1.0.64] — 2026-09-13
9
24
 
10
25
  ### Added — agent-native skills (LLM-driven orchestration doctrine)
package/dist/index.js CHANGED
@@ -6944,6 +6944,65 @@ function lineno(text, pos) {
6944
6944
  }
6945
6945
  return n;
6946
6946
  }
6947
+ function stripComments(text) {
6948
+ const out = text.split("");
6949
+ const n = text.length;
6950
+ let i = 0;
6951
+ while (i < n) {
6952
+ const c = text[i];
6953
+ const nc = text[i + 1];
6954
+ if (c === "'" || c === '"') {
6955
+ const q = c;
6956
+ out[i] = " ";
6957
+ i++;
6958
+ while (i < n && text[i] !== q) {
6959
+ if (text[i] === "\\") {
6960
+ out[i] = " ";
6961
+ i++;
6962
+ if (i < n)
6963
+ out[i] = " ";
6964
+ i++;
6965
+ continue;
6966
+ }
6967
+ if (text[i] !== `
6968
+ `)
6969
+ out[i] = " ";
6970
+ i++;
6971
+ }
6972
+ if (i < n)
6973
+ out[i] = " ";
6974
+ i++;
6975
+ continue;
6976
+ }
6977
+ if (c === "/" && nc === "/" || c === "#") {
6978
+ while (i < n && text[i] !== `
6979
+ `) {
6980
+ out[i] = " ";
6981
+ i++;
6982
+ }
6983
+ continue;
6984
+ }
6985
+ if (c === "/" && nc === "*") {
6986
+ out[i] = " ";
6987
+ out[i + 1] = " ";
6988
+ i += 2;
6989
+ while (i < n && !(text[i] === "*" && text[i + 1] === "/")) {
6990
+ if (text[i] !== `
6991
+ `)
6992
+ out[i] = " ";
6993
+ i++;
6994
+ }
6995
+ if (i < n) {
6996
+ out[i] = " ";
6997
+ out[i + 1] = " ";
6998
+ i += 2;
6999
+ }
7000
+ continue;
7001
+ }
7002
+ i++;
7003
+ }
7004
+ return out.join("");
7005
+ }
6947
7006
  function iterSourceFiles(root, maxFiles = 5000) {
6948
7007
  const out = [];
6949
7008
  const walk = (dir) => {
@@ -7005,23 +7064,24 @@ function scanFile(path) {
7005
7064
  } catch {
7006
7065
  return { file: path, endpoints: [], sinks: [], auth_gates_present: [], error: "unreadable" };
7007
7066
  }
7067
+ const code = stripComments(text);
7008
7068
  const endpoints = [];
7009
7069
  for (const hook of NOPRIV_HOOKS) {
7010
7070
  const re = new RegExp(escapeRegExp(hook), "g");
7011
7071
  let m;
7012
- while ((m = re.exec(text)) !== null) {
7013
- endpoints.push({ hook, line: lineno(text, m.index) });
7072
+ while ((m = re.exec(code)) !== null) {
7073
+ endpoints.push({ hook, line: lineno(code, m.index) });
7014
7074
  }
7015
7075
  }
7016
7076
  const sinks = [];
7017
7077
  for (const [sink, bugclass] of Object.entries(SINKS)) {
7018
7078
  const re = new RegExp(escapeRegExp(sink), "g");
7019
7079
  let m;
7020
- while ((m = re.exec(text)) !== null) {
7021
- sinks.push({ sink, class: bugclass, line: lineno(text, m.index) });
7080
+ while ((m = re.exec(code)) !== null) {
7081
+ sinks.push({ sink, class: bugclass, line: lineno(code, m.index) });
7022
7082
  }
7023
7083
  }
7024
- const gates = AUTH_GATES.filter((g) => text.includes(g));
7084
+ const gates = AUTH_GATES.filter((g) => code.includes(g));
7025
7085
  const lines = text.split(`
7026
7086
  `).length;
7027
7087
  return { file: path, lines, endpoints, sinks, auth_gates_present: gates };
@@ -7042,22 +7102,23 @@ function traceFunction(path, symbol) {
7042
7102
  continue;
7043
7103
  const [start, end] = bounds;
7044
7104
  const body = text.slice(start, end);
7105
+ const bodyCode = stripComments(body);
7045
7106
  const bodyStartLine = lineno(text, start);
7046
7107
  const bodyEndLine = lineno(text, end);
7047
7108
  const innerSinks = [];
7048
7109
  for (const [sink, bugclass] of Object.entries(SINKS)) {
7049
7110
  const re = new RegExp(escapeRegExp(sink), "g");
7050
7111
  let sm;
7051
- while ((sm = re.exec(body)) !== null) {
7112
+ while ((sm = re.exec(bodyCode)) !== null) {
7052
7113
  innerSinks.push({
7053
7114
  sink,
7054
7115
  class: bugclass,
7055
- line: bodyStartLine + body.slice(0, sm.index).split(`
7116
+ line: bodyStartLine + bodyCode.slice(0, sm.index).split(`
7056
7117
  `).length - 1
7057
7118
  });
7058
7119
  }
7059
7120
  }
7060
- const innerGates = AUTH_GATES.filter((g) => body.includes(g));
7121
+ const innerGates = AUTH_GATES.filter((g) => bodyCode.includes(g));
7061
7122
  results.push({
7062
7123
  line: bodyStartLine,
7063
7124
  line_end: bodyEndLine,
@@ -7085,11 +7146,12 @@ function grepInFunctions(root, sink, maxHits = 50) {
7085
7146
  } catch {
7086
7147
  continue;
7087
7148
  }
7149
+ const code = stripComments(text);
7088
7150
  const re = new RegExp(escapeRegExp(sink), "g");
7089
7151
  let m;
7090
- while ((m = re.exec(text)) !== null) {
7091
- const line = lineno(text, m.index);
7092
- const head = text.slice(0, m.index);
7152
+ while ((m = re.exec(code)) !== null) {
7153
+ const line = lineno(code, m.index);
7154
+ const head = code.slice(0, m.index);
7093
7155
  const funcRe = /function\s+&?\w+\s*\(/g;
7094
7156
  const funcs = [];
7095
7157
  let fm;
@@ -7098,10 +7160,10 @@ function grepInFunctions(root, sink, maxHits = 50) {
7098
7160
  if (funcs.length === 0)
7099
7161
  continue;
7100
7162
  const fstart = funcs[funcs.length - 1].index;
7101
- const bounds = findFunctionBounds(text, fstart);
7163
+ const bounds = findFunctionBounds(code, fstart);
7102
7164
  if (!bounds || !(bounds[0] <= m.index && m.index < bounds[1]))
7103
7165
  continue;
7104
- const body = text.slice(bounds[0], bounds[1]);
7166
+ const body = code.slice(bounds[0], bounds[1]);
7105
7167
  const gates = AUTH_GATES.filter((g) => body.includes(g));
7106
7168
  hits.push({
7107
7169
  file: p,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.64",
3
+ "version": "1.0.65",
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": {