@ory/claude-code 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
|
@@ -120,6 +120,8 @@ The plugin is **fail-open** on its own infrastructure failures (network errors,
|
|
|
120
120
|
|
|
121
121
|
### Enable enforcement
|
|
122
122
|
|
|
123
|
+
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.
|
|
124
|
+
|
|
123
125
|
1. **Turn on the user gate.** In your shell:
|
|
124
126
|
|
|
125
127
|
```bash
|
|
@@ -128,19 +130,29 @@ The plugin is **fail-open** on its own infrastructure failures (network errors,
|
|
|
128
130
|
|
|
129
131
|
The next Claude session opens a browser for PKCE login. Subsequent sessions reuse the persisted token until it expires.
|
|
130
132
|
|
|
131
|
-
2. **
|
|
133
|
+
2. **Bootstrap tuples for the built-in tools.** One idempotent command grants the current user `use` on every tool Claude ships with (Read, Write, Bash, …):
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx -y -p @ory/claude-code ory-claude permissions bootstrap
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
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.
|
|
140
|
+
|
|
141
|
+
3. **Check coverage.** `permissions status` probes every tool in the harness's catalog and prints allowed / denied per tool:
|
|
132
142
|
|
|
133
143
|
```bash
|
|
134
|
-
ory
|
|
135
|
-
--namespace AgentTools \
|
|
136
|
-
--object Bash \
|
|
137
|
-
--relation invoke \
|
|
138
|
-
--subject-id <your-user-subject-id>
|
|
144
|
+
npx -y -p @ory/claude-code ory-claude permissions status
|
|
139
145
|
```
|
|
140
146
|
|
|
141
|
-
|
|
147
|
+
Add tuples for any MCP server tools or custom commands by hand, or via the Ory MCP server from inside Claude (*"grant me use on the Bash tool"*).
|
|
148
|
+
|
|
149
|
+
4. **Promote to enforce.** Once the observe-mode logs look right, switch over:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npx -y -p @ory/claude-code ory-claude permissions enforce
|
|
153
|
+
```
|
|
142
154
|
|
|
143
|
-
|
|
155
|
+
Denies now block the tool call; Claude 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`.
|
|
144
156
|
|
|
145
157
|
## CLI reference
|
|
146
158
|
|
|
@@ -148,6 +160,7 @@ The plugin is **fail-open** on its own infrastructure failures (network errors,
|
|
|
148
160
|
npx -y -p @ory/claude-code ory-claude install | uninstall [--global]
|
|
149
161
|
npx -y -p @ory/claude-code ory-claude configure [--project-url <url>] [--api-key <key>] [--audit-only]
|
|
150
162
|
npx -y -p @ory/claude-code ory-claude agent <status|unregister> Manage the agent's OAuth2 identity
|
|
163
|
+
npx -y -p @ory/claude-code ory-claude permissions <status|bootstrap|observe|enforce>
|
|
151
164
|
npx -y -p @ory/claude-code ory-claude local <up|down|status|seed|logs|env|configure|reset>
|
|
152
165
|
npx -y -p @ory/claude-code ory-claude status
|
|
153
166
|
```
|
|
@@ -155,7 +168,8 @@ npx -y -p @ory/claude-code ory-claude status
|
|
|
155
168
|
Highlights:
|
|
156
169
|
|
|
157
170
|
- `agent status` — show the current persisted DCR identity for the agent.
|
|
158
|
-
- `
|
|
171
|
+
- `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.
|
|
172
|
+
- `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`.
|
|
159
173
|
- `local seed` / `local env` — reseed the test user, or print env vars for pointing other tools at the local stack.
|
|
160
174
|
|
|
161
175
|
## Troubleshooting
|
package/dist/cli/main.js
CHANGED
|
@@ -55,6 +55,10 @@ function main() {
|
|
|
55
55
|
switch (command) {
|
|
56
56
|
case "install":
|
|
57
57
|
(0, setup_js_1.install)(args);
|
|
58
|
+
postInstallPermissions("ory-claude", "claude-code").then(() => process.exit(0), (err) => {
|
|
59
|
+
console.error(err.message ?? err);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
58
62
|
break;
|
|
59
63
|
case "uninstall":
|
|
60
64
|
(0, setup_js_1.uninstall)(args);
|
|
@@ -68,6 +72,12 @@ function main() {
|
|
|
68
72
|
process.exit(1);
|
|
69
73
|
});
|
|
70
74
|
break;
|
|
75
|
+
case "permissions":
|
|
76
|
+
(0, argus_1.runPermissionsCommand)("ory-claude", "claude-code", args).then((code) => process.exit(code), (err) => {
|
|
77
|
+
console.error(err.message ?? err);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
});
|
|
80
|
+
break;
|
|
71
81
|
case "status":
|
|
72
82
|
status();
|
|
73
83
|
break;
|
|
@@ -89,6 +99,12 @@ function main() {
|
|
|
89
99
|
process.exit(1);
|
|
90
100
|
}
|
|
91
101
|
}
|
|
102
|
+
async function postInstallPermissions(binName, harness) {
|
|
103
|
+
const bootstrapped = await (0, argus_1.maybeAutoBootstrap)(binName, harness);
|
|
104
|
+
(0, argus_1.printPermissionsOnboardingHelp)(binName, harness, {
|
|
105
|
+
bootstrappedAutomatically: bootstrapped,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
92
108
|
function status() {
|
|
93
109
|
console.log("Ory Agent Plugin Status (Claude Code)");
|
|
94
110
|
console.log("======================================");
|
|
@@ -116,6 +132,7 @@ Commands:
|
|
|
116
132
|
install [--global] Install Ory plugin via claude CLI marketplace
|
|
117
133
|
uninstall [--global] Remove Ory plugin and marketplace
|
|
118
134
|
configure Set or view Ory project URL and API key
|
|
135
|
+
permissions <cmd> Manage permission mode and tool tuples (status, bootstrap, observe, enforce)
|
|
119
136
|
status Show plugin status and configuration
|
|
120
137
|
local <cmd> Manage local Ory dev environment (up, down, status, seed, ...)
|
|
121
138
|
|
package/dist/handlers.js
CHANGED
|
@@ -188,40 +188,65 @@ async function handlePreToolUse(input, client, deps = {}) {
|
|
|
188
188
|
subject,
|
|
189
189
|
spanAttributes: { toolName },
|
|
190
190
|
});
|
|
191
|
-
|
|
191
|
+
const mcpAttrs = {
|
|
192
|
+
toolName,
|
|
193
|
+
mcpServer: mcpTool.serverName,
|
|
194
|
+
mcpTool: mcpTool.toolName,
|
|
195
|
+
...inputSummary,
|
|
196
|
+
};
|
|
197
|
+
const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
|
|
198
|
+
object: mcpTool.serverName,
|
|
199
|
+
relation: "use",
|
|
200
|
+
subjectId,
|
|
201
|
+
spanAttributes: mcpAttrs,
|
|
202
|
+
});
|
|
203
|
+
if (decision.kind === "allow") {
|
|
204
|
+
client.tracer.record("tool.invoke", "ok", { attributes: mcpAttrs });
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
if (decision.kind === "observe") {
|
|
192
208
|
client.tracer.record("tool.block", "denied", {
|
|
193
|
-
attributes: {
|
|
209
|
+
attributes: { ...mcpAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
|
|
210
|
+
});
|
|
211
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
212
|
+
attributes: { ...mcpAttrs, allowed: false, observed: true },
|
|
194
213
|
});
|
|
195
|
-
return {
|
|
196
|
-
decision: "block",
|
|
197
|
-
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }),
|
|
198
|
-
};
|
|
214
|
+
return {};
|
|
199
215
|
}
|
|
200
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
201
|
-
attributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName, ...inputSummary },
|
|
202
|
-
});
|
|
203
|
-
return {};
|
|
204
|
-
}
|
|
205
|
-
const namespace = resolveNamespace();
|
|
206
|
-
const result = await client.checkPermission({
|
|
207
|
-
namespace,
|
|
208
|
-
object: toolName,
|
|
209
|
-
relation: "use",
|
|
210
|
-
...subject,
|
|
211
|
-
}, { spanAttributes: { toolName } });
|
|
212
|
-
if (!result.allowed) {
|
|
213
216
|
client.tracer.record("tool.block", "denied", {
|
|
214
|
-
attributes: {
|
|
217
|
+
attributes: { ...mcpAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
|
|
215
218
|
});
|
|
216
219
|
return {
|
|
217
220
|
decision: "block",
|
|
218
|
-
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId,
|
|
221
|
+
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }),
|
|
219
222
|
};
|
|
220
223
|
}
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
const namespace = resolveNamespace();
|
|
225
|
+
const decision = await (0, argus_1.checkAndDecide)(client, { namespace, object: toolName, relation: "use", ...subject }, { spanAttributes: { toolName } });
|
|
226
|
+
if (decision.kind === "fail_open") {
|
|
227
|
+
return handlePermissionError(decision.error, toolName, client);
|
|
228
|
+
}
|
|
229
|
+
const attrs = { toolName, ...inputSummary };
|
|
230
|
+
if (decision.kind === "allow") {
|
|
231
|
+
client.tracer.record("tool.invoke", "ok", { attributes: { ...attrs, allowed: true } });
|
|
232
|
+
return {};
|
|
233
|
+
}
|
|
234
|
+
if (decision.kind === "observe") {
|
|
235
|
+
client.tracer.record("tool.block", "denied", {
|
|
236
|
+
attributes: { ...attrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
|
|
237
|
+
});
|
|
238
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
239
|
+
attributes: { ...attrs, allowed: false, observed: true },
|
|
240
|
+
});
|
|
241
|
+
return {};
|
|
242
|
+
}
|
|
243
|
+
client.tracer.record("tool.block", "denied", {
|
|
244
|
+
attributes: { ...attrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
|
|
223
245
|
});
|
|
224
|
-
return {
|
|
246
|
+
return {
|
|
247
|
+
decision: "block",
|
|
248
|
+
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }),
|
|
249
|
+
};
|
|
225
250
|
}
|
|
226
251
|
catch (err) {
|
|
227
252
|
return handlePermissionError(err, toolName, client);
|
|
@@ -354,20 +379,34 @@ async function handlePermissionRequest(input, client) {
|
|
|
354
379
|
subject,
|
|
355
380
|
spanAttributes: { toolName },
|
|
356
381
|
});
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
:
|
|
382
|
+
const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
|
|
383
|
+
object: mcpTool.serverName,
|
|
384
|
+
relation: "use",
|
|
385
|
+
subjectId,
|
|
386
|
+
spanAttributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName },
|
|
387
|
+
});
|
|
388
|
+
if (decision.kind === "deny") {
|
|
389
|
+
return permissionRequestDecision("deny", (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }));
|
|
390
|
+
}
|
|
391
|
+
return permissionRequestDecision("allow", decision.kind === "observe"
|
|
392
|
+
? "Ory observe mode — denied by policy but allowed through"
|
|
393
|
+
: "Ory MCP server permission granted");
|
|
360
394
|
}
|
|
361
395
|
const namespace = resolveNamespace();
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
396
|
+
const decision = await (0, argus_1.checkAndDecide)(client, { namespace, object: toolName, relation: "use", ...subject }, { spanAttributes: { toolName } });
|
|
397
|
+
if (decision.kind === "fail_open") {
|
|
398
|
+
const code = decision.error.code;
|
|
399
|
+
const fallbackReason = code === "network_error" || code === "rate_limited"
|
|
400
|
+
? `Ory unavailable (${code}), falling back to user prompt`
|
|
401
|
+
: `Ory permission check failed: ${decision.error.message}`;
|
|
402
|
+
return permissionRequestFallback(fallbackReason);
|
|
403
|
+
}
|
|
404
|
+
if (decision.kind === "deny") {
|
|
405
|
+
return permissionRequestDecision("deny", (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }));
|
|
406
|
+
}
|
|
407
|
+
return permissionRequestDecision("allow", decision.kind === "observe"
|
|
408
|
+
? "Ory observe mode — denied by policy but allowed through"
|
|
409
|
+
: "Ory permission granted");
|
|
371
410
|
}
|
|
372
411
|
catch (err) {
|
|
373
412
|
const oryErr = err;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/claude-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Ory plugin for Claude Code: 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",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"!dist/**/*.tsbuildinfo"
|
|
73
73
|
],
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@ory/argus": "0.
|
|
75
|
+
"@ory/argus": "0.2.0"
|
|
76
76
|
},
|
|
77
77
|
"engines": {
|
|
78
78
|
"node": ">=24"
|