@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 +174 -27
- package/dist/cli.mjs +174 -27
- package/dist/dashboard.mjs +11 -2
- package/dist/index.js +10 -1
- package/dist/index.mjs +10 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
58001
|
+
return escapeUnsafe(redact(text));
|
|
57991
58002
|
}
|
|
57992
58003
|
var INPUT_PREVIEW_MAX = 80;
|
|
57993
58004
|
function inputPreview(raw) {
|
|
57994
|
-
const
|
|
57995
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
59155
|
-
|
|
59156
|
-
|
|
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 (${
|
|
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 (
|
|
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(
|
|
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: ${
|
|
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(
|
|
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(
|
|
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 =
|
|
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 " + (
|
|
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
|
-
|
|
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
|
|
57983
|
+
return escapeUnsafe(redact(text));
|
|
57973
57984
|
}
|
|
57974
57985
|
var INPUT_PREVIEW_MAX = 80;
|
|
57975
57986
|
function inputPreview(raw) {
|
|
57976
|
-
const
|
|
57977
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
59137
|
-
|
|
59138
|
-
|
|
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 (${
|
|
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 (
|
|
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(
|
|
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: ${
|
|
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(
|
|
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(
|
|
70916
|
+
statusStr = chalk47.gray(tierNote ?? "");
|
|
70770
70917
|
} else if (tier.status === "active") {
|
|
70771
|
-
const loc = tier.path ? chalk47.gray(tier.path) : "";
|
|
70772
|
-
const 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 " + (
|
|
70922
|
+
statusStr = chalk47.gray("\u25CB " + (tierNote ?? "not found"));
|
|
70776
70923
|
}
|
|
70777
70924
|
console.log(`${num3} ${chalk47.white(label3)} ${statusStr}`);
|
|
70778
70925
|
}
|
package/dist/dashboard.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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",
|