multicorn-shield 1.8.0 → 1.9.1

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.
@@ -22360,6 +22360,12 @@ var INIT_WIZARD_PLATFORM_REGISTRY = [
22360
22360
  section: "native",
22361
22361
  prereqUrl: "https://opencode.ai"
22362
22362
  },
22363
+ {
22364
+ slug: "codex-cli",
22365
+ displayName: "Codex CLI",
22366
+ section: "native",
22367
+ prereqUrl: "https://github.com/openai/codex"
22368
+ },
22363
22369
  {
22364
22370
  slug: "cursor",
22365
22371
  displayName: "Cursor",
@@ -22511,7 +22517,7 @@ async function writeExtensionBackup(claudeDesktopConfigPath, mcpServers) {
22511
22517
 
22512
22518
  // package.json
22513
22519
  var package_default = {
22514
- version: "1.8.0"};
22520
+ version: "1.9.1"};
22515
22521
 
22516
22522
  // src/package-meta.ts
22517
22523
  var PACKAGE_VERSION = package_default.version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multicorn-shield",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "description": "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
5
5
  "license": "MIT",
6
6
  "author": "Multicorn AI Pty Ltd",
@@ -41,6 +41,7 @@
41
41
  "plugins/cline",
42
42
  "plugins/gemini-cli",
43
43
  "plugins/opencode",
44
+ "plugins/codex-cli",
44
45
  "LICENSE",
45
46
  "README.md",
46
47
  "CHANGELOG.md"
@@ -0,0 +1,41 @@
1
+ # Codex CLI hook scripts for Multicorn Shield
2
+
3
+ These files support **Codex CLI** native hooks (`PreToolUse` / `PostToolUse`): permission checks before tools run and logging afterward.
4
+
5
+ ## Generated outputs
6
+
7
+ The runnable scripts under `hooks/scripts/` are **built from TypeScript** in `src/hooks/`:
8
+
9
+ | Output (`hooks/scripts/`) | Source |
10
+ | ---------------------------- | -------------------------------------- |
11
+ | `pre-tool-use.cjs` | `src/hooks/codex-cli-pre-tool-use.ts` |
12
+ | `post-tool-use.cjs` | `src/hooks/codex-cli-post-tool-use.ts` |
13
+ | `codex-cli-hooks-shared.cjs` | `src/hooks/codex-cli-hooks-shared.ts` |
14
+ | `codex-cli-tool-map.cjs` | `src/hooks/codex-cli-tool-map.ts` |
15
+
16
+ Do **not** edit the `.cjs` files by hand. Change the `.ts` sources and rebuild.
17
+
18
+ ## Build
19
+
20
+ From the **multicorn-shield** package root:
21
+
22
+ ```bash
23
+ pnpm build
24
+ ```
25
+
26
+ That runs `tsup`, which emits the Codex CLI hook bundle into `plugins/codex-cli/hooks/scripts/`.
27
+
28
+ ## Manual testing
29
+
30
+ Hooks read **one JSON object from stdin** (Codex passes the hook payload). Examples:
31
+
32
+ ```bash
33
+ echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | node plugins/codex-cli/hooks/scripts/pre-tool-use.cjs
34
+ echo '{"tool_name":"Bash","tool_input":{"command":"ls"},"tool_result":"ok"}' | node plugins/codex-cli/hooks/scripts/post-tool-use.cjs
35
+ ```
36
+
37
+ Use a valid `~/.multicorn/config.json` with `apiKey`, `baseUrl`, and agent entries when exercising the Shield API paths.
38
+
39
+ ## Main documentation
40
+
41
+ See the [multicorn-shield README](../../README.md) for installation, configuration, and Shield concepts.
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+
3
+ var fs = require("fs");
4
+ var http = require("http");
5
+ var https = require("https");
6
+ var os = require("os");
7
+ var path = require("path");
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== "default") {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(
17
+ n,
18
+ k,
19
+ d.get
20
+ ? d
21
+ : {
22
+ enumerable: true,
23
+ get: function () {
24
+ return e[k];
25
+ },
26
+ },
27
+ );
28
+ }
29
+ });
30
+ }
31
+ n.default = e;
32
+ return Object.freeze(n);
33
+ }
34
+
35
+ var fs__namespace = /*#__PURE__*/ _interopNamespace(fs);
36
+ var http__namespace = /*#__PURE__*/ _interopNamespace(http);
37
+ var https__namespace = /*#__PURE__*/ _interopNamespace(https);
38
+ var os__namespace = /*#__PURE__*/ _interopNamespace(os);
39
+ var path__namespace = /*#__PURE__*/ _interopNamespace(path);
40
+
41
+ // AUTO-GENERATED from src/hooks/codex-cli-*.ts — do not edit manually. Run pnpm build from the package root to regenerate.
42
+
43
+ var AUTH_HEADER = "X-Multicorn-Key";
44
+ var AUDIT_METADATA_MAX_CHARS = 1e4;
45
+ function redactSecretsForAudit(serialized) {
46
+ let out = serialized;
47
+ out = out.replace(/\bsk-[a-zA-Z0-9]{20,}\b/g, "[REDACTED]");
48
+ out = out.replace(/\bAKIA[0-9A-Z]{16}\b/g, "[REDACTED]");
49
+ out = out.replace(/\bghp_[a-zA-Z0-9]{20,}\b/g, "[REDACTED]");
50
+ out = out.replace(/\bgho_[a-zA-Z0-9]{20,}\b/g, "[REDACTED]");
51
+ out = out.replace(/\bghu_[a-zA-Z0-9]{20,}\b/g, "[REDACTED]");
52
+ out = out.replace(/\bghs_[a-zA-Z0-9]{20,}\b/g, "[REDACTED]");
53
+ out = out.replace(
54
+ /-----BEGIN[A-Z0-9 \n\r-]+-----[\s\S]*?-----END[A-Z0-9 \n\r-]+-----/g,
55
+ "[REDACTED]",
56
+ );
57
+ out = out.replace(/token=[^\s"&]+/gi, "token=[REDACTED]");
58
+ out = out.replace(/\bBearer\s+[a-zA-Z0-9._\-+/=]+\b/gi, "Bearer [REDACTED]");
59
+ return out;
60
+ }
61
+ function truncateForAudit(serialized, maxChars = AUDIT_METADATA_MAX_CHARS) {
62
+ if (serialized.length <= maxChars) return serialized;
63
+ return `${serialized.slice(0, maxChars)}[truncated]`;
64
+ }
65
+ function serializeHookAuditFragment(value) {
66
+ try {
67
+ const raw = typeof value === "string" ? value : JSON.stringify(value === void 0 ? null : value);
68
+ return truncateForAudit(redactSecretsForAudit(raw));
69
+ } catch {
70
+ return "[unserializable]";
71
+ }
72
+ }
73
+ function isLocalHostname(hostname) {
74
+ const h = hostname.toLowerCase();
75
+ return h === "localhost" || h === "127.0.0.1" || h === "::1";
76
+ }
77
+ function assertHttpsOrLocalhostForApiKey(u) {
78
+ if (u.protocol === "http:" && !isLocalHostname(u.hostname)) {
79
+ throw new Error(`HTTP_API_KEY_REFUSED:${u.hostname}`);
80
+ }
81
+ }
82
+ function cwdUnderWorkspacePath(cwdResolved, workspacePath) {
83
+ const w = path__namespace.resolve(workspacePath);
84
+ if (cwdResolved === w) return true;
85
+ const prefix = w.endsWith(path__namespace.sep) ? w : w + path__namespace.sep;
86
+ return cwdResolved.startsWith(prefix);
87
+ }
88
+ function resolveCodexCliAgentName(obj) {
89
+ const pwd = process.env["PWD"];
90
+ const cwdRaw = pwd !== void 0 && pwd.length > 0 ? pwd : process.cwd();
91
+ const agents = obj["agents"];
92
+ const defaultAgentRaw = obj["defaultAgent"];
93
+ const defaultAgentName =
94
+ typeof defaultAgentRaw === "string" && defaultAgentRaw.length > 0 ? defaultAgentRaw : "";
95
+ if (!Array.isArray(agents)) {
96
+ return typeof obj["agentName"] === "string" ? obj["agentName"] : "";
97
+ }
98
+ const matches = [];
99
+ for (const entry of agents) {
100
+ if (entry === null || typeof entry !== "object") continue;
101
+ const e = entry;
102
+ if (e["platform"] !== "codex-cli") continue;
103
+ const n = e["name"];
104
+ if (typeof n !== "string") continue;
105
+ const wp = e["workspacePath"];
106
+ matches.push({
107
+ name: n,
108
+ ...(typeof wp === "string" && wp.length > 0 ? { workspacePath: wp } : {}),
109
+ });
110
+ }
111
+ if (matches.length === 0) {
112
+ return typeof obj["agentName"] === "string" ? obj["agentName"] : "";
113
+ }
114
+ const withWs = matches.filter(
115
+ (m) => typeof m.workspacePath === "string" && m.workspacePath.length > 0,
116
+ );
117
+ const resolvedCwd = path__namespace.resolve(cwdRaw);
118
+ let best = null;
119
+ let bestLen = -1;
120
+ for (const m of withWs) {
121
+ const wp = m.workspacePath;
122
+ if (!cwdUnderWorkspacePath(resolvedCwd, wp)) continue;
123
+ const len = path__namespace.resolve(wp).length;
124
+ if (len > bestLen) {
125
+ bestLen = len;
126
+ best = { name: m.name, workspacePath: wp };
127
+ }
128
+ }
129
+ if (best !== null) return best.name;
130
+ if (defaultAgentName.length > 0) {
131
+ const d = matches.find((m) => m.name === defaultAgentName);
132
+ if (d !== void 0) return d.name;
133
+ }
134
+ const first = matches[0];
135
+ return first !== void 0 ? first.name : "";
136
+ }
137
+ function warnIfConfigWorldReadable(configPath) {
138
+ try {
139
+ const st = fs__namespace.statSync(configPath);
140
+ const mode777 = st.mode & 511;
141
+ if ((st.mode & 63) !== 0) {
142
+ process.stderr.write(
143
+ `[Shield] Warning: ~/.multicorn/config.json is readable by other users (current: 0${mode777.toString(8)}). Run: chmod 600 ~/.multicorn/config.json
144
+ `,
145
+ );
146
+ }
147
+ } catch {}
148
+ }
149
+ function loadCodexCliConfig() {
150
+ try {
151
+ const configPath = path__namespace.join(os__namespace.homedir(), ".multicorn", "config.json");
152
+ const raw = fs__namespace.readFileSync(configPath, "utf8");
153
+ warnIfConfigWorldReadable(configPath);
154
+ const obj = JSON.parse(raw);
155
+ const apiKey = typeof obj["apiKey"] === "string" ? obj["apiKey"] : "";
156
+ const baseUrl =
157
+ typeof obj["baseUrl"] === "string" && obj["baseUrl"].length > 0
158
+ ? obj["baseUrl"].replace(/\/+$/, "")
159
+ : "https://api.multicorn.ai";
160
+ const agentName = resolveCodexCliAgentName(obj);
161
+ return { apiKey, baseUrl, agentName };
162
+ } catch {
163
+ return null;
164
+ }
165
+ }
166
+ function formatShieldNetworkError(err) {
167
+ const debugEnv = process.env["MULTICORN_DEBUG"];
168
+ const debug = debugEnv === "1" || debugEnv === "true" || debugEnv === "yes";
169
+ let line =
170
+ "[Shield] Error: failed to connect to Shield API. Check your network and baseUrl configuration.\n";
171
+ if (debug && err instanceof Error && err.message.length > 0) {
172
+ line += ` Debug: ${err.message}
173
+ `;
174
+ }
175
+ return line;
176
+ }
177
+ function formatHttpApiKeyRefusal(hostname) {
178
+ return `[Shield] Error: refusing to send API key over unencrypted HTTP to ${hostname}. Use HTTPS or localhost.
179
+ `;
180
+ }
181
+ function readHttpApiKeyRefusalHostname(err) {
182
+ if (!(err instanceof Error) || !err.message.startsWith("HTTP_API_KEY_REFUSED:")) {
183
+ return null;
184
+ }
185
+ return err.message.slice("HTTP_API_KEY_REFUSED:".length);
186
+ }
187
+ async function shieldGetJson(baseUrl, apiKey, reqPath) {
188
+ const root = baseUrl.replace(/\/+$/, "");
189
+ const p = reqPath.startsWith("/") ? reqPath : `/${reqPath}`;
190
+ const u = new URL(`${root}${p}`);
191
+ assertHttpsOrLocalhostForApiKey(u);
192
+ const isHttps = u.protocol === "https:";
193
+ const lib = isHttps ? https__namespace : http__namespace;
194
+ const port = u.port !== "" ? Number(u.port) : isHttps ? 443 : 80;
195
+ return await new Promise((resolve2, reject) => {
196
+ const options = {
197
+ hostname: u.hostname,
198
+ port,
199
+ path: u.pathname + u.search,
200
+ method: "GET",
201
+ headers: {
202
+ [AUTH_HEADER]: apiKey,
203
+ },
204
+ };
205
+ const req = lib.request(options, (res) => {
206
+ const chunks = [];
207
+ res.on("data", (c) => chunks.push(c));
208
+ res.on("end", () => {
209
+ resolve2({
210
+ statusCode: res.statusCode ?? 0,
211
+ bodyText: Buffer.concat(chunks).toString("utf8"),
212
+ });
213
+ });
214
+ });
215
+ req.on("error", reject);
216
+ req.end();
217
+ });
218
+ }
219
+ async function shieldPostJson(baseUrl, apiKey, bodyObj) {
220
+ const root = baseUrl.replace(/\/+$/, "");
221
+ const u = new URL(`${root}/api/v1/actions`);
222
+ assertHttpsOrLocalhostForApiKey(u);
223
+ const payload = JSON.stringify(bodyObj);
224
+ const isHttps = u.protocol === "https:";
225
+ const lib = isHttps ? https__namespace : http__namespace;
226
+ const port = u.port !== "" ? Number(u.port) : isHttps ? 443 : 80;
227
+ return await new Promise((resolve2, reject) => {
228
+ const options = {
229
+ hostname: u.hostname,
230
+ port,
231
+ path: u.pathname + u.search,
232
+ method: "POST",
233
+ headers: {
234
+ "Content-Type": "application/json",
235
+ "Content-Length": Buffer.byteLength(payload, "utf8"),
236
+ [AUTH_HEADER]: apiKey,
237
+ },
238
+ };
239
+ const req = lib.request(options, (res) => {
240
+ const chunks = [];
241
+ res.on("data", (c) => chunks.push(c));
242
+ res.on("end", () => {
243
+ resolve2({
244
+ statusCode: res.statusCode ?? 0,
245
+ bodyText: Buffer.concat(chunks).toString("utf8"),
246
+ });
247
+ });
248
+ });
249
+ req.on("error", reject);
250
+ req.write(payload);
251
+ req.end();
252
+ });
253
+ }
254
+ async function shieldPostJsonFireAndForget(baseUrl, apiKey, bodyObj) {
255
+ await shieldPostJson(baseUrl, apiKey, bodyObj);
256
+ }
257
+
258
+ exports.AUTH_HEADER = AUTH_HEADER;
259
+ exports.assertHttpsOrLocalhostForApiKey = assertHttpsOrLocalhostForApiKey;
260
+ exports.cwdUnderWorkspacePath = cwdUnderWorkspacePath;
261
+ exports.formatHttpApiKeyRefusal = formatHttpApiKeyRefusal;
262
+ exports.formatShieldNetworkError = formatShieldNetworkError;
263
+ exports.isLocalHostname = isLocalHostname;
264
+ exports.loadCodexCliConfig = loadCodexCliConfig;
265
+ exports.readHttpApiKeyRefusalHostname = readHttpApiKeyRefusalHostname;
266
+ exports.redactSecretsForAudit = redactSecretsForAudit;
267
+ exports.resolveCodexCliAgentName = resolveCodexCliAgentName;
268
+ exports.serializeHookAuditFragment = serializeHookAuditFragment;
269
+ exports.shieldGetJson = shieldGetJson;
270
+ exports.shieldPostJson = shieldPostJson;
271
+ exports.shieldPostJsonFireAndForget = shieldPostJsonFireAndForget;
272
+ exports.truncateForAudit = truncateForAudit;
273
+ exports.warnIfConfigWorldReadable = warnIfConfigWorldReadable;
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+
3
+ // AUTO-GENERATED from src/hooks/codex-cli-*.ts — do not edit manually. Run pnpm build from the package root to regenerate.
4
+
5
+ // src/openclaw/tool-mapper.ts
6
+ var TOOL_MAP = {
7
+ // OpenClaw built-in tools
8
+ read: { service: "filesystem", permissionLevel: "read" },
9
+ write: { service: "filesystem", permissionLevel: "write" },
10
+ edit: { service: "filesystem", permissionLevel: "write" },
11
+ exec: { service: "terminal", permissionLevel: "execute" },
12
+ browser: { service: "browser", permissionLevel: "execute" },
13
+ message: { service: "messaging", permissionLevel: "write" },
14
+ process: { service: "terminal", permissionLevel: "execute" },
15
+ sessions_spawn: { service: "agents", permissionLevel: "execute" },
16
+ // Common integration tools (MCP servers, skills, etc.)
17
+ // Gmail
18
+ gmail: { service: "gmail", permissionLevel: "execute" },
19
+ gmail_send: { service: "gmail", permissionLevel: "write" },
20
+ gmail_read: { service: "gmail", permissionLevel: "read" },
21
+ // Google Calendar
22
+ google_calendar: { service: "google_calendar", permissionLevel: "execute" },
23
+ calendar: { service: "google_calendar", permissionLevel: "execute" },
24
+ calendar_create: { service: "google_calendar", permissionLevel: "write" },
25
+ calendar_read: { service: "google_calendar", permissionLevel: "read" },
26
+ // Google Drive
27
+ google_drive: { service: "google_drive", permissionLevel: "execute" },
28
+ drive: { service: "google_drive", permissionLevel: "execute" },
29
+ drive_read: { service: "google_drive", permissionLevel: "read" },
30
+ drive_write: { service: "google_drive", permissionLevel: "write" },
31
+ // Slack
32
+ slack: { service: "slack", permissionLevel: "execute" },
33
+ slack_send: { service: "slack", permissionLevel: "write" },
34
+ slack_read: { service: "slack", permissionLevel: "read" },
35
+ slack_message: { service: "slack", permissionLevel: "write" },
36
+ // Payments
37
+ payments: { service: "payments", permissionLevel: "write" },
38
+ payment: { service: "payments", permissionLevel: "write" },
39
+ stripe: { service: "payments", permissionLevel: "write" },
40
+ };
41
+ function mapToolToScope(toolName, command) {
42
+ const normalized = toolName.trim().toLowerCase();
43
+ if (normalized.length === 0) {
44
+ return { service: "unknown", permissionLevel: "execute" };
45
+ }
46
+ const known = TOOL_MAP[normalized];
47
+ if (known !== void 0) {
48
+ return known;
49
+ }
50
+ const integrationPrefixes = {
51
+ gmail: "gmail",
52
+ google_calendar: "google_calendar",
53
+ calendar: "google_calendar",
54
+ google_drive: "google_drive",
55
+ drive: "google_drive",
56
+ slack: "slack",
57
+ payments: "payments",
58
+ payment: "payments",
59
+ stripe: "payments",
60
+ };
61
+ for (const [prefix, service] of Object.entries(integrationPrefixes)) {
62
+ if (normalized.startsWith(prefix + "_") || normalized === prefix) {
63
+ let permissionLevel = "execute";
64
+ if (
65
+ normalized.includes("_read") ||
66
+ normalized.includes("_get") ||
67
+ normalized.includes("_list")
68
+ ) {
69
+ permissionLevel = "read";
70
+ } else if (
71
+ normalized.includes("_write") ||
72
+ normalized.includes("_send") ||
73
+ normalized.includes("_create") ||
74
+ normalized.includes("_update") ||
75
+ normalized.includes("_delete")
76
+ ) {
77
+ permissionLevel = "write";
78
+ }
79
+ return { service, permissionLevel };
80
+ }
81
+ }
82
+ return { service: normalized, permissionLevel: "execute" };
83
+ }
84
+ function isKnownTool(toolName) {
85
+ return Object.hasOwn(TOOL_MAP, toolName.trim().toLowerCase());
86
+ }
87
+
88
+ // src/hooks/codex-cli-tool-map.ts
89
+ var CODEX_DESTRUCTIVE_KEYWORDS = ["rm", "mv", "sudo", "chmod", "chown", "dd", "truncate", "shred"];
90
+ function escapeRegExp(s) {
91
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
92
+ }
93
+ function destructiveTokenRegex(keyword) {
94
+ const e = escapeRegExp(keyword);
95
+ return new RegExp(`(^|[^a-zA-Z0-9_])${e}(?![a-zA-Z0-9_-])`, "i");
96
+ }
97
+ function codexIsDestructiveExecCommand(command) {
98
+ const trimmed = command.trim();
99
+ if (/^\s*echo\b/i.test(trimmed) && !/[;&|]/.test(trimmed)) {
100
+ return false;
101
+ }
102
+ const withoutQuotes = trimmed.replace(/'[^']*'/g, " ").replace(/"([^"\\]|\\.)*"/g, " ");
103
+ const normalized = withoutQuotes.toLowerCase();
104
+ return CODEX_DESTRUCTIVE_KEYWORDS.some((kw) => destructiveTokenRegex(kw).test(normalized));
105
+ }
106
+ function extractExecCommand(toolInput) {
107
+ if (toolInput === void 0 || toolInput === null) {
108
+ return void 0;
109
+ }
110
+ if (typeof toolInput === "object") {
111
+ const o = toolInput;
112
+ const c = o["command"];
113
+ if (typeof c === "string") {
114
+ return c;
115
+ }
116
+ }
117
+ if (typeof toolInput === "string") {
118
+ try {
119
+ return extractExecCommand(JSON.parse(toolInput));
120
+ } catch {
121
+ return toolInput;
122
+ }
123
+ }
124
+ return void 0;
125
+ }
126
+ function mapCodexCliToolToShield(toolName, toolInput) {
127
+ const n = toolName.trim().toLowerCase();
128
+ if (n.length === 0) {
129
+ return { service: "unknown", actionType: "execute" };
130
+ }
131
+ if (n === "bash") {
132
+ const cmd = extractExecCommand(toolInput);
133
+ if (cmd !== void 0 && codexIsDestructiveExecCommand(cmd)) {
134
+ return { service: "terminal", actionType: "write" };
135
+ }
136
+ return { service: "terminal", actionType: "execute" };
137
+ }
138
+ if (n === "apply_patch" || n === "edit" || n === "write") {
139
+ return { service: "filesystem", actionType: "write" };
140
+ }
141
+ const scope = mapToolToScope(n);
142
+ let actionType = scope.permissionLevel;
143
+ if (!isKnownTool(n) && actionType === "execute") {
144
+ actionType = "write";
145
+ }
146
+ return { service: scope.service, actionType };
147
+ }
148
+
149
+ exports.codexIsDestructiveExecCommand = codexIsDestructiveExecCommand;
150
+ exports.extractExecCommand = extractExecCommand;
151
+ exports.mapCodexCliToolToShield = mapCodexCliToolToShield;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+
3
+ var codexCliHooksShared_js = require("./codex-cli-hooks-shared.cjs");
4
+ var codexCliToolMap_js = require("./codex-cli-tool-map.cjs");
5
+
6
+ // AUTO-GENERATED from src/hooks/codex-cli-*.ts — do not edit manually. Run pnpm build from the package root to regenerate.
7
+
8
+ function readStdin() {
9
+ return new Promise((resolve, reject) => {
10
+ const chunks = [];
11
+ process.stdin.setEncoding("utf8");
12
+ process.stdin.on("data", (c) => chunks.push(c));
13
+ process.stdin.on("end", () => {
14
+ resolve(chunks.join(""));
15
+ });
16
+ process.stdin.on("error", reject);
17
+ });
18
+ }
19
+ async function main() {
20
+ let raw;
21
+ try {
22
+ raw = await readStdin();
23
+ } catch {
24
+ process.exit(0);
25
+ }
26
+ const config = codexCliHooksShared_js.loadCodexCliConfig();
27
+ if (config === null || config.apiKey.length === 0 || config.agentName.length === 0) {
28
+ process.exit(0);
29
+ }
30
+ let hookPayload;
31
+ try {
32
+ hookPayload = JSON.parse(raw.length > 0 ? raw : "{}");
33
+ } catch {
34
+ process.exit(0);
35
+ }
36
+ const toolNameRaw =
37
+ (typeof hookPayload["tool_name"] === "string" && hookPayload["tool_name"]) || "";
38
+ const toolInput = hookPayload["tool_input"] !== void 0 ? hookPayload["tool_input"] : void 0;
39
+ const toolResult =
40
+ hookPayload["tool_response"] !== void 0
41
+ ? hookPayload["tool_response"]
42
+ : hookPayload["tool_result"] !== void 0
43
+ ? hookPayload["tool_result"]
44
+ : void 0;
45
+ try {
46
+ void (typeof toolInput === "string"
47
+ ? toolInput
48
+ : JSON.stringify(toolInput === void 0 ? null : toolInput));
49
+ void (typeof toolResult === "string"
50
+ ? toolResult
51
+ : JSON.stringify(toolResult === void 0 ? null : toolResult));
52
+ } catch {
53
+ process.exit(0);
54
+ }
55
+ const { service, actionType } = codexCliToolMap_js.mapCodexCliToolToShield(
56
+ toolNameRaw,
57
+ toolInput,
58
+ );
59
+ const metadata = {
60
+ tool_name: toolNameRaw,
61
+ tool_input: codexCliHooksShared_js.serializeHookAuditFragment(toolInput),
62
+ tool_result: codexCliHooksShared_js.serializeHookAuditFragment(toolResult),
63
+ source: "codex-cli",
64
+ };
65
+ const payload = {
66
+ agent: config.agentName,
67
+ service,
68
+ actionType,
69
+ status: "approved",
70
+ metadata,
71
+ platform: "codex-cli",
72
+ };
73
+ try {
74
+ await codexCliHooksShared_js.shieldPostJson(config.baseUrl, config.apiKey, payload);
75
+ } catch (e) {
76
+ const refusedHost = codexCliHooksShared_js.readHttpApiKeyRefusalHostname(e);
77
+ if (refusedHost !== null) {
78
+ process.stderr.write(codexCliHooksShared_js.formatHttpApiKeyRefusal(refusedHost));
79
+ process.exit(0);
80
+ }
81
+ process.stderr.write("[Shield] Warning: failed to send logs to Shield.\n");
82
+ process.stderr.write(codexCliHooksShared_js.formatShieldNetworkError(e));
83
+ }
84
+ process.exit(0);
85
+ }
86
+ main().catch((e) => {
87
+ const refusedHost = codexCliHooksShared_js.readHttpApiKeyRefusalHostname(e);
88
+ if (refusedHost !== null) {
89
+ process.stderr.write(codexCliHooksShared_js.formatHttpApiKeyRefusal(refusedHost));
90
+ process.exit(0);
91
+ }
92
+ process.stderr.write("[Shield] Warning: failed to send logs to Shield.\n");
93
+ process.stderr.write(codexCliHooksShared_js.formatShieldNetworkError(e));
94
+ process.exit(0);
95
+ });