multicorn-shield 1.8.0 → 1.9.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/CHANGELOG.md +28 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -0
- package/dist/multicorn-proxy.js +226 -2
- package/dist/multicorn-shield.js +226 -2
- package/dist/shield-extension.js +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
- Bump `version` in `package.json` before publishing to npm.
|
|
11
11
|
|
|
12
|
+
## [1.9.0] - 2026-05-12
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- OpenAI Codex CLI as a supported platform (native hooks + hosted proxy)
|
|
17
|
+
- Codex CLI hook scripts (PreToolUse, PostToolUse) for terminal command interception
|
|
18
|
+
- Codex CLI agent resolution and tool name mapping
|
|
19
|
+
- CLI wizard support for Codex CLI: native plugin install and hosted proxy TOML snippet
|
|
20
|
+
- `plugins/codex-cli/README.md` documenting hook script build and test workflow
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Codex config.toml feature flag updated from deprecated `codex_hooks` to `hooks`
|
|
25
|
+
- Config.toml migration: init now detects and replaces deprecated `codex_hooks` flag automatically
|
|
26
|
+
- Hook scripts reject plaintext HTTP for non-localhost API calls
|
|
27
|
+
- Config file permission warning when `~/.multicorn/config.json` is readable by other users
|
|
28
|
+
- Destructive command detection uses word-boundary matching instead of substring includes
|
|
29
|
+
- Unknown tool names default to restrictive `write` permission level instead of `execute`
|
|
30
|
+
- Audit log payloads size-bounded and secret patterns redacted before transmission
|
|
31
|
+
- Error messages sanitised: internal details hidden unless `MULTICORN_DEBUG` is set
|
|
32
|
+
- Test-mode polling escape hatch removed from production hook scripts
|
|
33
|
+
- Shared utility module extracted to eliminate duplication between hook scripts
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Error message prefix shortened from `[multicorn-shield]` to `[Shield]`
|
|
38
|
+
- "Audit trail" terminology replaced with "record the action" in user-facing messages
|
|
39
|
+
|
|
12
40
|
## [1.8.0] - 2026-05-11
|
|
13
41
|
|
|
14
42
|
### Added
|
package/dist/index.cjs
CHANGED
package/dist/index.d.cts
CHANGED
|
@@ -12,7 +12,7 @@ import { LitElement, PropertyValues, HTMLTemplateResult } from 'lit';
|
|
|
12
12
|
/**
|
|
13
13
|
* Agent client platforms supported by hosted proxy and native hooks (aligned with API validation).
|
|
14
14
|
*/
|
|
15
|
-
declare const AGENT_PLATFORM_SLUGS: readonly ["openclaw", "claude-code", "claude-desktop", "cursor", "windsurf", "cline", "gemini-cli", "continue-dev", "github-copilot", "goose", "kilo-code", "opencode", "other-mcp", "github-actions", "unknown"];
|
|
15
|
+
declare const AGENT_PLATFORM_SLUGS: readonly ["openclaw", "claude-code", "claude-desktop", "cursor", "windsurf", "cline", "gemini-cli", "continue-dev", "github-copilot", "goose", "kilo-code", "opencode", "codex-cli", "other-mcp", "github-actions", "unknown"];
|
|
16
16
|
type AgentPlatformSlug = (typeof AGENT_PLATFORM_SLUGS)[number];
|
|
17
17
|
/**
|
|
18
18
|
* Possible operational states for an agent.
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { LitElement, PropertyValues, HTMLTemplateResult } from 'lit';
|
|
|
12
12
|
/**
|
|
13
13
|
* Agent client platforms supported by hosted proxy and native hooks (aligned with API validation).
|
|
14
14
|
*/
|
|
15
|
-
declare const AGENT_PLATFORM_SLUGS: readonly ["openclaw", "claude-code", "claude-desktop", "cursor", "windsurf", "cline", "gemini-cli", "continue-dev", "github-copilot", "goose", "kilo-code", "opencode", "other-mcp", "github-actions", "unknown"];
|
|
15
|
+
declare const AGENT_PLATFORM_SLUGS: readonly ["openclaw", "claude-code", "claude-desktop", "cursor", "windsurf", "cline", "gemini-cli", "continue-dev", "github-copilot", "goose", "kilo-code", "opencode", "codex-cli", "other-mcp", "github-actions", "unknown"];
|
|
16
16
|
type AgentPlatformSlug = (typeof AGENT_PLATFORM_SLUGS)[number];
|
|
17
17
|
/**
|
|
18
18
|
* Possible operational states for an agent.
|
package/dist/index.js
CHANGED
package/dist/multicorn-proxy.js
CHANGED
|
@@ -941,6 +941,99 @@ async function installOpenCodeNativePlugin() {
|
|
|
941
941
|
const dest = join(destDir, "multicorn-shield.ts");
|
|
942
942
|
await copyFile(src, dest);
|
|
943
943
|
}
|
|
944
|
+
function getCodexCliHooksInstallDir() {
|
|
945
|
+
return join(homedir(), ".multicorn", "codex-cli-hooks");
|
|
946
|
+
}
|
|
947
|
+
function getCodexConfigTomlPath() {
|
|
948
|
+
return join(homedir(), ".codex", "config.toml");
|
|
949
|
+
}
|
|
950
|
+
function getCodexHooksJsonPath() {
|
|
951
|
+
return join(homedir(), ".codex", "hooks.json");
|
|
952
|
+
}
|
|
953
|
+
async function installCodexCliNativeHooks() {
|
|
954
|
+
const root = multicornShieldPackageRoot();
|
|
955
|
+
const scriptsDir = join(root, "plugins", "codex-cli", "hooks", "scripts");
|
|
956
|
+
const preHook = join(scriptsDir, "pre-tool-use.cjs");
|
|
957
|
+
const postHook = join(scriptsDir, "post-tool-use.cjs");
|
|
958
|
+
const toolMap = join(scriptsDir, "codex-cli-tool-map.cjs");
|
|
959
|
+
const sharedHook = join(scriptsDir, "codex-cli-hooks-shared.cjs");
|
|
960
|
+
if (!existsSync(preHook) || !existsSync(postHook) || !existsSync(toolMap) || !existsSync(sharedHook)) {
|
|
961
|
+
throw new Error(
|
|
962
|
+
`Could not find Shield Codex CLI hook scripts at ${scriptsDir}. If you use npm, install the latest multicorn-shield package.`
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
const destDir = getCodexCliHooksInstallDir();
|
|
966
|
+
await mkdir(destDir, { recursive: true });
|
|
967
|
+
await copyFile(preHook, join(destDir, "pre-tool-use.cjs"));
|
|
968
|
+
await copyFile(postHook, join(destDir, "post-tool-use.cjs"));
|
|
969
|
+
await copyFile(toolMap, join(destDir, "codex-cli-tool-map.cjs"));
|
|
970
|
+
await copyFile(sharedHook, join(destDir, "codex-cli-hooks-shared.cjs"));
|
|
971
|
+
const configTomlPath = getCodexConfigTomlPath();
|
|
972
|
+
await mkdir(join(homedir(), ".codex"), { recursive: true });
|
|
973
|
+
let tomlContent = "";
|
|
974
|
+
try {
|
|
975
|
+
tomlContent = await readFile(configTomlPath, "utf8");
|
|
976
|
+
} catch (err) {
|
|
977
|
+
if (!isErrnoException(err) || err.code !== "ENOENT") {
|
|
978
|
+
throw err;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (tomlContent.includes("codex_hooks")) {
|
|
982
|
+
tomlContent = tomlContent.replace(/codex_hooks/g, "hooks");
|
|
983
|
+
await writeFile(configTomlPath, tomlContent, "utf8");
|
|
984
|
+
}
|
|
985
|
+
if (!/\bhooks\s*=\s*true/.test(tomlContent)) {
|
|
986
|
+
tomlContent = tomlContent.includes("[features]") ? tomlContent.replace("[features]", "[features]\nhooks = true") : tomlContent.trimEnd() + (tomlContent.length > 0 ? "\n\n" : "") + "[features]\nhooks = true\n";
|
|
987
|
+
await writeFile(configTomlPath, tomlContent, "utf8");
|
|
988
|
+
}
|
|
989
|
+
const hooksConfig = {
|
|
990
|
+
hooks: {
|
|
991
|
+
PreToolUse: [
|
|
992
|
+
{
|
|
993
|
+
matcher: "",
|
|
994
|
+
hooks: [
|
|
995
|
+
{
|
|
996
|
+
type: "command",
|
|
997
|
+
command: `node ${join(destDir, "pre-tool-use.cjs")}`,
|
|
998
|
+
statusMessage: "Checking Shield permissions"
|
|
999
|
+
}
|
|
1000
|
+
]
|
|
1001
|
+
}
|
|
1002
|
+
],
|
|
1003
|
+
PostToolUse: [
|
|
1004
|
+
{
|
|
1005
|
+
matcher: "",
|
|
1006
|
+
hooks: [
|
|
1007
|
+
{
|
|
1008
|
+
type: "command",
|
|
1009
|
+
command: `node ${join(destDir, "post-tool-use.cjs")}`,
|
|
1010
|
+
timeout: 30,
|
|
1011
|
+
statusMessage: "Logging to Shield"
|
|
1012
|
+
}
|
|
1013
|
+
]
|
|
1014
|
+
}
|
|
1015
|
+
]
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
await writeFile(getCodexHooksJsonPath(), JSON.stringify(hooksConfig, null, 2) + "\n", "utf8");
|
|
1019
|
+
}
|
|
1020
|
+
async function promptCodexCliIntegrationMode(ask) {
|
|
1021
|
+
process.stderr.write("\n" + style.bold("Codex CLI integration") + "\n");
|
|
1022
|
+
process.stderr.write(
|
|
1023
|
+
" " + style.violet("1") + ". Native plugin - Shield checks terminal (Bash) commands via Codex Hooks\n"
|
|
1024
|
+
);
|
|
1025
|
+
process.stderr.write(
|
|
1026
|
+
" " + style.violet("2") + ". Hosted proxy - govern MCP server traffic via config.toml\n"
|
|
1027
|
+
);
|
|
1028
|
+
let choice = 0;
|
|
1029
|
+
while (choice === 0) {
|
|
1030
|
+
const input = await ask("Choose integration (1-2): ");
|
|
1031
|
+
const num = parseInt(input.trim(), 10);
|
|
1032
|
+
if (num === 1) choice = 1;
|
|
1033
|
+
if (num === 2) choice = 2;
|
|
1034
|
+
}
|
|
1035
|
+
return choice === 1 ? "native" : "hosted";
|
|
1036
|
+
}
|
|
944
1037
|
async function promptClineIntegrationMode(ask) {
|
|
945
1038
|
process.stderr.write("\n" + style.bold("Cline integration") + "\n");
|
|
946
1039
|
process.stderr.write(
|
|
@@ -2059,6 +2152,9 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
|
|
|
2059
2152
|
if (result === "parse-error") {
|
|
2060
2153
|
printHostedProxyJsonParseWarning(join(workspacePath, "opencode.json"));
|
|
2061
2154
|
}
|
|
2155
|
+
} else if (platform === "codex-cli") {
|
|
2156
|
+
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
2157
|
+
return;
|
|
2062
2158
|
} else if (platform === "continue-dev") {
|
|
2063
2159
|
result = await mergeContinueHostedMcp(
|
|
2064
2160
|
workspacePath,
|
|
@@ -2194,7 +2290,8 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
2194
2290
|
"github-copilot",
|
|
2195
2291
|
"continue-dev",
|
|
2196
2292
|
"goose",
|
|
2197
|
-
"opencode"
|
|
2293
|
+
"opencode",
|
|
2294
|
+
"codex-cli"
|
|
2198
2295
|
]);
|
|
2199
2296
|
const usesInlineKey = hostedInlinePlatforms.has(platform);
|
|
2200
2297
|
const authHeader = usesInlineKey ? `Bearer ${apiKey}` : "Bearer YOUR_SHIELD_API_KEY";
|
|
@@ -2295,6 +2392,11 @@ mcpServers:
|
|
|
2295
2392
|
null,
|
|
2296
2393
|
2
|
|
2297
2394
|
);
|
|
2395
|
+
} else if (platform === "codex-cli") {
|
|
2396
|
+
snippetText = `[mcp_servers.${shortName}]
|
|
2397
|
+
url = "${urlInSnippet}"
|
|
2398
|
+
bearer_token_env_var = "MULTICORN_API_KEY"
|
|
2399
|
+
`;
|
|
2298
2400
|
} else {
|
|
2299
2401
|
const urlKey = platform === "windsurf" ? "serverUrl" : "url";
|
|
2300
2402
|
snippetText = JSON.stringify(
|
|
@@ -2338,6 +2440,12 @@ mcpServers:
|
|
|
2338
2440
|
"Add this to opencode.json in your project root (or ~/.config/opencode/opencode.json for global config). OpenCode detects configured MCP servers on the next session start."
|
|
2339
2441
|
) + "\n\n"
|
|
2340
2442
|
);
|
|
2443
|
+
} else if (platform === "codex-cli") {
|
|
2444
|
+
process.stderr.write(
|
|
2445
|
+
"\n" + style.dim(
|
|
2446
|
+
"Add this to ~/.codex/config.toml (create the file if it does not exist). Set the MULTICORN_API_KEY environment variable to your Shield API key. Restart Codex CLI after saving."
|
|
2447
|
+
) + "\n\n"
|
|
2448
|
+
);
|
|
2341
2449
|
} else if (platform === "github-copilot") {
|
|
2342
2450
|
process.stderr.write(
|
|
2343
2451
|
"\n" + style.dim(
|
|
@@ -3054,6 +3162,100 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3054
3162
|
setupSucceeded = true;
|
|
3055
3163
|
}
|
|
3056
3164
|
}
|
|
3165
|
+
} else if (selectedPlatform === "codex-cli") {
|
|
3166
|
+
const codexMode = await promptCodexCliIntegrationMode(ask);
|
|
3167
|
+
if (codexMode === "native") {
|
|
3168
|
+
try {
|
|
3169
|
+
await installCodexCliNativeHooks();
|
|
3170
|
+
process.stderr.write("\n" + style.bold("Shield Codex CLI hooks installed") + "\n\n");
|
|
3171
|
+
process.stderr.write(
|
|
3172
|
+
style.dim("Hook scripts: ") + style.cyan(getCodexCliHooksInstallDir()) + "\n"
|
|
3173
|
+
);
|
|
3174
|
+
process.stderr.write(
|
|
3175
|
+
style.dim("Hooks config: ") + style.cyan(getCodexHooksJsonPath()) + "\n"
|
|
3176
|
+
);
|
|
3177
|
+
process.stderr.write(
|
|
3178
|
+
style.dim("Feature flag: ") + style.cyan(getCodexConfigTomlPath()) + "\n"
|
|
3179
|
+
);
|
|
3180
|
+
process.stderr.write("\n");
|
|
3181
|
+
process.stderr.write(
|
|
3182
|
+
style.dim(
|
|
3183
|
+
"Codex hooks currently intercept terminal (Bash) commands only. File edits and MCP tool calls are not yet covered by hooks."
|
|
3184
|
+
) + "\n\n"
|
|
3185
|
+
);
|
|
3186
|
+
process.stderr.write(
|
|
3187
|
+
style.dim(
|
|
3188
|
+
"Start Codex CLI, then type /hooks to review the Shield hooks. Press 't' to trust each one before they can run."
|
|
3189
|
+
) + "\n"
|
|
3190
|
+
);
|
|
3191
|
+
configuredAgents.push({
|
|
3192
|
+
selection,
|
|
3193
|
+
platform: selectedPlatform,
|
|
3194
|
+
platformLabel: selectedLabel,
|
|
3195
|
+
agentName,
|
|
3196
|
+
codexCliIntegration: "native"
|
|
3197
|
+
});
|
|
3198
|
+
setupSucceeded = true;
|
|
3199
|
+
} catch (error) {
|
|
3200
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3201
|
+
process.stderr.write(style.red("\u2717 ") + detail + "\n");
|
|
3202
|
+
}
|
|
3203
|
+
} else {
|
|
3204
|
+
const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
|
|
3205
|
+
let proxyUrl = "";
|
|
3206
|
+
let created = false;
|
|
3207
|
+
while (!created) {
|
|
3208
|
+
const spinner = withSpinner("Creating proxy config...");
|
|
3209
|
+
try {
|
|
3210
|
+
proxyUrl = await createProxyConfig(
|
|
3211
|
+
resolvedBaseUrl,
|
|
3212
|
+
apiKey,
|
|
3213
|
+
agentName,
|
|
3214
|
+
targetUrl,
|
|
3215
|
+
shortName,
|
|
3216
|
+
selectedPlatform,
|
|
3217
|
+
upstreamHeaders
|
|
3218
|
+
);
|
|
3219
|
+
spinner.stop(true, "Proxy config created!");
|
|
3220
|
+
created = true;
|
|
3221
|
+
} catch (error) {
|
|
3222
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3223
|
+
spinner.stop(false, detail);
|
|
3224
|
+
const retry = await ask("Try again? (Y/n) ");
|
|
3225
|
+
if (retry.trim().toLowerCase() === "n") {
|
|
3226
|
+
break;
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
if (created && proxyUrl.length > 0) {
|
|
3231
|
+
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
3232
|
+
process.stderr.write(
|
|
3233
|
+
" " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
|
|
3234
|
+
);
|
|
3235
|
+
await applyHostedProxyMcpConfig(
|
|
3236
|
+
selectedPlatform,
|
|
3237
|
+
proxyUrl,
|
|
3238
|
+
shortName,
|
|
3239
|
+
apiKey,
|
|
3240
|
+
initWorkspacePath
|
|
3241
|
+
);
|
|
3242
|
+
process.stderr.write(
|
|
3243
|
+
"\n" + style.dim(
|
|
3244
|
+
"Add the TOML snippet above to ~/.codex/config.toml. Set the MULTICORN_API_KEY environment variable to your Shield API key. Restart Codex CLI after saving."
|
|
3245
|
+
) + "\n"
|
|
3246
|
+
);
|
|
3247
|
+
configuredAgents.push({
|
|
3248
|
+
selection,
|
|
3249
|
+
platform: selectedPlatform,
|
|
3250
|
+
platformLabel: selectedLabel,
|
|
3251
|
+
agentName,
|
|
3252
|
+
shortName,
|
|
3253
|
+
proxyUrl,
|
|
3254
|
+
codexCliIntegration: "hosted"
|
|
3255
|
+
});
|
|
3256
|
+
setupSucceeded = true;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3057
3259
|
} else if (selectedPlatform === "cline") {
|
|
3058
3260
|
const clineMode = await promptClineIntegrationMode(ask);
|
|
3059
3261
|
if (clineMode === "native") {
|
|
@@ -3366,6 +3568,22 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3366
3568
|
"\n" + style.bold("OpenCode (hosted)") + '\n \u2192 Restart OpenCode or start a new session after saving opencode.json\n \u2192 Try it: paste this into OpenCode:\n "Use the ' + ocLabel + ' MCP server to list allowed directories"\n'
|
|
3367
3569
|
);
|
|
3368
3570
|
}
|
|
3571
|
+
const codexNativeConfigured = configuredAgents.some(
|
|
3572
|
+
(a) => a.platform === "codex-cli" && a.codexCliIntegration === "native"
|
|
3573
|
+
);
|
|
3574
|
+
const codexHostedConfigured = configuredAgents.some(
|
|
3575
|
+
(a) => a.platform === "codex-cli" && a.codexCliIntegration === "hosted"
|
|
3576
|
+
);
|
|
3577
|
+
if (codexNativeConfigured) {
|
|
3578
|
+
blocks.push(
|
|
3579
|
+
"\n" + style.bold("Codex CLI (native)") + "\n \u2192 Start Codex CLI (run 'codex' in your terminal)\n \u2192 Type /hooks to review the Shield hooks - press 't' to trust each one\n \u2192 Once both hooks are trusted, try a Bash command - Shield will intercept and check permissions\n \u2192 Note: hooks currently cover terminal (Bash) commands only\n"
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3582
|
+
if (codexHostedConfigured) {
|
|
3583
|
+
blocks.push(
|
|
3584
|
+
"\n" + style.bold("Codex CLI (hosted)") + "\n \u2192 Set the MULTICORN_API_KEY environment variable to your Shield API key\n \u2192 Restart Codex CLI after saving config.toml\n \u2192 Try it: make a request that uses an MCP tool through Shield\n"
|
|
3585
|
+
);
|
|
3586
|
+
}
|
|
3369
3587
|
if (configuredPlatforms.has("other-mcp")) {
|
|
3370
3588
|
blocks.push(
|
|
3371
3589
|
"\n" + style.bold("Local MCP / Other") + "\n \u2192 Run your configured wrap command (for example " + style.cyan("npx multicorn-shield --wrap ...") + ")\n \u2192 Try it: make a request in your coding agent - Shield will intercept the first tool call and ask for your consent\n"
|
|
@@ -3431,6 +3649,12 @@ var init_config = __esm({
|
|
|
3431
3649
|
section: "native",
|
|
3432
3650
|
prereqUrl: "https://opencode.ai"
|
|
3433
3651
|
},
|
|
3652
|
+
{
|
|
3653
|
+
slug: "codex-cli",
|
|
3654
|
+
displayName: "Codex CLI",
|
|
3655
|
+
section: "native",
|
|
3656
|
+
prereqUrl: "https://github.com/openai/codex"
|
|
3657
|
+
},
|
|
3434
3658
|
{
|
|
3435
3659
|
slug: "cursor",
|
|
3436
3660
|
displayName: "Cursor",
|
|
@@ -4436,7 +4660,7 @@ var init_package = __esm({
|
|
|
4436
4660
|
"package.json"() {
|
|
4437
4661
|
package_default = {
|
|
4438
4662
|
name: "multicorn-shield",
|
|
4439
|
-
version: "1.
|
|
4663
|
+
version: "1.9.0",
|
|
4440
4664
|
description: "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
|
|
4441
4665
|
license: "MIT",
|
|
4442
4666
|
author: "Multicorn AI Pty Ltd",
|
package/dist/multicorn-shield.js
CHANGED
|
@@ -955,6 +955,99 @@ async function installOpenCodeNativePlugin() {
|
|
|
955
955
|
const dest = join(destDir, "multicorn-shield.ts");
|
|
956
956
|
await copyFile(src, dest);
|
|
957
957
|
}
|
|
958
|
+
function getCodexCliHooksInstallDir() {
|
|
959
|
+
return join(homedir(), ".multicorn", "codex-cli-hooks");
|
|
960
|
+
}
|
|
961
|
+
function getCodexConfigTomlPath() {
|
|
962
|
+
return join(homedir(), ".codex", "config.toml");
|
|
963
|
+
}
|
|
964
|
+
function getCodexHooksJsonPath() {
|
|
965
|
+
return join(homedir(), ".codex", "hooks.json");
|
|
966
|
+
}
|
|
967
|
+
async function installCodexCliNativeHooks() {
|
|
968
|
+
const root = multicornShieldPackageRoot();
|
|
969
|
+
const scriptsDir = join(root, "plugins", "codex-cli", "hooks", "scripts");
|
|
970
|
+
const preHook = join(scriptsDir, "pre-tool-use.cjs");
|
|
971
|
+
const postHook = join(scriptsDir, "post-tool-use.cjs");
|
|
972
|
+
const toolMap = join(scriptsDir, "codex-cli-tool-map.cjs");
|
|
973
|
+
const sharedHook = join(scriptsDir, "codex-cli-hooks-shared.cjs");
|
|
974
|
+
if (!existsSync(preHook) || !existsSync(postHook) || !existsSync(toolMap) || !existsSync(sharedHook)) {
|
|
975
|
+
throw new Error(
|
|
976
|
+
`Could not find Shield Codex CLI hook scripts at ${scriptsDir}. If you use npm, install the latest multicorn-shield package.`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
const destDir = getCodexCliHooksInstallDir();
|
|
980
|
+
await mkdir(destDir, { recursive: true });
|
|
981
|
+
await copyFile(preHook, join(destDir, "pre-tool-use.cjs"));
|
|
982
|
+
await copyFile(postHook, join(destDir, "post-tool-use.cjs"));
|
|
983
|
+
await copyFile(toolMap, join(destDir, "codex-cli-tool-map.cjs"));
|
|
984
|
+
await copyFile(sharedHook, join(destDir, "codex-cli-hooks-shared.cjs"));
|
|
985
|
+
const configTomlPath = getCodexConfigTomlPath();
|
|
986
|
+
await mkdir(join(homedir(), ".codex"), { recursive: true });
|
|
987
|
+
let tomlContent = "";
|
|
988
|
+
try {
|
|
989
|
+
tomlContent = await readFile(configTomlPath, "utf8");
|
|
990
|
+
} catch (err) {
|
|
991
|
+
if (!isErrnoException(err) || err.code !== "ENOENT") {
|
|
992
|
+
throw err;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
if (tomlContent.includes("codex_hooks")) {
|
|
996
|
+
tomlContent = tomlContent.replace(/codex_hooks/g, "hooks");
|
|
997
|
+
await writeFile(configTomlPath, tomlContent, "utf8");
|
|
998
|
+
}
|
|
999
|
+
if (!/\bhooks\s*=\s*true/.test(tomlContent)) {
|
|
1000
|
+
tomlContent = tomlContent.includes("[features]") ? tomlContent.replace("[features]", "[features]\nhooks = true") : tomlContent.trimEnd() + (tomlContent.length > 0 ? "\n\n" : "") + "[features]\nhooks = true\n";
|
|
1001
|
+
await writeFile(configTomlPath, tomlContent, "utf8");
|
|
1002
|
+
}
|
|
1003
|
+
const hooksConfig = {
|
|
1004
|
+
hooks: {
|
|
1005
|
+
PreToolUse: [
|
|
1006
|
+
{
|
|
1007
|
+
matcher: "",
|
|
1008
|
+
hooks: [
|
|
1009
|
+
{
|
|
1010
|
+
type: "command",
|
|
1011
|
+
command: `node ${join(destDir, "pre-tool-use.cjs")}`,
|
|
1012
|
+
statusMessage: "Checking Shield permissions"
|
|
1013
|
+
}
|
|
1014
|
+
]
|
|
1015
|
+
}
|
|
1016
|
+
],
|
|
1017
|
+
PostToolUse: [
|
|
1018
|
+
{
|
|
1019
|
+
matcher: "",
|
|
1020
|
+
hooks: [
|
|
1021
|
+
{
|
|
1022
|
+
type: "command",
|
|
1023
|
+
command: `node ${join(destDir, "post-tool-use.cjs")}`,
|
|
1024
|
+
timeout: 30,
|
|
1025
|
+
statusMessage: "Logging to Shield"
|
|
1026
|
+
}
|
|
1027
|
+
]
|
|
1028
|
+
}
|
|
1029
|
+
]
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
await writeFile(getCodexHooksJsonPath(), JSON.stringify(hooksConfig, null, 2) + "\n", "utf8");
|
|
1033
|
+
}
|
|
1034
|
+
async function promptCodexCliIntegrationMode(ask) {
|
|
1035
|
+
process.stderr.write("\n" + style.bold("Codex CLI integration") + "\n");
|
|
1036
|
+
process.stderr.write(
|
|
1037
|
+
" " + style.violet("1") + ". Native plugin - Shield checks terminal (Bash) commands via Codex Hooks\n"
|
|
1038
|
+
);
|
|
1039
|
+
process.stderr.write(
|
|
1040
|
+
" " + style.violet("2") + ". Hosted proxy - govern MCP server traffic via config.toml\n"
|
|
1041
|
+
);
|
|
1042
|
+
let choice = 0;
|
|
1043
|
+
while (choice === 0) {
|
|
1044
|
+
const input = await ask("Choose integration (1-2): ");
|
|
1045
|
+
const num = parseInt(input.trim(), 10);
|
|
1046
|
+
if (num === 1) choice = 1;
|
|
1047
|
+
if (num === 2) choice = 2;
|
|
1048
|
+
}
|
|
1049
|
+
return choice === 1 ? "native" : "hosted";
|
|
1050
|
+
}
|
|
958
1051
|
async function promptClineIntegrationMode(ask) {
|
|
959
1052
|
process.stderr.write("\n" + style.bold("Cline integration") + "\n");
|
|
960
1053
|
process.stderr.write(
|
|
@@ -1406,6 +1499,12 @@ var INIT_WIZARD_PLATFORM_REGISTRY = [
|
|
|
1406
1499
|
section: "native",
|
|
1407
1500
|
prereqUrl: "https://opencode.ai"
|
|
1408
1501
|
},
|
|
1502
|
+
{
|
|
1503
|
+
slug: "codex-cli",
|
|
1504
|
+
displayName: "Codex CLI",
|
|
1505
|
+
section: "native",
|
|
1506
|
+
prereqUrl: "https://github.com/openai/codex"
|
|
1507
|
+
},
|
|
1409
1508
|
{
|
|
1410
1509
|
slug: "cursor",
|
|
1411
1510
|
displayName: "Cursor",
|
|
@@ -2146,6 +2245,9 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
|
|
|
2146
2245
|
if (result === "parse-error") {
|
|
2147
2246
|
printHostedProxyJsonParseWarning(join(workspacePath, "opencode.json"));
|
|
2148
2247
|
}
|
|
2248
|
+
} else if (platform === "codex-cli") {
|
|
2249
|
+
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
2250
|
+
return;
|
|
2149
2251
|
} else if (platform === "continue-dev") {
|
|
2150
2252
|
result = await mergeContinueHostedMcp(
|
|
2151
2253
|
workspacePath,
|
|
@@ -2281,7 +2383,8 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
2281
2383
|
"github-copilot",
|
|
2282
2384
|
"continue-dev",
|
|
2283
2385
|
"goose",
|
|
2284
|
-
"opencode"
|
|
2386
|
+
"opencode",
|
|
2387
|
+
"codex-cli"
|
|
2285
2388
|
]);
|
|
2286
2389
|
const usesInlineKey = hostedInlinePlatforms.has(platform);
|
|
2287
2390
|
const authHeader = usesInlineKey ? `Bearer ${apiKey}` : "Bearer YOUR_SHIELD_API_KEY";
|
|
@@ -2382,6 +2485,11 @@ mcpServers:
|
|
|
2382
2485
|
null,
|
|
2383
2486
|
2
|
|
2384
2487
|
);
|
|
2488
|
+
} else if (platform === "codex-cli") {
|
|
2489
|
+
snippetText = `[mcp_servers.${shortName}]
|
|
2490
|
+
url = "${urlInSnippet}"
|
|
2491
|
+
bearer_token_env_var = "MULTICORN_API_KEY"
|
|
2492
|
+
`;
|
|
2385
2493
|
} else {
|
|
2386
2494
|
const urlKey = platform === "windsurf" ? "serverUrl" : "url";
|
|
2387
2495
|
snippetText = JSON.stringify(
|
|
@@ -2425,6 +2533,12 @@ mcpServers:
|
|
|
2425
2533
|
"Add this to opencode.json in your project root (or ~/.config/opencode/opencode.json for global config). OpenCode detects configured MCP servers on the next session start."
|
|
2426
2534
|
) + "\n\n"
|
|
2427
2535
|
);
|
|
2536
|
+
} else if (platform === "codex-cli") {
|
|
2537
|
+
process.stderr.write(
|
|
2538
|
+
"\n" + style.dim(
|
|
2539
|
+
"Add this to ~/.codex/config.toml (create the file if it does not exist). Set the MULTICORN_API_KEY environment variable to your Shield API key. Restart Codex CLI after saving."
|
|
2540
|
+
) + "\n\n"
|
|
2541
|
+
);
|
|
2428
2542
|
} else if (platform === "github-copilot") {
|
|
2429
2543
|
process.stderr.write(
|
|
2430
2544
|
"\n" + style.dim(
|
|
@@ -3142,6 +3256,100 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3142
3256
|
setupSucceeded = true;
|
|
3143
3257
|
}
|
|
3144
3258
|
}
|
|
3259
|
+
} else if (selectedPlatform === "codex-cli") {
|
|
3260
|
+
const codexMode = await promptCodexCliIntegrationMode(ask);
|
|
3261
|
+
if (codexMode === "native") {
|
|
3262
|
+
try {
|
|
3263
|
+
await installCodexCliNativeHooks();
|
|
3264
|
+
process.stderr.write("\n" + style.bold("Shield Codex CLI hooks installed") + "\n\n");
|
|
3265
|
+
process.stderr.write(
|
|
3266
|
+
style.dim("Hook scripts: ") + style.cyan(getCodexCliHooksInstallDir()) + "\n"
|
|
3267
|
+
);
|
|
3268
|
+
process.stderr.write(
|
|
3269
|
+
style.dim("Hooks config: ") + style.cyan(getCodexHooksJsonPath()) + "\n"
|
|
3270
|
+
);
|
|
3271
|
+
process.stderr.write(
|
|
3272
|
+
style.dim("Feature flag: ") + style.cyan(getCodexConfigTomlPath()) + "\n"
|
|
3273
|
+
);
|
|
3274
|
+
process.stderr.write("\n");
|
|
3275
|
+
process.stderr.write(
|
|
3276
|
+
style.dim(
|
|
3277
|
+
"Codex hooks currently intercept terminal (Bash) commands only. File edits and MCP tool calls are not yet covered by hooks."
|
|
3278
|
+
) + "\n\n"
|
|
3279
|
+
);
|
|
3280
|
+
process.stderr.write(
|
|
3281
|
+
style.dim(
|
|
3282
|
+
"Start Codex CLI, then type /hooks to review the Shield hooks. Press 't' to trust each one before they can run."
|
|
3283
|
+
) + "\n"
|
|
3284
|
+
);
|
|
3285
|
+
configuredAgents.push({
|
|
3286
|
+
selection,
|
|
3287
|
+
platform: selectedPlatform,
|
|
3288
|
+
platformLabel: selectedLabel,
|
|
3289
|
+
agentName,
|
|
3290
|
+
codexCliIntegration: "native"
|
|
3291
|
+
});
|
|
3292
|
+
setupSucceeded = true;
|
|
3293
|
+
} catch (error) {
|
|
3294
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3295
|
+
process.stderr.write(style.red("\u2717 ") + detail + "\n");
|
|
3296
|
+
}
|
|
3297
|
+
} else {
|
|
3298
|
+
const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
|
|
3299
|
+
let proxyUrl = "";
|
|
3300
|
+
let created = false;
|
|
3301
|
+
while (!created) {
|
|
3302
|
+
const spinner = withSpinner("Creating proxy config...");
|
|
3303
|
+
try {
|
|
3304
|
+
proxyUrl = await createProxyConfig(
|
|
3305
|
+
resolvedBaseUrl,
|
|
3306
|
+
apiKey,
|
|
3307
|
+
agentName,
|
|
3308
|
+
targetUrl,
|
|
3309
|
+
shortName,
|
|
3310
|
+
selectedPlatform,
|
|
3311
|
+
upstreamHeaders
|
|
3312
|
+
);
|
|
3313
|
+
spinner.stop(true, "Proxy config created!");
|
|
3314
|
+
created = true;
|
|
3315
|
+
} catch (error) {
|
|
3316
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3317
|
+
spinner.stop(false, detail);
|
|
3318
|
+
const retry = await ask("Try again? (Y/n) ");
|
|
3319
|
+
if (retry.trim().toLowerCase() === "n") {
|
|
3320
|
+
break;
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
if (created && proxyUrl.length > 0) {
|
|
3325
|
+
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
3326
|
+
process.stderr.write(
|
|
3327
|
+
" " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
|
|
3328
|
+
);
|
|
3329
|
+
await applyHostedProxyMcpConfig(
|
|
3330
|
+
selectedPlatform,
|
|
3331
|
+
proxyUrl,
|
|
3332
|
+
shortName,
|
|
3333
|
+
apiKey,
|
|
3334
|
+
initWorkspacePath
|
|
3335
|
+
);
|
|
3336
|
+
process.stderr.write(
|
|
3337
|
+
"\n" + style.dim(
|
|
3338
|
+
"Add the TOML snippet above to ~/.codex/config.toml. Set the MULTICORN_API_KEY environment variable to your Shield API key. Restart Codex CLI after saving."
|
|
3339
|
+
) + "\n"
|
|
3340
|
+
);
|
|
3341
|
+
configuredAgents.push({
|
|
3342
|
+
selection,
|
|
3343
|
+
platform: selectedPlatform,
|
|
3344
|
+
platformLabel: selectedLabel,
|
|
3345
|
+
agentName,
|
|
3346
|
+
shortName,
|
|
3347
|
+
proxyUrl,
|
|
3348
|
+
codexCliIntegration: "hosted"
|
|
3349
|
+
});
|
|
3350
|
+
setupSucceeded = true;
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3145
3353
|
} else if (selectedPlatform === "cline") {
|
|
3146
3354
|
const clineMode = await promptClineIntegrationMode(ask);
|
|
3147
3355
|
if (clineMode === "native") {
|
|
@@ -3454,6 +3662,22 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3454
3662
|
"\n" + style.bold("OpenCode (hosted)") + '\n \u2192 Restart OpenCode or start a new session after saving opencode.json\n \u2192 Try it: paste this into OpenCode:\n "Use the ' + ocLabel + ' MCP server to list allowed directories"\n'
|
|
3455
3663
|
);
|
|
3456
3664
|
}
|
|
3665
|
+
const codexNativeConfigured = configuredAgents.some(
|
|
3666
|
+
(a) => a.platform === "codex-cli" && a.codexCliIntegration === "native"
|
|
3667
|
+
);
|
|
3668
|
+
const codexHostedConfigured = configuredAgents.some(
|
|
3669
|
+
(a) => a.platform === "codex-cli" && a.codexCliIntegration === "hosted"
|
|
3670
|
+
);
|
|
3671
|
+
if (codexNativeConfigured) {
|
|
3672
|
+
blocks.push(
|
|
3673
|
+
"\n" + style.bold("Codex CLI (native)") + "\n \u2192 Start Codex CLI (run 'codex' in your terminal)\n \u2192 Type /hooks to review the Shield hooks - press 't' to trust each one\n \u2192 Once both hooks are trusted, try a Bash command - Shield will intercept and check permissions\n \u2192 Note: hooks currently cover terminal (Bash) commands only\n"
|
|
3674
|
+
);
|
|
3675
|
+
}
|
|
3676
|
+
if (codexHostedConfigured) {
|
|
3677
|
+
blocks.push(
|
|
3678
|
+
"\n" + style.bold("Codex CLI (hosted)") + "\n \u2192 Set the MULTICORN_API_KEY environment variable to your Shield API key\n \u2192 Restart Codex CLI after saving config.toml\n \u2192 Try it: make a request that uses an MCP tool through Shield\n"
|
|
3679
|
+
);
|
|
3680
|
+
}
|
|
3457
3681
|
if (configuredPlatforms.has("other-mcp")) {
|
|
3458
3682
|
blocks.push(
|
|
3459
3683
|
"\n" + style.bold("Local MCP / Other") + "\n \u2192 Run your configured wrap command (for example " + style.cyan("npx multicorn-shield --wrap ...") + ")\n \u2192 Try it: make a request in your coding agent - Shield will intercept the first tool call and ask for your consent\n"
|
|
@@ -4359,7 +4583,7 @@ async function restoreClaudeDesktopMcpFromBackup() {
|
|
|
4359
4583
|
|
|
4360
4584
|
// package.json
|
|
4361
4585
|
var package_default = {
|
|
4362
|
-
version: "1.
|
|
4586
|
+
version: "1.9.0"};
|
|
4363
4587
|
|
|
4364
4588
|
// src/package-meta.ts
|
|
4365
4589
|
var PACKAGE_VERSION = package_default.version;
|
package/dist/shield-extension.js
CHANGED
|
@@ -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.
|
|
22520
|
+
version: "1.9.0"};
|
|
22515
22521
|
|
|
22516
22522
|
// src/package-meta.ts
|
|
22517
22523
|
var PACKAGE_VERSION = package_default.version;
|
package/package.json
CHANGED