@node9/proxy 1.21.5 → 1.22.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 +211 -14
- package/dist/cli.mjs +211 -14
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6359,7 +6359,7 @@ function teardownClaude() {
|
|
|
6359
6359
|
let changed = false;
|
|
6360
6360
|
const settings = readJson(hooksPath);
|
|
6361
6361
|
if (settings?.hooks) {
|
|
6362
|
-
for (const event of ["PreToolUse", "PostToolUse"]) {
|
|
6362
|
+
for (const event of ["PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
|
|
6363
6363
|
const before = settings.hooks[event]?.length ?? 0;
|
|
6364
6364
|
settings.hooks[event] = settings.hooks[event]?.filter(
|
|
6365
6365
|
(m) => !m.hooks.some((h) => isNode9Hook(h.command))
|
|
@@ -6540,6 +6540,33 @@ async function setupClaude() {
|
|
|
6540
6540
|
}
|
|
6541
6541
|
}
|
|
6542
6542
|
}
|
|
6543
|
+
const hasPromptHook = settings.hooks.UserPromptSubmit?.some(
|
|
6544
|
+
(m) => m.hooks.some((h) => isNode9Hook(h.command))
|
|
6545
|
+
);
|
|
6546
|
+
if (!hasPromptHook) {
|
|
6547
|
+
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
|
|
6548
|
+
settings.hooks.UserPromptSubmit.push({
|
|
6549
|
+
matcher: ".*",
|
|
6550
|
+
hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 600 }]
|
|
6551
|
+
});
|
|
6552
|
+
console.log(import_chalk.default.green(" \u2705 UserPromptSubmit hook added \u2192 node9 check (prompt DLP)"));
|
|
6553
|
+
hooksChanged = true;
|
|
6554
|
+
anythingChanged = true;
|
|
6555
|
+
} else if (settings.hooks.UserPromptSubmit) {
|
|
6556
|
+
for (const matcher of settings.hooks.UserPromptSubmit) {
|
|
6557
|
+
for (const h of matcher.hooks) {
|
|
6558
|
+
const cmd = h.command ?? "";
|
|
6559
|
+
if (isNode9Hook(cmd) && isStaleHookCommand(cmd)) {
|
|
6560
|
+
h.command = fullPathCommand("check");
|
|
6561
|
+
console.log(
|
|
6562
|
+
import_chalk.default.yellow(" \u{1F527} UserPromptSubmit hook repaired (stale path \u2192 current binary)")
|
|
6563
|
+
);
|
|
6564
|
+
hooksChanged = true;
|
|
6565
|
+
anythingChanged = true;
|
|
6566
|
+
}
|
|
6567
|
+
}
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6543
6570
|
if (!hasNode9McpServer(servers)) {
|
|
6544
6571
|
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
6545
6572
|
claudeConfig.mcpServers = servers;
|
|
@@ -6826,9 +6853,79 @@ function writeToml(filePath, data) {
|
|
|
6826
6853
|
async function setupCodex() {
|
|
6827
6854
|
const homeDir2 = import_os11.default.homedir();
|
|
6828
6855
|
const configPath = import_path14.default.join(homeDir2, ".codex", "config.toml");
|
|
6856
|
+
const hooksPath = import_path14.default.join(homeDir2, ".codex", "hooks.json");
|
|
6829
6857
|
const config = readToml(configPath) ?? {};
|
|
6830
6858
|
const servers = config.mcp_servers ?? {};
|
|
6831
6859
|
let anythingChanged = false;
|
|
6860
|
+
const hooksFile = readJson(hooksPath) ?? {};
|
|
6861
|
+
if (!hooksFile.hooks) hooksFile.hooks = {};
|
|
6862
|
+
let hooksChanged = false;
|
|
6863
|
+
if (!hooksFile.hooks.PreToolUse) hooksFile.hooks.PreToolUse = [];
|
|
6864
|
+
for (const matcher of CODEX_PRE_TOOL_MATCHERS) {
|
|
6865
|
+
const existing = hooksFile.hooks.PreToolUse.find((m) => m.matcher === matcher);
|
|
6866
|
+
if (!existing) {
|
|
6867
|
+
hooksFile.hooks.PreToolUse.push({
|
|
6868
|
+
matcher,
|
|
6869
|
+
hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 600 }]
|
|
6870
|
+
});
|
|
6871
|
+
hooksChanged = true;
|
|
6872
|
+
} else {
|
|
6873
|
+
for (const h of existing.hooks) {
|
|
6874
|
+
const cmd = h.command ?? "";
|
|
6875
|
+
if (isNode9Hook(cmd) && isStaleHookCommand(cmd)) {
|
|
6876
|
+
h.command = fullPathCommand("check");
|
|
6877
|
+
hooksChanged = true;
|
|
6878
|
+
}
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
}
|
|
6882
|
+
if (!hooksFile.hooks.UserPromptSubmit) hooksFile.hooks.UserPromptSubmit = [];
|
|
6883
|
+
const hasPromptHook = hooksFile.hooks.UserPromptSubmit.some(
|
|
6884
|
+
(m) => m.hooks.some((h) => isNode9Hook(h.command))
|
|
6885
|
+
);
|
|
6886
|
+
if (!hasPromptHook) {
|
|
6887
|
+
hooksFile.hooks.UserPromptSubmit.push({
|
|
6888
|
+
hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 600 }]
|
|
6889
|
+
});
|
|
6890
|
+
hooksChanged = true;
|
|
6891
|
+
} else {
|
|
6892
|
+
for (const m of hooksFile.hooks.UserPromptSubmit) {
|
|
6893
|
+
for (const h of m.hooks) {
|
|
6894
|
+
const cmd = h.command ?? "";
|
|
6895
|
+
if (isNode9Hook(cmd) && isStaleHookCommand(cmd)) {
|
|
6896
|
+
h.command = fullPathCommand("check");
|
|
6897
|
+
hooksChanged = true;
|
|
6898
|
+
}
|
|
6899
|
+
}
|
|
6900
|
+
}
|
|
6901
|
+
}
|
|
6902
|
+
if (!hooksFile.hooks.PostToolUse) hooksFile.hooks.PostToolUse = [];
|
|
6903
|
+
const hasPostHook = hooksFile.hooks.PostToolUse.some(
|
|
6904
|
+
(m) => m.hooks.some((h) => isNode9Hook(h.command))
|
|
6905
|
+
);
|
|
6906
|
+
if (!hasPostHook) {
|
|
6907
|
+
hooksFile.hooks.PostToolUse.push({
|
|
6908
|
+
matcher: ".*",
|
|
6909
|
+
hooks: [{ type: "command", command: fullPathCommand("log"), timeout: 600 }]
|
|
6910
|
+
});
|
|
6911
|
+
hooksChanged = true;
|
|
6912
|
+
} else {
|
|
6913
|
+
for (const m of hooksFile.hooks.PostToolUse) {
|
|
6914
|
+
for (const h of m.hooks) {
|
|
6915
|
+
const cmd = h.command ?? "";
|
|
6916
|
+
if (isNode9Hook(cmd) && isStaleHookCommand(cmd)) {
|
|
6917
|
+
h.command = fullPathCommand("log");
|
|
6918
|
+
hooksChanged = true;
|
|
6919
|
+
}
|
|
6920
|
+
}
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
if (hooksChanged) {
|
|
6924
|
+
writeJson(hooksPath, hooksFile);
|
|
6925
|
+
console.log(import_chalk.default.green(" \u2705 Codex hooks added \u2192 node9 check / node9 log"));
|
|
6926
|
+
anythingChanged = true;
|
|
6927
|
+
}
|
|
6928
|
+
const hooksInstalled = (hooksFile.hooks?.PreToolUse?.length ?? 0) > 0;
|
|
6832
6929
|
if (!hasNode9McpServer(servers)) {
|
|
6833
6930
|
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
6834
6931
|
config.mcp_servers = servers;
|
|
@@ -6868,23 +6965,39 @@ async function setupCodex() {
|
|
|
6868
6965
|
}
|
|
6869
6966
|
console.log("");
|
|
6870
6967
|
}
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
" \u26A0\uFE0F Note: Codex does not yet support native pre-execution hooks.\n MCP proxy wrapping is the only supported protection mode for Codex.\n Native bash and file operations are not monitored."
|
|
6874
|
-
)
|
|
6875
|
-
);
|
|
6876
|
-
console.log("");
|
|
6877
|
-
if (!anythingChanged && serversToWrap.length === 0) {
|
|
6968
|
+
const hooksDisabled = config.features?.hooks === false || config.codex_hooks === false;
|
|
6969
|
+
if (hooksDisabled) {
|
|
6878
6970
|
console.log(
|
|
6879
|
-
import_chalk.default.
|
|
6880
|
-
"\
|
|
6971
|
+
import_chalk.default.yellow(
|
|
6972
|
+
" \u26A0\uFE0F Codex hooks are disabled in ~/.codex/config.toml ([features].hooks = false).\n Re-enable hooks to activate Node9 shield evaluation on Bash, apply_patch,\n MCP tool calls, and prompt submissions. Until then, only MCP proxy wrapping\n is active."
|
|
6973
|
+
)
|
|
6974
|
+
);
|
|
6975
|
+
console.log("");
|
|
6976
|
+
}
|
|
6977
|
+
const printCodexTrustReminder = () => {
|
|
6978
|
+
console.log(
|
|
6979
|
+
import_chalk.default.yellow(
|
|
6980
|
+
" \u279C Open Codex and run /hooks to review and trust the Node9 entries.\n Until trusted, only MCP proxy wrapping is active."
|
|
6881
6981
|
)
|
|
6882
6982
|
);
|
|
6983
|
+
};
|
|
6984
|
+
if (!anythingChanged && serversToWrap.length === 0) {
|
|
6985
|
+
if (hooksInstalled) {
|
|
6986
|
+
console.log(import_chalk.default.blue("\u2139\uFE0F Codex hooks already installed."));
|
|
6987
|
+
printCodexTrustReminder();
|
|
6988
|
+
} else {
|
|
6989
|
+
console.log(
|
|
6990
|
+
import_chalk.default.blue(
|
|
6991
|
+
"\u2139\uFE0F No MCP servers found to wrap. Add MCP servers to ~/.codex/config.toml and re-run."
|
|
6992
|
+
)
|
|
6993
|
+
);
|
|
6994
|
+
}
|
|
6883
6995
|
printDaemonTip();
|
|
6884
6996
|
return;
|
|
6885
6997
|
}
|
|
6886
6998
|
if (anythingChanged) {
|
|
6887
|
-
console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9
|
|
6999
|
+
console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 hooks installed for Codex."));
|
|
7000
|
+
printCodexTrustReminder();
|
|
6888
7001
|
console.log(import_chalk.default.gray(" Restart Codex for changes to take effect."));
|
|
6889
7002
|
printDaemonTip();
|
|
6890
7003
|
}
|
|
@@ -6892,6 +7005,23 @@ async function setupCodex() {
|
|
|
6892
7005
|
function teardownCodex() {
|
|
6893
7006
|
const homeDir2 = import_os11.default.homedir();
|
|
6894
7007
|
const configPath = import_path14.default.join(homeDir2, ".codex", "config.toml");
|
|
7008
|
+
const hooksPath = import_path14.default.join(homeDir2, ".codex", "hooks.json");
|
|
7009
|
+
const hooksFile = readJson(hooksPath);
|
|
7010
|
+
if (hooksFile?.hooks) {
|
|
7011
|
+
let hooksChanged = false;
|
|
7012
|
+
for (const event of ["PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
|
|
7013
|
+
const before = hooksFile.hooks[event]?.length ?? 0;
|
|
7014
|
+
hooksFile.hooks[event] = hooksFile.hooks[event]?.filter(
|
|
7015
|
+
(m) => !m.hooks.some((h) => isNode9Hook(h.command))
|
|
7016
|
+
);
|
|
7017
|
+
if ((hooksFile.hooks[event]?.length ?? 0) < before) hooksChanged = true;
|
|
7018
|
+
if (hooksFile.hooks[event]?.length === 0) delete hooksFile.hooks[event];
|
|
7019
|
+
}
|
|
7020
|
+
if (hooksChanged) {
|
|
7021
|
+
writeJson(hooksPath, hooksFile);
|
|
7022
|
+
console.log(import_chalk.default.green(" \u2705 Removed Node9 hooks from ~/.codex/hooks.json"));
|
|
7023
|
+
}
|
|
7024
|
+
}
|
|
6895
7025
|
const config = readToml(configPath);
|
|
6896
7026
|
if (!config?.mcp_servers) {
|
|
6897
7027
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.codex/config.toml not found \u2014 nothing to remove"));
|
|
@@ -7350,7 +7480,7 @@ function getAgentsStatus(homeDir2 = import_os11.default.homedir()) {
|
|
|
7350
7480
|
}
|
|
7351
7481
|
];
|
|
7352
7482
|
}
|
|
7353
|
-
var import_fs12, import_path14, import_os11, import_chalk, import_prompts, import_smol_toml, NODE9_MCP_SERVER_ENTRY;
|
|
7483
|
+
var import_fs12, import_path14, import_os11, import_chalk, import_prompts, import_smol_toml, NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS;
|
|
7354
7484
|
var init_setup = __esm({
|
|
7355
7485
|
"src/setup.ts"() {
|
|
7356
7486
|
"use strict";
|
|
@@ -7361,6 +7491,7 @@ var init_setup = __esm({
|
|
|
7361
7491
|
import_prompts = require("@inquirer/prompts");
|
|
7362
7492
|
import_smol_toml = require("smol-toml");
|
|
7363
7493
|
NODE9_MCP_SERVER_ENTRY = { command: "node9", args: ["mcp-server"] };
|
|
7494
|
+
CODEX_PRE_TOOL_MATCHERS = ["^Bash$", "^apply_patch$", "^mcp__.*"];
|
|
7364
7495
|
}
|
|
7365
7496
|
});
|
|
7366
7497
|
|
|
@@ -15655,10 +15786,15 @@ function resolveUserSkillRoot(entry, cwd) {
|
|
|
15655
15786
|
}
|
|
15656
15787
|
|
|
15657
15788
|
// src/cli/commands/check.ts
|
|
15789
|
+
init_dlp();
|
|
15790
|
+
init_audit();
|
|
15658
15791
|
function sanitize2(value) {
|
|
15659
15792
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
15660
15793
|
}
|
|
15661
15794
|
function detectAiAgent(payload) {
|
|
15795
|
+
if (payload.turn_id !== void 0) {
|
|
15796
|
+
return "Codex";
|
|
15797
|
+
}
|
|
15662
15798
|
if (payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0) {
|
|
15663
15799
|
return "Claude Code";
|
|
15664
15800
|
}
|
|
@@ -15704,7 +15840,68 @@ RAW: ${raw}
|
|
|
15704
15840
|
}
|
|
15705
15841
|
process.exit(0);
|
|
15706
15842
|
}
|
|
15707
|
-
|
|
15843
|
+
if (payload.hook_event_name === "UserPromptSubmit") {
|
|
15844
|
+
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
15845
|
+
if (process.env.NODE9_DEBUG === "1") {
|
|
15846
|
+
try {
|
|
15847
|
+
const logPath = import_path32.default.join(import_os27.default.homedir(), ".node9", "hook-debug.log");
|
|
15848
|
+
if (!import_fs31.default.existsSync(import_path32.default.dirname(logPath)))
|
|
15849
|
+
import_fs31.default.mkdirSync(import_path32.default.dirname(logPath), { recursive: true });
|
|
15850
|
+
const sanitized = JSON.stringify({
|
|
15851
|
+
...payload,
|
|
15852
|
+
prompt: `<redacted, ${prompt.length} bytes>`
|
|
15853
|
+
});
|
|
15854
|
+
import_fs31.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${sanitized}
|
|
15855
|
+
`);
|
|
15856
|
+
} catch {
|
|
15857
|
+
}
|
|
15858
|
+
}
|
|
15859
|
+
if (!prompt) process.exit(0);
|
|
15860
|
+
const dlpMatch = scanArgs({ prompt });
|
|
15861
|
+
if (!dlpMatch) process.exit(0);
|
|
15862
|
+
const agent2 = detectAiAgent(payload);
|
|
15863
|
+
const sessionId2 = typeof payload.session_id === "string" ? payload.session_id : void 0;
|
|
15864
|
+
appendLocalAudit(
|
|
15865
|
+
"UserPromptSubmit",
|
|
15866
|
+
{ prompt },
|
|
15867
|
+
"deny",
|
|
15868
|
+
"dlp-block",
|
|
15869
|
+
{ agent: agent2, sessionId: sessionId2 },
|
|
15870
|
+
true
|
|
15871
|
+
);
|
|
15872
|
+
const reason = `\u{1F6A8} Node9 DLP: ${dlpMatch.patternName} detected in prompt (${dlpMatch.redactedSample}). Prompt was not submitted \u2014 remove the credential and try again.`;
|
|
15873
|
+
try {
|
|
15874
|
+
const ttyFd = import_fs31.default.openSync("/dev/tty", "w");
|
|
15875
|
+
import_fs31.default.writeSync(
|
|
15876
|
+
ttyFd,
|
|
15877
|
+
import_chalk9.default.bgRed.white.bold(`
|
|
15878
|
+
\u{1F6A8} NODE9 DLP \u2014 PROMPT BLOCKED
|
|
15879
|
+
`) + import_chalk9.default.red(` ${dlpMatch.patternName} detected in your prompt.
|
|
15880
|
+
`) + import_chalk9.default.gray(` Match: ${dlpMatch.redactedSample}
|
|
15881
|
+
`) + import_chalk9.default.cyan(` Edit the prompt to remove the credential and resubmit.
|
|
15882
|
+
|
|
15883
|
+
`)
|
|
15884
|
+
);
|
|
15885
|
+
import_fs31.default.closeSync(ttyFd);
|
|
15886
|
+
} catch {
|
|
15887
|
+
}
|
|
15888
|
+
const isCodex = agent2 === "Codex";
|
|
15889
|
+
process.stdout.write(
|
|
15890
|
+
JSON.stringify({
|
|
15891
|
+
decision: "block",
|
|
15892
|
+
reason,
|
|
15893
|
+
systemMessage: reason,
|
|
15894
|
+
hookSpecificOutput: isCodex ? { hookEventName: "UserPromptSubmit" } : {
|
|
15895
|
+
hookEventName: "UserPromptSubmit",
|
|
15896
|
+
permissionDecision: "deny",
|
|
15897
|
+
permissionDecisionReason: reason
|
|
15898
|
+
}
|
|
15899
|
+
}) + "\n"
|
|
15900
|
+
);
|
|
15901
|
+
process.exit(2);
|
|
15902
|
+
}
|
|
15903
|
+
const safeCwdForConfig = typeof payload.cwd === "string" && import_path32.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
15904
|
+
const config = getConfig(safeCwdForConfig);
|
|
15708
15905
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
15709
15906
|
try {
|
|
15710
15907
|
const scriptPath = process.argv[1];
|
|
@@ -16088,7 +16285,7 @@ function registerLogCommand(program2) {
|
|
|
16088
16285
|
const payload = JSON.parse(raw);
|
|
16089
16286
|
const tool = sanitize3(payload.tool_name ?? payload.name ?? "unknown");
|
|
16090
16287
|
const rawInput = payload.tool_input ?? payload.args ?? {};
|
|
16091
|
-
const agent = payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : void 0;
|
|
16288
|
+
const agent = payload.turn_id !== void 0 ? "Codex" : payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : void 0;
|
|
16092
16289
|
const entry = {
|
|
16093
16290
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16094
16291
|
tool,
|
package/dist/cli.mjs
CHANGED
|
@@ -6341,7 +6341,7 @@ function teardownClaude() {
|
|
|
6341
6341
|
let changed = false;
|
|
6342
6342
|
const settings = readJson(hooksPath);
|
|
6343
6343
|
if (settings?.hooks) {
|
|
6344
|
-
for (const event of ["PreToolUse", "PostToolUse"]) {
|
|
6344
|
+
for (const event of ["PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
|
|
6345
6345
|
const before = settings.hooks[event]?.length ?? 0;
|
|
6346
6346
|
settings.hooks[event] = settings.hooks[event]?.filter(
|
|
6347
6347
|
(m) => !m.hooks.some((h) => isNode9Hook(h.command))
|
|
@@ -6522,6 +6522,33 @@ async function setupClaude() {
|
|
|
6522
6522
|
}
|
|
6523
6523
|
}
|
|
6524
6524
|
}
|
|
6525
|
+
const hasPromptHook = settings.hooks.UserPromptSubmit?.some(
|
|
6526
|
+
(m) => m.hooks.some((h) => isNode9Hook(h.command))
|
|
6527
|
+
);
|
|
6528
|
+
if (!hasPromptHook) {
|
|
6529
|
+
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
|
|
6530
|
+
settings.hooks.UserPromptSubmit.push({
|
|
6531
|
+
matcher: ".*",
|
|
6532
|
+
hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 600 }]
|
|
6533
|
+
});
|
|
6534
|
+
console.log(chalk.green(" \u2705 UserPromptSubmit hook added \u2192 node9 check (prompt DLP)"));
|
|
6535
|
+
hooksChanged = true;
|
|
6536
|
+
anythingChanged = true;
|
|
6537
|
+
} else if (settings.hooks.UserPromptSubmit) {
|
|
6538
|
+
for (const matcher of settings.hooks.UserPromptSubmit) {
|
|
6539
|
+
for (const h of matcher.hooks) {
|
|
6540
|
+
const cmd = h.command ?? "";
|
|
6541
|
+
if (isNode9Hook(cmd) && isStaleHookCommand(cmd)) {
|
|
6542
|
+
h.command = fullPathCommand("check");
|
|
6543
|
+
console.log(
|
|
6544
|
+
chalk.yellow(" \u{1F527} UserPromptSubmit hook repaired (stale path \u2192 current binary)")
|
|
6545
|
+
);
|
|
6546
|
+
hooksChanged = true;
|
|
6547
|
+
anythingChanged = true;
|
|
6548
|
+
}
|
|
6549
|
+
}
|
|
6550
|
+
}
|
|
6551
|
+
}
|
|
6525
6552
|
if (!hasNode9McpServer(servers)) {
|
|
6526
6553
|
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
6527
6554
|
claudeConfig.mcpServers = servers;
|
|
@@ -6808,9 +6835,79 @@ function writeToml(filePath, data) {
|
|
|
6808
6835
|
async function setupCodex() {
|
|
6809
6836
|
const homeDir2 = os11.homedir();
|
|
6810
6837
|
const configPath = path14.join(homeDir2, ".codex", "config.toml");
|
|
6838
|
+
const hooksPath = path14.join(homeDir2, ".codex", "hooks.json");
|
|
6811
6839
|
const config = readToml(configPath) ?? {};
|
|
6812
6840
|
const servers = config.mcp_servers ?? {};
|
|
6813
6841
|
let anythingChanged = false;
|
|
6842
|
+
const hooksFile = readJson(hooksPath) ?? {};
|
|
6843
|
+
if (!hooksFile.hooks) hooksFile.hooks = {};
|
|
6844
|
+
let hooksChanged = false;
|
|
6845
|
+
if (!hooksFile.hooks.PreToolUse) hooksFile.hooks.PreToolUse = [];
|
|
6846
|
+
for (const matcher of CODEX_PRE_TOOL_MATCHERS) {
|
|
6847
|
+
const existing = hooksFile.hooks.PreToolUse.find((m) => m.matcher === matcher);
|
|
6848
|
+
if (!existing) {
|
|
6849
|
+
hooksFile.hooks.PreToolUse.push({
|
|
6850
|
+
matcher,
|
|
6851
|
+
hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 600 }]
|
|
6852
|
+
});
|
|
6853
|
+
hooksChanged = true;
|
|
6854
|
+
} else {
|
|
6855
|
+
for (const h of existing.hooks) {
|
|
6856
|
+
const cmd = h.command ?? "";
|
|
6857
|
+
if (isNode9Hook(cmd) && isStaleHookCommand(cmd)) {
|
|
6858
|
+
h.command = fullPathCommand("check");
|
|
6859
|
+
hooksChanged = true;
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
6862
|
+
}
|
|
6863
|
+
}
|
|
6864
|
+
if (!hooksFile.hooks.UserPromptSubmit) hooksFile.hooks.UserPromptSubmit = [];
|
|
6865
|
+
const hasPromptHook = hooksFile.hooks.UserPromptSubmit.some(
|
|
6866
|
+
(m) => m.hooks.some((h) => isNode9Hook(h.command))
|
|
6867
|
+
);
|
|
6868
|
+
if (!hasPromptHook) {
|
|
6869
|
+
hooksFile.hooks.UserPromptSubmit.push({
|
|
6870
|
+
hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 600 }]
|
|
6871
|
+
});
|
|
6872
|
+
hooksChanged = true;
|
|
6873
|
+
} else {
|
|
6874
|
+
for (const m of hooksFile.hooks.UserPromptSubmit) {
|
|
6875
|
+
for (const h of m.hooks) {
|
|
6876
|
+
const cmd = h.command ?? "";
|
|
6877
|
+
if (isNode9Hook(cmd) && isStaleHookCommand(cmd)) {
|
|
6878
|
+
h.command = fullPathCommand("check");
|
|
6879
|
+
hooksChanged = true;
|
|
6880
|
+
}
|
|
6881
|
+
}
|
|
6882
|
+
}
|
|
6883
|
+
}
|
|
6884
|
+
if (!hooksFile.hooks.PostToolUse) hooksFile.hooks.PostToolUse = [];
|
|
6885
|
+
const hasPostHook = hooksFile.hooks.PostToolUse.some(
|
|
6886
|
+
(m) => m.hooks.some((h) => isNode9Hook(h.command))
|
|
6887
|
+
);
|
|
6888
|
+
if (!hasPostHook) {
|
|
6889
|
+
hooksFile.hooks.PostToolUse.push({
|
|
6890
|
+
matcher: ".*",
|
|
6891
|
+
hooks: [{ type: "command", command: fullPathCommand("log"), timeout: 600 }]
|
|
6892
|
+
});
|
|
6893
|
+
hooksChanged = true;
|
|
6894
|
+
} else {
|
|
6895
|
+
for (const m of hooksFile.hooks.PostToolUse) {
|
|
6896
|
+
for (const h of m.hooks) {
|
|
6897
|
+
const cmd = h.command ?? "";
|
|
6898
|
+
if (isNode9Hook(cmd) && isStaleHookCommand(cmd)) {
|
|
6899
|
+
h.command = fullPathCommand("log");
|
|
6900
|
+
hooksChanged = true;
|
|
6901
|
+
}
|
|
6902
|
+
}
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
if (hooksChanged) {
|
|
6906
|
+
writeJson(hooksPath, hooksFile);
|
|
6907
|
+
console.log(chalk.green(" \u2705 Codex hooks added \u2192 node9 check / node9 log"));
|
|
6908
|
+
anythingChanged = true;
|
|
6909
|
+
}
|
|
6910
|
+
const hooksInstalled = (hooksFile.hooks?.PreToolUse?.length ?? 0) > 0;
|
|
6814
6911
|
if (!hasNode9McpServer(servers)) {
|
|
6815
6912
|
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
6816
6913
|
config.mcp_servers = servers;
|
|
@@ -6850,23 +6947,39 @@ async function setupCodex() {
|
|
|
6850
6947
|
}
|
|
6851
6948
|
console.log("");
|
|
6852
6949
|
}
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
" \u26A0\uFE0F Note: Codex does not yet support native pre-execution hooks.\n MCP proxy wrapping is the only supported protection mode for Codex.\n Native bash and file operations are not monitored."
|
|
6856
|
-
)
|
|
6857
|
-
);
|
|
6858
|
-
console.log("");
|
|
6859
|
-
if (!anythingChanged && serversToWrap.length === 0) {
|
|
6950
|
+
const hooksDisabled = config.features?.hooks === false || config.codex_hooks === false;
|
|
6951
|
+
if (hooksDisabled) {
|
|
6860
6952
|
console.log(
|
|
6861
|
-
chalk.
|
|
6862
|
-
"\
|
|
6953
|
+
chalk.yellow(
|
|
6954
|
+
" \u26A0\uFE0F Codex hooks are disabled in ~/.codex/config.toml ([features].hooks = false).\n Re-enable hooks to activate Node9 shield evaluation on Bash, apply_patch,\n MCP tool calls, and prompt submissions. Until then, only MCP proxy wrapping\n is active."
|
|
6955
|
+
)
|
|
6956
|
+
);
|
|
6957
|
+
console.log("");
|
|
6958
|
+
}
|
|
6959
|
+
const printCodexTrustReminder = () => {
|
|
6960
|
+
console.log(
|
|
6961
|
+
chalk.yellow(
|
|
6962
|
+
" \u279C Open Codex and run /hooks to review and trust the Node9 entries.\n Until trusted, only MCP proxy wrapping is active."
|
|
6863
6963
|
)
|
|
6864
6964
|
);
|
|
6965
|
+
};
|
|
6966
|
+
if (!anythingChanged && serversToWrap.length === 0) {
|
|
6967
|
+
if (hooksInstalled) {
|
|
6968
|
+
console.log(chalk.blue("\u2139\uFE0F Codex hooks already installed."));
|
|
6969
|
+
printCodexTrustReminder();
|
|
6970
|
+
} else {
|
|
6971
|
+
console.log(
|
|
6972
|
+
chalk.blue(
|
|
6973
|
+
"\u2139\uFE0F No MCP servers found to wrap. Add MCP servers to ~/.codex/config.toml and re-run."
|
|
6974
|
+
)
|
|
6975
|
+
);
|
|
6976
|
+
}
|
|
6865
6977
|
printDaemonTip();
|
|
6866
6978
|
return;
|
|
6867
6979
|
}
|
|
6868
6980
|
if (anythingChanged) {
|
|
6869
|
-
console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9
|
|
6981
|
+
console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 hooks installed for Codex."));
|
|
6982
|
+
printCodexTrustReminder();
|
|
6870
6983
|
console.log(chalk.gray(" Restart Codex for changes to take effect."));
|
|
6871
6984
|
printDaemonTip();
|
|
6872
6985
|
}
|
|
@@ -6874,6 +6987,23 @@ async function setupCodex() {
|
|
|
6874
6987
|
function teardownCodex() {
|
|
6875
6988
|
const homeDir2 = os11.homedir();
|
|
6876
6989
|
const configPath = path14.join(homeDir2, ".codex", "config.toml");
|
|
6990
|
+
const hooksPath = path14.join(homeDir2, ".codex", "hooks.json");
|
|
6991
|
+
const hooksFile = readJson(hooksPath);
|
|
6992
|
+
if (hooksFile?.hooks) {
|
|
6993
|
+
let hooksChanged = false;
|
|
6994
|
+
for (const event of ["PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
|
|
6995
|
+
const before = hooksFile.hooks[event]?.length ?? 0;
|
|
6996
|
+
hooksFile.hooks[event] = hooksFile.hooks[event]?.filter(
|
|
6997
|
+
(m) => !m.hooks.some((h) => isNode9Hook(h.command))
|
|
6998
|
+
);
|
|
6999
|
+
if ((hooksFile.hooks[event]?.length ?? 0) < before) hooksChanged = true;
|
|
7000
|
+
if (hooksFile.hooks[event]?.length === 0) delete hooksFile.hooks[event];
|
|
7001
|
+
}
|
|
7002
|
+
if (hooksChanged) {
|
|
7003
|
+
writeJson(hooksPath, hooksFile);
|
|
7004
|
+
console.log(chalk.green(" \u2705 Removed Node9 hooks from ~/.codex/hooks.json"));
|
|
7005
|
+
}
|
|
7006
|
+
}
|
|
6877
7007
|
const config = readToml(configPath);
|
|
6878
7008
|
if (!config?.mcp_servers) {
|
|
6879
7009
|
console.log(chalk.blue(" \u2139\uFE0F ~/.codex/config.toml not found \u2014 nothing to remove"));
|
|
@@ -7332,11 +7462,12 @@ function getAgentsStatus(homeDir2 = os11.homedir()) {
|
|
|
7332
7462
|
}
|
|
7333
7463
|
];
|
|
7334
7464
|
}
|
|
7335
|
-
var NODE9_MCP_SERVER_ENTRY;
|
|
7465
|
+
var NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS;
|
|
7336
7466
|
var init_setup = __esm({
|
|
7337
7467
|
"src/setup.ts"() {
|
|
7338
7468
|
"use strict";
|
|
7339
7469
|
NODE9_MCP_SERVER_ENTRY = { command: "node9", args: ["mcp-server"] };
|
|
7470
|
+
CODEX_PRE_TOOL_MATCHERS = ["^Bash$", "^apply_patch$", "^mcp__.*"];
|
|
7340
7471
|
}
|
|
7341
7472
|
});
|
|
7342
7473
|
|
|
@@ -15628,10 +15759,15 @@ function resolveUserSkillRoot(entry, cwd) {
|
|
|
15628
15759
|
}
|
|
15629
15760
|
|
|
15630
15761
|
// src/cli/commands/check.ts
|
|
15762
|
+
init_dlp();
|
|
15763
|
+
init_audit();
|
|
15631
15764
|
function sanitize2(value) {
|
|
15632
15765
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
15633
15766
|
}
|
|
15634
15767
|
function detectAiAgent(payload) {
|
|
15768
|
+
if (payload.turn_id !== void 0) {
|
|
15769
|
+
return "Codex";
|
|
15770
|
+
}
|
|
15635
15771
|
if (payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0) {
|
|
15636
15772
|
return "Claude Code";
|
|
15637
15773
|
}
|
|
@@ -15677,7 +15813,68 @@ RAW: ${raw}
|
|
|
15677
15813
|
}
|
|
15678
15814
|
process.exit(0);
|
|
15679
15815
|
}
|
|
15680
|
-
|
|
15816
|
+
if (payload.hook_event_name === "UserPromptSubmit") {
|
|
15817
|
+
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
15818
|
+
if (process.env.NODE9_DEBUG === "1") {
|
|
15819
|
+
try {
|
|
15820
|
+
const logPath = path32.join(os27.homedir(), ".node9", "hook-debug.log");
|
|
15821
|
+
if (!fs31.existsSync(path32.dirname(logPath)))
|
|
15822
|
+
fs31.mkdirSync(path32.dirname(logPath), { recursive: true });
|
|
15823
|
+
const sanitized = JSON.stringify({
|
|
15824
|
+
...payload,
|
|
15825
|
+
prompt: `<redacted, ${prompt.length} bytes>`
|
|
15826
|
+
});
|
|
15827
|
+
fs31.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${sanitized}
|
|
15828
|
+
`);
|
|
15829
|
+
} catch {
|
|
15830
|
+
}
|
|
15831
|
+
}
|
|
15832
|
+
if (!prompt) process.exit(0);
|
|
15833
|
+
const dlpMatch = scanArgs({ prompt });
|
|
15834
|
+
if (!dlpMatch) process.exit(0);
|
|
15835
|
+
const agent2 = detectAiAgent(payload);
|
|
15836
|
+
const sessionId2 = typeof payload.session_id === "string" ? payload.session_id : void 0;
|
|
15837
|
+
appendLocalAudit(
|
|
15838
|
+
"UserPromptSubmit",
|
|
15839
|
+
{ prompt },
|
|
15840
|
+
"deny",
|
|
15841
|
+
"dlp-block",
|
|
15842
|
+
{ agent: agent2, sessionId: sessionId2 },
|
|
15843
|
+
true
|
|
15844
|
+
);
|
|
15845
|
+
const reason = `\u{1F6A8} Node9 DLP: ${dlpMatch.patternName} detected in prompt (${dlpMatch.redactedSample}). Prompt was not submitted \u2014 remove the credential and try again.`;
|
|
15846
|
+
try {
|
|
15847
|
+
const ttyFd = fs31.openSync("/dev/tty", "w");
|
|
15848
|
+
fs31.writeSync(
|
|
15849
|
+
ttyFd,
|
|
15850
|
+
chalk9.bgRed.white.bold(`
|
|
15851
|
+
\u{1F6A8} NODE9 DLP \u2014 PROMPT BLOCKED
|
|
15852
|
+
`) + chalk9.red(` ${dlpMatch.patternName} detected in your prompt.
|
|
15853
|
+
`) + chalk9.gray(` Match: ${dlpMatch.redactedSample}
|
|
15854
|
+
`) + chalk9.cyan(` Edit the prompt to remove the credential and resubmit.
|
|
15855
|
+
|
|
15856
|
+
`)
|
|
15857
|
+
);
|
|
15858
|
+
fs31.closeSync(ttyFd);
|
|
15859
|
+
} catch {
|
|
15860
|
+
}
|
|
15861
|
+
const isCodex = agent2 === "Codex";
|
|
15862
|
+
process.stdout.write(
|
|
15863
|
+
JSON.stringify({
|
|
15864
|
+
decision: "block",
|
|
15865
|
+
reason,
|
|
15866
|
+
systemMessage: reason,
|
|
15867
|
+
hookSpecificOutput: isCodex ? { hookEventName: "UserPromptSubmit" } : {
|
|
15868
|
+
hookEventName: "UserPromptSubmit",
|
|
15869
|
+
permissionDecision: "deny",
|
|
15870
|
+
permissionDecisionReason: reason
|
|
15871
|
+
}
|
|
15872
|
+
}) + "\n"
|
|
15873
|
+
);
|
|
15874
|
+
process.exit(2);
|
|
15875
|
+
}
|
|
15876
|
+
const safeCwdForConfig = typeof payload.cwd === "string" && path32.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
15877
|
+
const config = getConfig(safeCwdForConfig);
|
|
15681
15878
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
15682
15879
|
try {
|
|
15683
15880
|
const scriptPath = process.argv[1];
|
|
@@ -16061,7 +16258,7 @@ function registerLogCommand(program2) {
|
|
|
16061
16258
|
const payload = JSON.parse(raw);
|
|
16062
16259
|
const tool = sanitize3(payload.tool_name ?? payload.name ?? "unknown");
|
|
16063
16260
|
const rawInput = payload.tool_input ?? payload.args ?? {};
|
|
16064
|
-
const agent = payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : void 0;
|
|
16261
|
+
const agent = payload.turn_id !== void 0 ? "Codex" : payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : void 0;
|
|
16065
16262
|
const entry = {
|
|
16066
16263
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16067
16264
|
tool,
|