fullcourtdefense-cli 1.34.1 → 1.34.2

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.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Name dictionary, not an effective configuration or an authorization source.
3
+ * Include user-owned profiles conservatively: hook payloads do not identify the
4
+ * selected profile. Aliases shared by different names are denied, never guessed.
5
+ * Never consult project files unless the user configuration trusts their root.
6
+ */
7
+ export declare function codexMcpServerNames(home?: string, cwd?: string): string[];
8
+ /** Codex exposes sanitized tool namespaces. Resolve only unique configured identities. */
9
+ export declare function resolveCodexMcpTool(tool: string, serverNames: string[]): {
10
+ server: string;
11
+ tool: string;
12
+ } | undefined;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.codexMcpServerNames = codexMcpServerNames;
37
+ exports.resolveCodexMcpTool = resolveCodexMcpTool;
38
+ const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ const toml_1 = require("@iarna/toml");
42
+ /** Parse config without executing commands or exposing its contents in errors. */
43
+ function readConfig(file) {
44
+ try {
45
+ return (0, toml_1.parse)(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
46
+ }
47
+ catch (err) {
48
+ if (err.code === 'ENOENT')
49
+ return {};
50
+ throw new Error('Cannot parse Codex configuration to resolve MCP server identity.');
51
+ }
52
+ }
53
+ /**
54
+ * Name dictionary, not an effective configuration or an authorization source.
55
+ * Include user-owned profiles conservatively: hook payloads do not identify the
56
+ * selected profile. Aliases shared by different names are denied, never guessed.
57
+ * Never consult project files unless the user configuration trusts their root.
58
+ */
59
+ function codexMcpServerNames(home = process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), cwd = process.cwd()) {
60
+ const base = readConfig(path.join(home, 'config.toml'));
61
+ const configs = [base];
62
+ const system = process.platform === 'win32'
63
+ ? path.join(process.env.ProgramData || 'C:\\ProgramData', 'OpenAI', 'Codex', 'config.toml')
64
+ : '/etc/codex/config.toml';
65
+ configs.push(readConfig(system));
66
+ let profiles = [];
67
+ try {
68
+ profiles = fs.readdirSync(home).filter(name => /^[A-Za-z0-9_-]+\.config\.toml$/.test(name));
69
+ }
70
+ catch (err) {
71
+ if (err.code !== 'ENOENT')
72
+ throw err;
73
+ }
74
+ for (const file of profiles) {
75
+ // An invalid, unselected profile is not active in Codex and cannot supply names.
76
+ // Selecting an invalid profile fails Codex startup itself.
77
+ try {
78
+ configs.push(readConfig(path.join(home, file)));
79
+ }
80
+ catch { /* inactive invalid profile */ }
81
+ }
82
+ const within = (root, dir) => {
83
+ const rel = path.relative(root, dir);
84
+ return rel === '' || (rel !== '..' && !rel.startsWith('..' + path.sep) && !path.isAbsolute(rel));
85
+ };
86
+ const roots = Object.keys(base.projects || {}).filter(root => path.isAbsolute(root) && within(root, path.resolve(cwd)))
87
+ .sort((a, b) => b.length - a.length);
88
+ const trustedRoot = roots[0];
89
+ if (trustedRoot && base.projects[trustedRoot]?.trust_level === 'trusted') {
90
+ for (let dir = path.resolve(cwd); within(trustedRoot, dir); dir = path.dirname(dir)) {
91
+ configs.push(readConfig(path.join(dir, '.codex', 'config.toml')));
92
+ if (path.dirname(dir) === dir)
93
+ break;
94
+ }
95
+ }
96
+ const names = new Set();
97
+ for (const config of configs) {
98
+ if (config.mcp_servers && typeof config.mcp_servers === 'object' && !Array.isArray(config.mcp_servers)) {
99
+ for (const name of Object.keys(config.mcp_servers))
100
+ names.add(name);
101
+ }
102
+ }
103
+ return [...names];
104
+ }
105
+ /** Codex exposes sanitized tool namespaces. Resolve only unique configured identities. */
106
+ function resolveCodexMcpTool(tool, serverNames) {
107
+ if (!tool.startsWith('mcp__'))
108
+ return undefined;
109
+ const matches = [...new Set(serverNames)].flatMap(server => {
110
+ const aliases = new Set([server, server.replace(/[^A-Za-z0-9_]/g, '_')]);
111
+ return [...aliases].filter(alias => tool.startsWith(`mcp__${alias}__`))
112
+ .map(alias => ({ server, tool: tool.slice(`mcp__${alias}__`.length) }));
113
+ });
114
+ if (matches.length > 1)
115
+ throw new Error('Ambiguous Codex MCP server identity; use distinct server names.');
116
+ if (matches.length && !matches[0].tool)
117
+ throw new Error('Missing Codex MCP tool name.');
118
+ return matches[0];
119
+ }
@@ -161,8 +161,11 @@ function mapClaudeToolPayload(payload) {
161
161
  // MCP tools arrive as `mcp__<server>__<tool>` (same in Claude Code and VS Code).
162
162
  if (toolName.startsWith('mcp__')) {
163
163
  const parts = toolName.split('__');
164
- const server = parts[1] || 'mcp';
165
- const shortTool = parts.slice(2).join('__') || toolName;
164
+ // A verified explicit server preserves names containing the separator itself.
165
+ const explicitServer = str(payload.mcp_server_name);
166
+ const server = explicitServer && toolName.startsWith(`mcp__${explicitServer}__`)
167
+ ? explicitServer : parts[1] || 'mcp';
168
+ const shortTool = toolName.slice(`mcp__${server}__`.length) || toolName;
166
169
  return {
167
170
  event: 'mcp',
168
171
  payload: { ...payload, tool_name: shortTool, mcp_server_name: server, tool_input: input },
@@ -789,6 +792,7 @@ function tryLocalPolicyEnforcement(ctx, call, detail, opts = {}) {
789
792
  }
790
793
  const { operation, context } = (0, actionPolicyEngine_1.inferToolContext)(call.toolName, call.toolArgs);
791
794
  context.toolName = call.toolName;
795
+ context.agentName = agentNameForCall(ctx.event, ctx.payload, ctx.client);
792
796
  (0, machineIdentity_1.stampMachineIdentityContext)(context, developerId());
793
797
  const declaredOperations = (0, actionPolicyEngine_1.deriveToolOperations)(call.toolName);
794
798
  const local = (0, actionPolicyEngine_1.evaluateActionPolicies)(policies, call.toolName, operation, context, declaredOperations);
@@ -1800,6 +1804,7 @@ async function enforceActionPolicy(ctx) {
1800
1804
  if (ctx.localPolicies && (ctx.localPolicies.length > 0 || localAskPending)) {
1801
1805
  const { operation: localOp, context: localCtx } = (0, actionPolicyEngine_1.inferToolContext)(call.toolName, call.toolArgs);
1802
1806
  localCtx.toolName = call.toolName;
1807
+ localCtx.agentName = agentNameForCall(event, payload, ctx.client);
1803
1808
  (0, machineIdentity_1.stampMachineIdentityContext)(localCtx, developerId());
1804
1809
  const localVerdict = (0, actionPolicyEngine_1.evaluateActionPolicies)(ctx.localPolicies, call.toolName, localOp, localCtx, (0, actionPolicyEngine_1.deriveToolOperations)(call.toolName));
1805
1810
  (0, hookIo_1.dbg)({ phase: 'policy_local_first', event, tool: call.toolName, operation: localOp, verdict: localVerdict.verdict, policyName: localVerdict.policyName });
@@ -4,7 +4,7 @@ export type NativeClient = 'codex' | 'gemini_cli' | 'windsurf' | 'kiro';
4
4
  type Payload = Record<string, any>;
5
5
  export declare const NATIVE_CLIENTS: NativeClient[];
6
6
  export declare function patchToolCalls(raw: Payload): Payload[];
7
- export declare function normalizeNativeInput(client: NativeClient, raw: Payload): Payload;
7
+ export declare function normalizeNativeInput(client: NativeClient, raw: Payload, codexServers?: string[]): Payload;
8
8
  export declare function nativeVerdict(client: NativeClient, denied: boolean, reason?: string): {
9
9
  stdout: string;
10
10
  stderr: string;
@@ -15,6 +15,7 @@ const runtimeConfig_1 = require("./runtimeConfig");
15
15
  const hookIo_1 = require("./hookIo");
16
16
  const devConfirm_1 = require("./devConfirm");
17
17
  const telemetry_1 = require("./telemetry");
18
+ const codexMcpIdentity_1 = require("./codexMcpIdentity");
18
19
  exports.NATIVE_CLIENTS = ['codex', 'gemini_cli', 'windsurf', 'kiro'];
19
20
  function patchToolCalls(raw) {
20
21
  const patch = typeof raw.tool_input === 'string' ? raw.tool_input : String(raw.tool_input?.command || raw.tool_input?.patch || '');
@@ -37,10 +38,17 @@ function patchToolCalls(raw) {
37
38
  throw new Error('Patch contains no recognized file operations');
38
39
  return calls;
39
40
  }
40
- function normalizeNativeInput(client, raw) {
41
+ function normalizeNativeInput(client, raw, codexServers = []) {
41
42
  let name = String(raw.hook_event_name || '');
42
43
  let tool = String(raw.tool_name || '');
43
44
  let input = raw.tool_input || {};
45
+ if (client === 'codex') {
46
+ const identity = (0, codexMcpIdentity_1.resolveCodexMcpTool)(tool, codexServers);
47
+ if (identity) {
48
+ tool = `mcp__${identity.server}__${identity.tool}`;
49
+ raw = { ...raw, mcp_server_name: identity.server };
50
+ }
51
+ }
44
52
  if (client === 'windsurf') {
45
53
  const event = String(raw.agent_action_name || '');
46
54
  const info = raw.tool_info || {};
@@ -158,7 +166,7 @@ async function evaluateNativeHook(client, raw, args, config, confirm = confirmNa
158
166
  }
159
167
  let payload;
160
168
  try {
161
- payload = normalizeNativeInput(client, raw);
169
+ payload = normalizeNativeInput(client, raw, client === 'codex' && String(raw.tool_name || '').startsWith('mcp__') ? (0, codexMcpIdentity_1.codexMcpServerNames)() : []);
162
170
  }
163
171
  catch (error) {
164
172
  return nativeErrorVerdict(client, `FullCourtDefense could not parse the hook: ${String(error)}`);
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.34.1"
2
+ "version": "1.34.2"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.34.1",
3
+ "version": "1.34.2",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -118,6 +118,7 @@
118
118
  "node": ">=18.0.0"
119
119
  },
120
120
  "dependencies": {
121
+ "@iarna/toml": "^2.2.5",
121
122
  "@napi-rs/keyring": "^1.3.0",
122
123
  "yaml": "^2.8.4"
123
124
  }