@ory/codex 0.1.3 → 0.2.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/README.md +23 -9
- package/dist/cli/main.js +17 -0
- package/dist/handlers.js +75 -36
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -109,6 +109,8 @@ The plugin is **fail-open** on its own infrastructure failures (network errors,
|
|
|
109
109
|
|
|
110
110
|
### Enable enforcement
|
|
111
111
|
|
|
112
|
+
After install the plugin runs in **observe mode**: every tool call is checked against Ory Permissions, but a deny is recorded as a `permission.observe_deny` audit span and the tool runs anyway. This lets you see what *would* be blocked before turning on hard blocking.
|
|
113
|
+
|
|
112
114
|
1. **Turn on the user gate.** In your shell:
|
|
113
115
|
|
|
114
116
|
```bash
|
|
@@ -117,19 +119,29 @@ The plugin is **fail-open** on its own infrastructure failures (network errors,
|
|
|
117
119
|
|
|
118
120
|
The next Codex session opens a browser for PKCE login. Subsequent sessions reuse the persisted token until it expires.
|
|
119
121
|
|
|
120
|
-
2. **
|
|
122
|
+
2. **Bootstrap tuples for the built-in tools.** One idempotent command grants the current user `use` on every tool Codex ships with (shell, apply_patch, …):
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx -y -p @ory/codex ory-codex permissions bootstrap
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If a user identity is already cached at install time, the installer runs this for you automatically — re-run after adding tools, switching subjects, or changing the namespace.
|
|
129
|
+
|
|
130
|
+
3. **Check coverage.** `permissions status` probes every tool in the harness's catalog and prints allowed / denied per tool:
|
|
121
131
|
|
|
122
132
|
```bash
|
|
123
|
-
ory
|
|
124
|
-
--namespace AgentTools \
|
|
125
|
-
--object Bash \
|
|
126
|
-
--relation invoke \
|
|
127
|
-
--subject-id <your-user-subject-id>
|
|
133
|
+
npx -y -p @ory/codex ory-codex permissions status
|
|
128
134
|
```
|
|
129
135
|
|
|
130
|
-
|
|
136
|
+
Add tuples for any MCP server tools or custom commands by hand, or via the Ory MCP server from inside Codex (*"grant me use on the shell tool"*).
|
|
137
|
+
|
|
138
|
+
4. **Promote to enforce.** Once the observe-mode logs look right, switch over:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npx -y -p @ory/codex ory-codex permissions enforce
|
|
142
|
+
```
|
|
131
143
|
|
|
132
|
-
|
|
144
|
+
Denies now block the tool call; Codex shows the denial reason and the decision is recorded as a `tool.block` trace span with `blocked: true`. Switch back any time with `permissions observe`.
|
|
133
145
|
|
|
134
146
|
## CLI reference
|
|
135
147
|
|
|
@@ -137,6 +149,7 @@ The plugin is **fail-open** on its own infrastructure failures (network errors,
|
|
|
137
149
|
npx -y -p @ory/codex ory-codex install | uninstall [--global]
|
|
138
150
|
npx -y -p @ory/codex ory-codex configure [--project-url <url>] [--api-key <key>] [--audit-only]
|
|
139
151
|
npx -y -p @ory/codex ory-codex agent <status|unregister> Manage the agent's OAuth2 identity
|
|
152
|
+
npx -y -p @ory/codex ory-codex permissions <status|bootstrap|observe|enforce>
|
|
140
153
|
npx -y -p @ory/codex ory-codex local <up|down|status|seed|logs|env|configure|reset>
|
|
141
154
|
npx -y -p @ory/codex ory-codex status
|
|
142
155
|
```
|
|
@@ -144,7 +157,8 @@ npx -y -p @ory/codex ory-codex status
|
|
|
144
157
|
Highlights:
|
|
145
158
|
|
|
146
159
|
- `agent status` — show the current persisted DCR identity for the agent.
|
|
147
|
-
- `
|
|
160
|
+
- `permissions observe` / `permissions enforce` — switch between "log denies, allow through" (the install default) and "block denies." `permissions bootstrap` writes `use` tuples for the harness's built-in tools so the promotion path doesn't require hand-writing relationships.
|
|
161
|
+
- `configure --audit-only` — kill switch that disables Ory entirely (no auth, no permission checks; only audit logging of tool invocations). For phased rollouts, prefer `permissions observe` over `--audit-only`.
|
|
148
162
|
- `local seed` / `local env` — reseed the test user, or print env vars for pointing other tools at the local stack.
|
|
149
163
|
|
|
150
164
|
## Troubleshooting
|
package/dist/cli/main.js
CHANGED
|
@@ -53,6 +53,10 @@ function main() {
|
|
|
53
53
|
switch (command) {
|
|
54
54
|
case "install":
|
|
55
55
|
install(args);
|
|
56
|
+
postInstallPermissions("ory-codex", "codex").then(() => process.exit(0), (err) => {
|
|
57
|
+
console.error(err.message ?? err);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
|
56
60
|
break;
|
|
57
61
|
case "uninstall":
|
|
58
62
|
uninstall(args);
|
|
@@ -66,6 +70,12 @@ function main() {
|
|
|
66
70
|
process.exit(1);
|
|
67
71
|
});
|
|
68
72
|
break;
|
|
73
|
+
case "permissions":
|
|
74
|
+
(0, argus_1.runPermissionsCommand)("ory-codex", "codex", args).then((code) => process.exit(code), (err) => {
|
|
75
|
+
console.error(err.message ?? err);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
});
|
|
78
|
+
break;
|
|
69
79
|
case "setup":
|
|
70
80
|
require("./setup.js");
|
|
71
81
|
break;
|
|
@@ -105,6 +115,12 @@ function manualSetup(isGlobal) {
|
|
|
105
115
|
process.argv = ["node", "setup.js", ...setupArgs];
|
|
106
116
|
require("./setup.js");
|
|
107
117
|
}
|
|
118
|
+
async function postInstallPermissions(binName, harness) {
|
|
119
|
+
const bootstrapped = await (0, argus_1.maybeAutoBootstrap)(binName, harness);
|
|
120
|
+
(0, argus_1.printPermissionsOnboardingHelp)(binName, harness, {
|
|
121
|
+
bootstrappedAutomatically: bootstrapped,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
108
124
|
function status() {
|
|
109
125
|
console.log("Ory Agent Plugin Status (Codex)");
|
|
110
126
|
console.log("================================");
|
|
@@ -129,6 +145,7 @@ Commands:
|
|
|
129
145
|
install [--global] Install the Ory hooks into Codex configuration
|
|
130
146
|
uninstall Remove the Ory hooks from Codex configuration
|
|
131
147
|
configure Set or view Ory project URL and API key
|
|
148
|
+
permissions <cmd> Manage permission mode and tool tuples (status, bootstrap, observe, enforce)
|
|
132
149
|
setup [--global] Write hooks directly to config (fallback)
|
|
133
150
|
status Show plugin status and configuration
|
|
134
151
|
local <cmd> Manage local Ory dev environment (up, down, status, seed, ...)
|
package/dist/handlers.js
CHANGED
|
@@ -180,40 +180,65 @@ async function handlePreToolUse(input, client) {
|
|
|
180
180
|
subject,
|
|
181
181
|
spanAttributes: { toolName },
|
|
182
182
|
});
|
|
183
|
-
|
|
183
|
+
const mcpAttrs = {
|
|
184
|
+
toolName,
|
|
185
|
+
mcpServer: mcpTool.serverName,
|
|
186
|
+
mcpTool: mcpTool.toolName,
|
|
187
|
+
...inputSummary,
|
|
188
|
+
};
|
|
189
|
+
const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
|
|
190
|
+
object: mcpTool.serverName,
|
|
191
|
+
relation: "use",
|
|
192
|
+
subjectId,
|
|
193
|
+
spanAttributes: mcpAttrs,
|
|
194
|
+
});
|
|
195
|
+
if (decision.kind === "allow") {
|
|
196
|
+
client.tracer.record("tool.invoke", "ok", { attributes: mcpAttrs });
|
|
197
|
+
return {};
|
|
198
|
+
}
|
|
199
|
+
if (decision.kind === "observe") {
|
|
184
200
|
client.tracer.record("tool.block", "denied", {
|
|
185
|
-
attributes: {
|
|
201
|
+
attributes: { ...mcpAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
|
|
186
202
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
};
|
|
203
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
204
|
+
attributes: { ...mcpAttrs, allowed: false, observed: true },
|
|
205
|
+
});
|
|
206
|
+
return {};
|
|
191
207
|
}
|
|
192
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
193
|
-
attributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName, ...inputSummary },
|
|
194
|
-
});
|
|
195
|
-
return {};
|
|
196
|
-
}
|
|
197
|
-
const namespace = resolveNamespace();
|
|
198
|
-
const result = await client.checkPermission({
|
|
199
|
-
namespace,
|
|
200
|
-
object: toolName,
|
|
201
|
-
relation: "use",
|
|
202
|
-
...subject,
|
|
203
|
-
}, { spanAttributes: { toolName } });
|
|
204
|
-
if (!result.allowed) {
|
|
205
208
|
client.tracer.record("tool.block", "denied", {
|
|
206
|
-
attributes: {
|
|
209
|
+
attributes: { ...mcpAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
|
|
207
210
|
});
|
|
208
211
|
return {
|
|
209
212
|
decision: "block",
|
|
210
|
-
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId,
|
|
213
|
+
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }),
|
|
211
214
|
};
|
|
212
215
|
}
|
|
213
|
-
|
|
214
|
-
|
|
216
|
+
const namespace = resolveNamespace();
|
|
217
|
+
const decision = await (0, argus_1.checkAndDecide)(client, { namespace, object: toolName, relation: "use", ...subject }, { spanAttributes: { toolName } });
|
|
218
|
+
if (decision.kind === "fail_open") {
|
|
219
|
+
return handlePermissionError(decision.error, toolName, client);
|
|
220
|
+
}
|
|
221
|
+
const attrs = { toolName, ...inputSummary };
|
|
222
|
+
if (decision.kind === "allow") {
|
|
223
|
+
client.tracer.record("tool.invoke", "ok", { attributes: { ...attrs, allowed: true } });
|
|
224
|
+
return {};
|
|
225
|
+
}
|
|
226
|
+
if (decision.kind === "observe") {
|
|
227
|
+
client.tracer.record("tool.block", "denied", {
|
|
228
|
+
attributes: { ...attrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
|
|
229
|
+
});
|
|
230
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
231
|
+
attributes: { ...attrs, allowed: false, observed: true },
|
|
232
|
+
});
|
|
233
|
+
return {};
|
|
234
|
+
}
|
|
235
|
+
client.tracer.record("tool.block", "denied", {
|
|
236
|
+
attributes: { ...attrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
|
|
215
237
|
});
|
|
216
|
-
return {
|
|
238
|
+
return {
|
|
239
|
+
decision: "block",
|
|
240
|
+
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }),
|
|
241
|
+
};
|
|
217
242
|
}
|
|
218
243
|
catch (err) {
|
|
219
244
|
return handlePermissionError(err, toolName, client);
|
|
@@ -271,20 +296,34 @@ async function handlePermissionRequest(input, client) {
|
|
|
271
296
|
subject,
|
|
272
297
|
spanAttributes: { toolName },
|
|
273
298
|
});
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
:
|
|
299
|
+
const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
|
|
300
|
+
object: mcpTool.serverName,
|
|
301
|
+
relation: "use",
|
|
302
|
+
subjectId,
|
|
303
|
+
spanAttributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName },
|
|
304
|
+
});
|
|
305
|
+
if (decision.kind === "deny") {
|
|
306
|
+
return permissionRequestDecision("deny", (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }));
|
|
307
|
+
}
|
|
308
|
+
return permissionRequestDecision("allow", decision.kind === "observe"
|
|
309
|
+
? "Ory observe mode — denied by policy but allowed through"
|
|
310
|
+
: "Ory MCP server permission granted");
|
|
277
311
|
}
|
|
278
312
|
const namespace = resolveNamespace();
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
313
|
+
const decision = await (0, argus_1.checkAndDecide)(client, { namespace, object: toolName, relation: "use", ...subject }, { spanAttributes: { toolName } });
|
|
314
|
+
if (decision.kind === "fail_open") {
|
|
315
|
+
const code = decision.error.code;
|
|
316
|
+
const reason = code === "network_error" || code === "rate_limited"
|
|
317
|
+
? `Ory unavailable (${code}), falling back to user prompt`
|
|
318
|
+
: `Ory permission check failed: ${decision.error.message}`;
|
|
319
|
+
return permissionRequestFallback(reason);
|
|
320
|
+
}
|
|
321
|
+
if (decision.kind === "deny") {
|
|
322
|
+
return permissionRequestDecision("deny", (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }));
|
|
323
|
+
}
|
|
324
|
+
return permissionRequestDecision("allow", decision.kind === "observe"
|
|
325
|
+
? "Ory observe mode — denied by policy but allowed through"
|
|
326
|
+
: "Ory permission granted");
|
|
288
327
|
}
|
|
289
328
|
catch (err) {
|
|
290
329
|
const oryErr = err;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/codex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Ory plugin for Codex: scaffolding skills, a local Ory instance, and authentication, authorization, and audit for every tool call",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ory.com",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
".codex-plugin"
|
|
68
68
|
],
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@ory/argus": "0.
|
|
70
|
+
"@ory/argus": "0.2.0"
|
|
71
71
|
},
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=24"
|