@node9/proxy 2.28.0 → 2.29.0

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/dist/cli.js CHANGED
@@ -11103,6 +11103,14 @@ async function checkStatePredicates(predicates) {
11103
11103
  return null;
11104
11104
  }
11105
11105
  }
11106
+ function resolveDaemonPort(env = process.env) {
11107
+ if (env.NODE9_TESTING !== "1") return DEFAULT_DAEMON_PORT;
11108
+ const raw = env.NODE9_DAEMON_PORT;
11109
+ if (raw === void 0 || raw === "") return DEFAULT_DAEMON_PORT;
11110
+ const port = Number(raw);
11111
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) return DEFAULT_DAEMON_PORT;
11112
+ return port;
11113
+ }
11106
11114
  function getInternalToken() {
11107
11115
  try {
11108
11116
  const pidFile = import_path10.default.join(import_os8.default.homedir(), ".node9", "daemon.pid");
@@ -11376,7 +11384,7 @@ async function resolveViaDaemon(id, decision, internalToken, source) {
11376
11384
  signal: AbortSignal.timeout(3e3)
11377
11385
  });
11378
11386
  }
11379
- var import_fs10, import_net, import_path10, import_os8, ACTIVITY_SOCKET_PATH, DAEMON_PORT, DAEMON_HOST;
11387
+ var import_fs10, import_net, import_path10, import_os8, ACTIVITY_SOCKET_PATH, DEFAULT_DAEMON_PORT, DAEMON_PORT, DAEMON_HOST;
11380
11388
  var init_daemon = __esm({
11381
11389
  "src/auth/daemon.ts"() {
11382
11390
  "use strict";
@@ -11385,7 +11393,8 @@ var init_daemon = __esm({
11385
11393
  import_path10 = __toESM(require("path"));
11386
11394
  import_os8 = __toESM(require("os"));
11387
11395
  ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path10.default.join(import_os8.default.tmpdir(), "node9-activity.sock");
11388
- DAEMON_PORT = 7391;
11396
+ DEFAULT_DAEMON_PORT = 7391;
11397
+ DAEMON_PORT = resolveDaemonPort();
11389
11398
  DAEMON_HOST = "127.0.0.1";
11390
11399
  }
11391
11400
  });
@@ -57986,15 +57995,25 @@ var VISIBLE_ESCAPES = {
57986
57995
  function toVisibleEscape(c) {
57987
57996
  return VISIBLE_ESCAPES[c] ?? `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`;
57988
57997
  }
57998
+ var redact = (s) => redactText(s).result;
57999
+ var escapeUnsafe = (s) => s.replace(UNSAFE_CHARS, toVisibleEscape);
57989
58000
  function displaySafe(text) {
57990
- return redactText(text).result.replace(UNSAFE_CHARS, toVisibleEscape);
58001
+ return escapeUnsafe(redact(text));
57991
58002
  }
57992
58003
  var INPUT_PREVIEW_MAX = 80;
57993
58004
  function inputPreview(raw) {
57994
- const safe = displaySafe(raw);
57995
- return safe.length > INPUT_PREVIEW_MAX ? safe.slice(0, INPUT_PREVIEW_MAX - 3) + "\u2026" : safe;
58005
+ const redacted = redact(raw);
58006
+ const points = Array.from(redacted);
58007
+ const cut = points.length > INPUT_PREVIEW_MAX ? points.slice(0, INPUT_PREVIEW_MAX - 3).join("") + "\u2026" : redacted;
58008
+ return escapeUnsafe(cut);
58009
+ }
58010
+ var JSON_RAW_UNSAFE = /[\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g;
58011
+ function toSafeJson(doc) {
58012
+ return JSON.stringify(doc, null, 2).replace(
58013
+ JSON_RAW_UNSAFE,
58014
+ (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`
58015
+ );
57996
58016
  }
57997
- var redact = (s) => redactText(s).result;
57998
58017
  function buildExplainJson(result, rawInput) {
57999
58018
  return {
58000
58019
  schemaVersion: 1,
@@ -58009,7 +58028,11 @@ function buildExplainJson(result, rawInput) {
58009
58028
  detail: redact(s.detail),
58010
58029
  final: s.isFinal === true
58011
58030
  })),
58012
- waterfall: result.waterfall
58031
+ waterfall: result.waterfall.map((t) => ({
58032
+ ...t,
58033
+ ...t.path !== void 0 && { path: redact(t.path) },
58034
+ ...t.note !== void 0 && { note: redact(t.note) }
58035
+ }))
58013
58036
  };
58014
58037
  }
58015
58038
  function buildExplainJsonError(message) {
@@ -58935,6 +58958,88 @@ ${neutralizeMarkers(text)}
58935
58958
  ${f.footer}`;
58936
58959
  }
58937
58960
 
58961
+ // src/tool-output.ts
58962
+ init_dlp();
58963
+ var MAX_DEPTH3 = 8;
58964
+ function collectToolOutputText(toolResponse, opts = {}) {
58965
+ const maxBytes = opts.maxBytes ?? DLP_SCAN_LIMITS.maxStringBytes;
58966
+ const parts = [];
58967
+ let size = 0;
58968
+ let fields = 0;
58969
+ let truncated = false;
58970
+ const push = (s) => {
58971
+ if (s.length === 0) return true;
58972
+ const room = maxBytes - size;
58973
+ if (s.length > room) {
58974
+ if (room > 0) {
58975
+ parts.push(s.slice(0, room));
58976
+ fields++;
58977
+ }
58978
+ size = maxBytes;
58979
+ truncated = true;
58980
+ return false;
58981
+ }
58982
+ parts.push(s);
58983
+ fields++;
58984
+ size += s.length + 1;
58985
+ return true;
58986
+ };
58987
+ const walk = (v, depth) => {
58988
+ if (typeof v === "string") return push(v);
58989
+ if (depth >= MAX_DEPTH3 || v === null || typeof v !== "object") return true;
58990
+ const children = Array.isArray(v) ? v : Object.values(v);
58991
+ for (const child of children) if (!walk(child, depth + 1)) return false;
58992
+ return true;
58993
+ };
58994
+ walk(toolResponse, 0);
58995
+ return { text: parts.join("\n"), truncated, fields };
58996
+ }
58997
+ function shellOutputText(toolResponse) {
58998
+ if (toolResponse && typeof toolResponse === "object" && !Array.isArray(toolResponse)) {
58999
+ const r = toolResponse;
59000
+ if (typeof r.stdout === "string" || typeof r.stderr === "string") {
59001
+ return [r.stdout, r.stderr].filter((s) => typeof s === "string").join("\n");
59002
+ }
59003
+ }
59004
+ return collectToolOutputText(toolResponse).text;
59005
+ }
59006
+ function mapToolOutputStrings(toolResponse, fn) {
59007
+ const walk = (v, depth) => {
59008
+ if (typeof v === "string") return fn(v);
59009
+ if (depth >= MAX_DEPTH3 || v === null || typeof v !== "object") return v;
59010
+ if (Array.isArray(v)) return v.map((x) => walk(x, depth + 1));
59011
+ const out = {};
59012
+ for (const [k, x] of Object.entries(v)) out[k] = walk(x, depth + 1);
59013
+ return out;
59014
+ };
59015
+ return walk(toolResponse, 0);
59016
+ }
59017
+ function frameToolOutputLeaves(toolResponse, isSuspect, frame, neutralize) {
59018
+ const suspect = [];
59019
+ let largestAt = -1;
59020
+ let largestLen = 0;
59021
+ mapToolOutputStrings(toolResponse, (s) => {
59022
+ const i2 = suspect.length;
59023
+ suspect.push(s.length > 0 && isSuspect(s));
59024
+ if (s.length > largestLen) {
59025
+ largestLen = s.length;
59026
+ largestAt = i2;
59027
+ }
59028
+ return s;
59029
+ });
59030
+ const anySuspect = suspect.some(Boolean);
59031
+ let i = 0;
59032
+ return mapToolOutputStrings(toolResponse, (s) => {
59033
+ const at = i++;
59034
+ const pick2 = anySuspect ? suspect[at] : at === largestAt;
59035
+ if (!pick2) return neutralize(s);
59036
+ const f = frame();
59037
+ return `${f.header}
59038
+ ${neutralize(s)}
59039
+ ${f.footer}`;
59040
+ });
59041
+ }
59042
+
58938
59043
  // src/cli/commands/log.ts
58939
59044
  init_hasher();
58940
59045
 
@@ -59080,7 +59185,7 @@ function registerLogCommand(program2) {
59080
59185
  }
59081
59186
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
59082
59187
  const bashCommand = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
59083
- const output = payload.tool_response?.output ?? "";
59188
+ const output = shellOutputText(payload.tool_response);
59084
59189
  if (bashCommand && output) {
59085
59190
  const testResult = detectTestResult(bashCommand, output);
59086
59191
  if (testResult) {
@@ -59132,10 +59237,24 @@ function registerLogCommand(program2) {
59132
59237
  }
59133
59238
  }
59134
59239
  {
59135
- const toolOutput = payload.tool_response?.output;
59240
+ const rawResponse = payload.tool_response;
59241
+ const shimOutput = rawResponse && typeof rawResponse === "object" && typeof rawResponse.output === "string" ? rawResponse.output : void 0;
59242
+ const collected = redactOutputMode ? {
59243
+ text: shimOutput ?? collectToolOutputText(rawResponse, { maxBytes: Number.MAX_SAFE_INTEGER }).text,
59244
+ truncated: false
59245
+ } : collectToolOutputText(rawResponse);
59246
+ const toolOutput = collected.text;
59247
+ if (collected.truncated) {
59248
+ appendToLog(HOOK_DEBUG_LOG, {
59249
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
59250
+ event: "post-tool-scan-truncated",
59251
+ tool,
59252
+ scannedBytes: toolOutput.length
59253
+ });
59254
+ }
59136
59255
  const inj = config.policy.injectionScan;
59137
59256
  const injectionOn = inj.enabled && !inj.allow.includes(tool);
59138
- if (typeof toolOutput === "string" && toolOutput.length > 0) {
59257
+ if (toolOutput.length > 0) {
59139
59258
  if (redactOutputMode) {
59140
59259
  const { result, found } = redactText(toolOutput);
59141
59260
  let out = result;
@@ -59150,19 +59269,36 @@ function registerLogCommand(program2) {
59150
59269
  process.stdout.write(JSON.stringify({ redacted: out, found, injection }) + "\n");
59151
59270
  } else {
59152
59271
  const warnings = [];
59272
+ const inPlace = agent === "Claude Code" && config.policy.dlp.enabled;
59273
+ const redactedNames = /* @__PURE__ */ new Set();
59274
+ let updatedToolOutput = void 0;
59275
+ if (inPlace) {
59276
+ const mapped = mapToolOutputStrings(payload.tool_response, (leaf) => {
59277
+ const r = redactText(leaf);
59278
+ for (const name of r.found) redactedNames.add(name);
59279
+ return r.result;
59280
+ });
59281
+ if (redactedNames.size > 0) updatedToolOutput = mapped;
59282
+ }
59153
59283
  const hit = scanText(toolOutput);
59154
- if (hit) {
59155
- await notifySessionTaint(
59156
- payloadSessionId ?? "",
59157
- `output-secret:${hit.patternName}`
59158
- );
59284
+ const secretName = hit?.patternName ?? [...redactedNames][0];
59285
+ if (secretName) {
59286
+ await notifySessionTaint(payloadSessionId ?? "", `output-secret:${secretName}`);
59159
59287
  warnings.push(
59160
- `\u26A0\uFE0F node9: this tool output contained a credential (${hit.patternName}). Do not echo, store, or transmit it \u2014 treat it as compromised and rotate it. node9 has flagged this session: the next network or write action will require approval.`
59288
+ updatedToolOutput !== void 0 ? `\u26A0\uFE0F node9: this tool output contained a credential (${secretName}); node9 replaced it with [node9-redacted:\u2026] before you read it. Treat the credential as compromised. node9 has flagged this session: the next network or write action will require approval.` : `\u26A0\uFE0F node9: this tool output contained a credential (${secretName}). Do not echo, store, or transmit it \u2014 treat it as compromised and rotate it. node9 has flagged this session: the next network or write action will require approval.`
59161
59289
  );
59162
59290
  }
59163
59291
  if (injectionOn) {
59164
59292
  const m = scanInjection(toolOutput, { tool: rawToolName });
59165
59293
  if (m && atLeastConfidence(m.confidence, inj.minConfidence)) {
59294
+ if (agent === "Claude Code") {
59295
+ updatedToolOutput = frameToolOutputLeaves(
59296
+ updatedToolOutput ?? payload.tool_response,
59297
+ (leaf) => scanInjection(leaf, { tool: rawToolName }) !== null,
59298
+ newUntrustedFrame,
59299
+ neutralizeMarkers
59300
+ );
59301
+ }
59166
59302
  await notifySessionTaint(
59167
59303
  payloadSessionId ?? "",
59168
59304
  `output-injection:${m.signals.join("+")}`
@@ -59172,12 +59308,22 @@ function registerLogCommand(program2) {
59172
59308
  );
59173
59309
  }
59174
59310
  }
59175
- if (warnings.length > 0 && (agent === "Claude Code" || agent === "Codex")) {
59311
+ if (redactedNames.size > 0) {
59312
+ appendToLog(LOCAL_AUDIT_LOG, {
59313
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
59314
+ tool,
59315
+ source: "output-redacted",
59316
+ outputRedacted: [...redactedNames],
59317
+ ...payloadSessionId ? { sessionId: payloadSessionId } : {}
59318
+ });
59319
+ }
59320
+ if ((warnings.length > 0 || updatedToolOutput !== void 0) && (agent === "Claude Code" || agent === "Codex")) {
59176
59321
  process.stdout.write(
59177
59322
  JSON.stringify({
59178
59323
  hookSpecificOutput: {
59179
59324
  hookEventName: "PostToolUse",
59180
- additionalContext: warnings.join("\n\n")
59325
+ ...warnings.length > 0 ? { additionalContext: warnings.join("\n\n") } : {},
59326
+ ...updatedToolOutput !== void 0 ? { updatedToolOutput } : {}
59181
59327
  }
59182
59328
  }) + "\n"
59183
59329
  );
@@ -59196,7 +59342,7 @@ function registerLogCommand(program2) {
59196
59342
  } catch {
59197
59343
  }
59198
59344
  }
59199
- process.exit(0);
59345
+ process.stdout.write("", () => process.exit(0));
59200
59346
  };
59201
59347
  if (data) {
59202
59348
  await logPayload(data);
@@ -70739,7 +70885,7 @@ program.command("explain").description(
70739
70885
  'Machine-readable output (one JSON document; no "decision" field means failure)'
70740
70886
  ).action(async (tool, argsRaw, opts) => {
70741
70887
  const failJson = (message) => {
70742
- console.log(JSON.stringify(buildExplainJsonError(message), null, 2));
70888
+ console.log(toSafeJson(buildExplainJsonError(message)));
70743
70889
  process.exit(1);
70744
70890
  };
70745
70891
  let args = {};
@@ -70751,7 +70897,7 @@ program.command("explain").description(
70751
70897
  } catch {
70752
70898
  if (opts.json) failJson("Invalid JSON in [args]");
70753
70899
  console.error(import_chalk47.default.red(`
70754
- \u274C Invalid JSON: ${inputPreview(trimmed)}
70900
+ \u274C Invalid JSON: ${displaySafe(trimmed)}
70755
70901
  `));
70756
70902
  process.exit(1);
70757
70903
  }
@@ -70767,7 +70913,7 @@ program.command("explain").description(
70767
70913
  throw err2;
70768
70914
  }
70769
70915
  if (opts.json) {
70770
- console.log(JSON.stringify(buildExplainJson(result, argsRaw), null, 2));
70916
+ console.log(toSafeJson(buildExplainJson(result, argsRaw)));
70771
70917
  return;
70772
70918
  }
70773
70919
  console.log("");
@@ -70781,16 +70927,17 @@ program.command("explain").description(
70781
70927
  console.log(import_chalk47.default.bold("Config Sources (Waterfall):"));
70782
70928
  for (const tier of result.waterfall) {
70783
70929
  const num3 = import_chalk47.default.gray(` ${tier.tier}.`);
70784
- const label3 = tier.label.padEnd(16);
70930
+ const label3 = displaySafe(tier.label).padEnd(16);
70931
+ const tierNote = tier.note === void 0 ? void 0 : displaySafe(tier.note);
70785
70932
  let statusStr;
70786
70933
  if (tier.tier === 1) {
70787
- statusStr = import_chalk47.default.gray(tier.note ?? "");
70934
+ statusStr = import_chalk47.default.gray(tierNote ?? "");
70788
70935
  } else if (tier.status === "active") {
70789
- const loc = tier.path ? import_chalk47.default.gray(tier.path) : "";
70790
- const note = tier.note ? import_chalk47.default.gray(`(${tier.note})`) : "";
70936
+ const loc = tier.path ? import_chalk47.default.gray(displaySafe(tier.path)) : "";
70937
+ const note = tierNote ? import_chalk47.default.gray(`(${tierNote})`) : "";
70791
70938
  statusStr = import_chalk47.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
70792
70939
  } else {
70793
- statusStr = import_chalk47.default.gray("\u25CB " + (tier.note ?? "not found"));
70940
+ statusStr = import_chalk47.default.gray("\u25CB " + (tierNote ?? "not found"));
70794
70941
  }
70795
70942
  console.log(`${num3} ${import_chalk47.default.white(label3)} ${statusStr}`);
70796
70943
  }
package/dist/cli.mjs CHANGED
@@ -11109,6 +11109,14 @@ async function checkStatePredicates(predicates) {
11109
11109
  return null;
11110
11110
  }
11111
11111
  }
11112
+ function resolveDaemonPort(env = process.env) {
11113
+ if (env.NODE9_TESTING !== "1") return DEFAULT_DAEMON_PORT;
11114
+ const raw = env.NODE9_DAEMON_PORT;
11115
+ if (raw === void 0 || raw === "") return DEFAULT_DAEMON_PORT;
11116
+ const port = Number(raw);
11117
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) return DEFAULT_DAEMON_PORT;
11118
+ return port;
11119
+ }
11112
11120
  function getInternalToken() {
11113
11121
  try {
11114
11122
  const pidFile = path10.join(os8.homedir(), ".node9", "daemon.pid");
@@ -11382,12 +11390,13 @@ async function resolveViaDaemon(id, decision, internalToken, source) {
11382
11390
  signal: AbortSignal.timeout(3e3)
11383
11391
  });
11384
11392
  }
11385
- var ACTIVITY_SOCKET_PATH, DAEMON_PORT, DAEMON_HOST;
11393
+ var ACTIVITY_SOCKET_PATH, DEFAULT_DAEMON_PORT, DAEMON_PORT, DAEMON_HOST;
11386
11394
  var init_daemon = __esm({
11387
11395
  "src/auth/daemon.ts"() {
11388
11396
  "use strict";
11389
11397
  ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path10.join(os8.tmpdir(), "node9-activity.sock");
11390
- DAEMON_PORT = 7391;
11398
+ DEFAULT_DAEMON_PORT = 7391;
11399
+ DAEMON_PORT = resolveDaemonPort();
11391
11400
  DAEMON_HOST = "127.0.0.1";
11392
11401
  }
11393
11402
  });
@@ -57968,15 +57977,25 @@ var VISIBLE_ESCAPES = {
57968
57977
  function toVisibleEscape(c) {
57969
57978
  return VISIBLE_ESCAPES[c] ?? `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`;
57970
57979
  }
57980
+ var redact = (s) => redactText(s).result;
57981
+ var escapeUnsafe = (s) => s.replace(UNSAFE_CHARS, toVisibleEscape);
57971
57982
  function displaySafe(text) {
57972
- return redactText(text).result.replace(UNSAFE_CHARS, toVisibleEscape);
57983
+ return escapeUnsafe(redact(text));
57973
57984
  }
57974
57985
  var INPUT_PREVIEW_MAX = 80;
57975
57986
  function inputPreview(raw) {
57976
- const safe = displaySafe(raw);
57977
- return safe.length > INPUT_PREVIEW_MAX ? safe.slice(0, INPUT_PREVIEW_MAX - 3) + "\u2026" : safe;
57987
+ const redacted = redact(raw);
57988
+ const points = Array.from(redacted);
57989
+ const cut = points.length > INPUT_PREVIEW_MAX ? points.slice(0, INPUT_PREVIEW_MAX - 3).join("") + "\u2026" : redacted;
57990
+ return escapeUnsafe(cut);
57991
+ }
57992
+ var JSON_RAW_UNSAFE = /[\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g;
57993
+ function toSafeJson(doc) {
57994
+ return JSON.stringify(doc, null, 2).replace(
57995
+ JSON_RAW_UNSAFE,
57996
+ (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`
57997
+ );
57978
57998
  }
57979
- var redact = (s) => redactText(s).result;
57980
57999
  function buildExplainJson(result, rawInput) {
57981
58000
  return {
57982
58001
  schemaVersion: 1,
@@ -57991,7 +58010,11 @@ function buildExplainJson(result, rawInput) {
57991
58010
  detail: redact(s.detail),
57992
58011
  final: s.isFinal === true
57993
58012
  })),
57994
- waterfall: result.waterfall
58013
+ waterfall: result.waterfall.map((t) => ({
58014
+ ...t,
58015
+ ...t.path !== void 0 && { path: redact(t.path) },
58016
+ ...t.note !== void 0 && { note: redact(t.note) }
58017
+ }))
57995
58018
  };
57996
58019
  }
57997
58020
  function buildExplainJsonError(message) {
@@ -58917,6 +58940,88 @@ ${neutralizeMarkers(text)}
58917
58940
  ${f.footer}`;
58918
58941
  }
58919
58942
 
58943
+ // src/tool-output.ts
58944
+ init_dlp();
58945
+ var MAX_DEPTH3 = 8;
58946
+ function collectToolOutputText(toolResponse, opts = {}) {
58947
+ const maxBytes = opts.maxBytes ?? DLP_SCAN_LIMITS.maxStringBytes;
58948
+ const parts = [];
58949
+ let size = 0;
58950
+ let fields = 0;
58951
+ let truncated = false;
58952
+ const push = (s) => {
58953
+ if (s.length === 0) return true;
58954
+ const room = maxBytes - size;
58955
+ if (s.length > room) {
58956
+ if (room > 0) {
58957
+ parts.push(s.slice(0, room));
58958
+ fields++;
58959
+ }
58960
+ size = maxBytes;
58961
+ truncated = true;
58962
+ return false;
58963
+ }
58964
+ parts.push(s);
58965
+ fields++;
58966
+ size += s.length + 1;
58967
+ return true;
58968
+ };
58969
+ const walk = (v, depth) => {
58970
+ if (typeof v === "string") return push(v);
58971
+ if (depth >= MAX_DEPTH3 || v === null || typeof v !== "object") return true;
58972
+ const children = Array.isArray(v) ? v : Object.values(v);
58973
+ for (const child of children) if (!walk(child, depth + 1)) return false;
58974
+ return true;
58975
+ };
58976
+ walk(toolResponse, 0);
58977
+ return { text: parts.join("\n"), truncated, fields };
58978
+ }
58979
+ function shellOutputText(toolResponse) {
58980
+ if (toolResponse && typeof toolResponse === "object" && !Array.isArray(toolResponse)) {
58981
+ const r = toolResponse;
58982
+ if (typeof r.stdout === "string" || typeof r.stderr === "string") {
58983
+ return [r.stdout, r.stderr].filter((s) => typeof s === "string").join("\n");
58984
+ }
58985
+ }
58986
+ return collectToolOutputText(toolResponse).text;
58987
+ }
58988
+ function mapToolOutputStrings(toolResponse, fn) {
58989
+ const walk = (v, depth) => {
58990
+ if (typeof v === "string") return fn(v);
58991
+ if (depth >= MAX_DEPTH3 || v === null || typeof v !== "object") return v;
58992
+ if (Array.isArray(v)) return v.map((x) => walk(x, depth + 1));
58993
+ const out = {};
58994
+ for (const [k, x] of Object.entries(v)) out[k] = walk(x, depth + 1);
58995
+ return out;
58996
+ };
58997
+ return walk(toolResponse, 0);
58998
+ }
58999
+ function frameToolOutputLeaves(toolResponse, isSuspect, frame, neutralize) {
59000
+ const suspect = [];
59001
+ let largestAt = -1;
59002
+ let largestLen = 0;
59003
+ mapToolOutputStrings(toolResponse, (s) => {
59004
+ const i2 = suspect.length;
59005
+ suspect.push(s.length > 0 && isSuspect(s));
59006
+ if (s.length > largestLen) {
59007
+ largestLen = s.length;
59008
+ largestAt = i2;
59009
+ }
59010
+ return s;
59011
+ });
59012
+ const anySuspect = suspect.some(Boolean);
59013
+ let i = 0;
59014
+ return mapToolOutputStrings(toolResponse, (s) => {
59015
+ const at = i++;
59016
+ const pick2 = anySuspect ? suspect[at] : at === largestAt;
59017
+ if (!pick2) return neutralize(s);
59018
+ const f = frame();
59019
+ return `${f.header}
59020
+ ${neutralize(s)}
59021
+ ${f.footer}`;
59022
+ });
59023
+ }
59024
+
58920
59025
  // src/cli/commands/log.ts
58921
59026
  init_hasher();
58922
59027
 
@@ -59062,7 +59167,7 @@ function registerLogCommand(program2) {
59062
59167
  }
59063
59168
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
59064
59169
  const bashCommand = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
59065
- const output = payload.tool_response?.output ?? "";
59170
+ const output = shellOutputText(payload.tool_response);
59066
59171
  if (bashCommand && output) {
59067
59172
  const testResult = detectTestResult(bashCommand, output);
59068
59173
  if (testResult) {
@@ -59114,10 +59219,24 @@ function registerLogCommand(program2) {
59114
59219
  }
59115
59220
  }
59116
59221
  {
59117
- const toolOutput = payload.tool_response?.output;
59222
+ const rawResponse = payload.tool_response;
59223
+ const shimOutput = rawResponse && typeof rawResponse === "object" && typeof rawResponse.output === "string" ? rawResponse.output : void 0;
59224
+ const collected = redactOutputMode ? {
59225
+ text: shimOutput ?? collectToolOutputText(rawResponse, { maxBytes: Number.MAX_SAFE_INTEGER }).text,
59226
+ truncated: false
59227
+ } : collectToolOutputText(rawResponse);
59228
+ const toolOutput = collected.text;
59229
+ if (collected.truncated) {
59230
+ appendToLog(HOOK_DEBUG_LOG, {
59231
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
59232
+ event: "post-tool-scan-truncated",
59233
+ tool,
59234
+ scannedBytes: toolOutput.length
59235
+ });
59236
+ }
59118
59237
  const inj = config.policy.injectionScan;
59119
59238
  const injectionOn = inj.enabled && !inj.allow.includes(tool);
59120
- if (typeof toolOutput === "string" && toolOutput.length > 0) {
59239
+ if (toolOutput.length > 0) {
59121
59240
  if (redactOutputMode) {
59122
59241
  const { result, found } = redactText(toolOutput);
59123
59242
  let out = result;
@@ -59132,19 +59251,36 @@ function registerLogCommand(program2) {
59132
59251
  process.stdout.write(JSON.stringify({ redacted: out, found, injection }) + "\n");
59133
59252
  } else {
59134
59253
  const warnings = [];
59254
+ const inPlace = agent === "Claude Code" && config.policy.dlp.enabled;
59255
+ const redactedNames = /* @__PURE__ */ new Set();
59256
+ let updatedToolOutput = void 0;
59257
+ if (inPlace) {
59258
+ const mapped = mapToolOutputStrings(payload.tool_response, (leaf) => {
59259
+ const r = redactText(leaf);
59260
+ for (const name of r.found) redactedNames.add(name);
59261
+ return r.result;
59262
+ });
59263
+ if (redactedNames.size > 0) updatedToolOutput = mapped;
59264
+ }
59135
59265
  const hit = scanText(toolOutput);
59136
- if (hit) {
59137
- await notifySessionTaint(
59138
- payloadSessionId ?? "",
59139
- `output-secret:${hit.patternName}`
59140
- );
59266
+ const secretName = hit?.patternName ?? [...redactedNames][0];
59267
+ if (secretName) {
59268
+ await notifySessionTaint(payloadSessionId ?? "", `output-secret:${secretName}`);
59141
59269
  warnings.push(
59142
- `\u26A0\uFE0F node9: this tool output contained a credential (${hit.patternName}). Do not echo, store, or transmit it \u2014 treat it as compromised and rotate it. node9 has flagged this session: the next network or write action will require approval.`
59270
+ updatedToolOutput !== void 0 ? `\u26A0\uFE0F node9: this tool output contained a credential (${secretName}); node9 replaced it with [node9-redacted:\u2026] before you read it. Treat the credential as compromised. node9 has flagged this session: the next network or write action will require approval.` : `\u26A0\uFE0F node9: this tool output contained a credential (${secretName}). Do not echo, store, or transmit it \u2014 treat it as compromised and rotate it. node9 has flagged this session: the next network or write action will require approval.`
59143
59271
  );
59144
59272
  }
59145
59273
  if (injectionOn) {
59146
59274
  const m = scanInjection(toolOutput, { tool: rawToolName });
59147
59275
  if (m && atLeastConfidence(m.confidence, inj.minConfidence)) {
59276
+ if (agent === "Claude Code") {
59277
+ updatedToolOutput = frameToolOutputLeaves(
59278
+ updatedToolOutput ?? payload.tool_response,
59279
+ (leaf) => scanInjection(leaf, { tool: rawToolName }) !== null,
59280
+ newUntrustedFrame,
59281
+ neutralizeMarkers
59282
+ );
59283
+ }
59148
59284
  await notifySessionTaint(
59149
59285
  payloadSessionId ?? "",
59150
59286
  `output-injection:${m.signals.join("+")}`
@@ -59154,12 +59290,22 @@ function registerLogCommand(program2) {
59154
59290
  );
59155
59291
  }
59156
59292
  }
59157
- if (warnings.length > 0 && (agent === "Claude Code" || agent === "Codex")) {
59293
+ if (redactedNames.size > 0) {
59294
+ appendToLog(LOCAL_AUDIT_LOG, {
59295
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
59296
+ tool,
59297
+ source: "output-redacted",
59298
+ outputRedacted: [...redactedNames],
59299
+ ...payloadSessionId ? { sessionId: payloadSessionId } : {}
59300
+ });
59301
+ }
59302
+ if ((warnings.length > 0 || updatedToolOutput !== void 0) && (agent === "Claude Code" || agent === "Codex")) {
59158
59303
  process.stdout.write(
59159
59304
  JSON.stringify({
59160
59305
  hookSpecificOutput: {
59161
59306
  hookEventName: "PostToolUse",
59162
- additionalContext: warnings.join("\n\n")
59307
+ ...warnings.length > 0 ? { additionalContext: warnings.join("\n\n") } : {},
59308
+ ...updatedToolOutput !== void 0 ? { updatedToolOutput } : {}
59163
59309
  }
59164
59310
  }) + "\n"
59165
59311
  );
@@ -59178,7 +59324,7 @@ function registerLogCommand(program2) {
59178
59324
  } catch {
59179
59325
  }
59180
59326
  }
59181
- process.exit(0);
59327
+ process.stdout.write("", () => process.exit(0));
59182
59328
  };
59183
59329
  if (data) {
59184
59330
  await logPayload(data);
@@ -70721,7 +70867,7 @@ program.command("explain").description(
70721
70867
  'Machine-readable output (one JSON document; no "decision" field means failure)'
70722
70868
  ).action(async (tool, argsRaw, opts) => {
70723
70869
  const failJson = (message) => {
70724
- console.log(JSON.stringify(buildExplainJsonError(message), null, 2));
70870
+ console.log(toSafeJson(buildExplainJsonError(message)));
70725
70871
  process.exit(1);
70726
70872
  };
70727
70873
  let args = {};
@@ -70733,7 +70879,7 @@ program.command("explain").description(
70733
70879
  } catch {
70734
70880
  if (opts.json) failJson("Invalid JSON in [args]");
70735
70881
  console.error(chalk47.red(`
70736
- \u274C Invalid JSON: ${inputPreview(trimmed)}
70882
+ \u274C Invalid JSON: ${displaySafe(trimmed)}
70737
70883
  `));
70738
70884
  process.exit(1);
70739
70885
  }
@@ -70749,7 +70895,7 @@ program.command("explain").description(
70749
70895
  throw err2;
70750
70896
  }
70751
70897
  if (opts.json) {
70752
- console.log(JSON.stringify(buildExplainJson(result, argsRaw), null, 2));
70898
+ console.log(toSafeJson(buildExplainJson(result, argsRaw)));
70753
70899
  return;
70754
70900
  }
70755
70901
  console.log("");
@@ -70763,16 +70909,17 @@ program.command("explain").description(
70763
70909
  console.log(chalk47.bold("Config Sources (Waterfall):"));
70764
70910
  for (const tier of result.waterfall) {
70765
70911
  const num3 = chalk47.gray(` ${tier.tier}.`);
70766
- const label3 = tier.label.padEnd(16);
70912
+ const label3 = displaySafe(tier.label).padEnd(16);
70913
+ const tierNote = tier.note === void 0 ? void 0 : displaySafe(tier.note);
70767
70914
  let statusStr;
70768
70915
  if (tier.tier === 1) {
70769
- statusStr = chalk47.gray(tier.note ?? "");
70916
+ statusStr = chalk47.gray(tierNote ?? "");
70770
70917
  } else if (tier.status === "active") {
70771
- const loc = tier.path ? chalk47.gray(tier.path) : "";
70772
- const note = tier.note ? chalk47.gray(`(${tier.note})`) : "";
70918
+ const loc = tier.path ? chalk47.gray(displaySafe(tier.path)) : "";
70919
+ const note = tierNote ? chalk47.gray(`(${tierNote})`) : "";
70773
70920
  statusStr = chalk47.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
70774
70921
  } else {
70775
- statusStr = chalk47.gray("\u25CB " + (tier.note ?? "not found"));
70922
+ statusStr = chalk47.gray("\u25CB " + (tierNote ?? "not found"));
70776
70923
  }
70777
70924
  console.log(`${num3} ${chalk47.white(label3)} ${statusStr}`);
70778
70925
  }
@@ -5380,6 +5380,14 @@ var init_blast = __esm({
5380
5380
  import fs4 from "fs";
5381
5381
  import path5 from "path";
5382
5382
  import os5 from "os";
5383
+ function resolveDaemonPort(env = process.env) {
5384
+ if (env.NODE9_TESTING !== "1") return DEFAULT_DAEMON_PORT;
5385
+ const raw = env.NODE9_DAEMON_PORT;
5386
+ if (raw === void 0 || raw === "") return DEFAULT_DAEMON_PORT;
5387
+ const port = Number(raw);
5388
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) return DEFAULT_DAEMON_PORT;
5389
+ return port;
5390
+ }
5383
5391
  function getInternalToken() {
5384
5392
  try {
5385
5393
  const pidFile = path5.join(os5.homedir(), ".node9", "daemon.pid");
@@ -5391,12 +5399,13 @@ function getInternalToken() {
5391
5399
  return null;
5392
5400
  }
5393
5401
  }
5394
- var ACTIVITY_SOCKET_PATH, DAEMON_PORT, DAEMON_HOST;
5402
+ var ACTIVITY_SOCKET_PATH, DEFAULT_DAEMON_PORT, DAEMON_PORT, DAEMON_HOST;
5395
5403
  var init_daemon = __esm({
5396
5404
  "src/auth/daemon.ts"() {
5397
5405
  "use strict";
5398
5406
  ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path5.join(os5.tmpdir(), "node9-activity.sock");
5399
- DAEMON_PORT = 7391;
5407
+ DEFAULT_DAEMON_PORT = 7391;
5408
+ DAEMON_PORT = resolveDaemonPort();
5400
5409
  DAEMON_HOST = "127.0.0.1";
5401
5410
  }
5402
5411
  });
package/dist/index.js CHANGED
@@ -9625,7 +9625,16 @@ async function checkStatePredicates(predicates) {
9625
9625
  return null;
9626
9626
  }
9627
9627
  }
9628
- var DAEMON_PORT = 7391;
9628
+ var DEFAULT_DAEMON_PORT = 7391;
9629
+ function resolveDaemonPort(env = process.env) {
9630
+ if (env.NODE9_TESTING !== "1") return DEFAULT_DAEMON_PORT;
9631
+ const raw = env.NODE9_DAEMON_PORT;
9632
+ if (raw === void 0 || raw === "") return DEFAULT_DAEMON_PORT;
9633
+ const port = Number(raw);
9634
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) return DEFAULT_DAEMON_PORT;
9635
+ return port;
9636
+ }
9637
+ var DAEMON_PORT = resolveDaemonPort();
9629
9638
  var DAEMON_HOST = "127.0.0.1";
9630
9639
  function getInternalToken() {
9631
9640
  try {
package/dist/index.mjs CHANGED
@@ -9595,7 +9595,16 @@ async function checkStatePredicates(predicates) {
9595
9595
  return null;
9596
9596
  }
9597
9597
  }
9598
- var DAEMON_PORT = 7391;
9598
+ var DEFAULT_DAEMON_PORT = 7391;
9599
+ function resolveDaemonPort(env = process.env) {
9600
+ if (env.NODE9_TESTING !== "1") return DEFAULT_DAEMON_PORT;
9601
+ const raw = env.NODE9_DAEMON_PORT;
9602
+ if (raw === void 0 || raw === "") return DEFAULT_DAEMON_PORT;
9603
+ const port = Number(raw);
9604
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) return DEFAULT_DAEMON_PORT;
9605
+ return port;
9606
+ }
9607
+ var DAEMON_PORT = resolveDaemonPort();
9599
9608
  var DAEMON_HOST = "127.0.0.1";
9600
9609
  function getInternalToken() {
9601
9610
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "2.28.0",
3
+ "version": "2.29.0",
4
4
  "description": "Access control for AI agents. Set what Claude Code, Codex, Gemini, Cursor and any MCP server are allowed to do, review risky actions before they run, and keep every action on the record.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",