@ory/argus 1.0.1 → 1.1.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/assets/skills/ory-build-agent/SKILL.md +5 -4
- package/assets/skills/ory-temporal-worker/SKILL.md +9 -9
- package/assets/skills/permissions-onboarding/SKILL.md +66 -142
- package/dist/adapters.js +1 -1
- package/dist/bash-parser.js +13 -1
- package/dist/build-info.json +4 -4
- package/dist/cli.js +1 -1
- package/dist/contract-suite.js +5 -2
- package/dist/local/manager.js +1 -1
- package/dist/local/seed.d.ts +1 -1
- package/dist/local/seed.js +4 -62
- package/dist/opl.d.ts +11 -18
- package/dist/opl.js +21 -36
- package/dist/permissions-cli.d.ts +2 -2
- package/dist/permissions-cli.js +15 -32
- package/dist/permissions.d.ts +7 -10
- package/dist/permissions.js +16 -28
- package/dist/post-install.d.ts +1 -1
- package/dist/post-install.js +2 -2
- package/dist/setup.js +1 -1
- package/dist/status-cli.d.ts +1 -1
- package/dist/status-cli.js +12 -12
- package/dist/status-data.d.ts +8 -5
- package/dist/status-data.js +12 -11
- package/package.json +1 -1
|
@@ -361,7 +361,8 @@ PKCE flow, permission tuples, and activity events are all visible:
|
|
|
361
361
|
`jq` — you
|
|
362
362
|
should see `user.auth` → `agent.auth` → `permission.check` →
|
|
363
363
|
`tool.complete` for every call.
|
|
364
|
-
5.
|
|
364
|
+
5. Native tools are allowed unless explicitly blocked. Promote to enforce once
|
|
365
|
+
the block policy is ready. The posture is a
|
|
365
366
|
permission on the Ory project, read on every session — an admin sets it
|
|
366
367
|
in the Ory Console (Agent Security); see the resolved value with
|
|
367
368
|
`... permissions` via any harness CLI (same shared config file).
|
|
@@ -376,9 +377,9 @@ For full env-var coverage (including the user/agent split,
|
|
|
376
377
|
- It does not generate the agent. The user owns the agent loop, tool
|
|
377
378
|
catalog, and deployment shape. This skill only drops `@ory/argus` into
|
|
378
379
|
whatever they already have.
|
|
379
|
-
- It does not
|
|
380
|
-
(Agent Security); `@ory/argus` only reads
|
|
381
|
-
seed
|
|
380
|
+
- It does not write permissions. Native-tool blocks and MCP grants are
|
|
381
|
+
provisioned in the Ory Console (Agent Security); `@ory/argus` only reads
|
|
382
|
+
them. The local seed writes no baseline native-tool grants.
|
|
382
383
|
- It does not adapt one of the existing harness plugins (`@ory/claude-code`,
|
|
383
384
|
`@ory/codex`, `@ory/gemini-cli`, `@ory/openclaw`, `@ory/opencode`). Those
|
|
384
385
|
are for users running those harnesses — not building a custom agent.
|
|
@@ -244,19 +244,19 @@ The Workflow itself produces no Ory activity — only its Activities do.
|
|
|
244
244
|
|
|
245
245
|
## Step 6 — Promotion from observe to enforce
|
|
246
246
|
|
|
247
|
-
The worker starts in `observe` mode by default:
|
|
248
|
-
recorded as a `permission.
|
|
249
|
-
the
|
|
247
|
+
The worker starts in `observe` mode by default: explicit blocks pass through but
|
|
248
|
+
each is recorded as a `permission.block_observed` activity event. Once the user
|
|
249
|
+
has confirmed the block set is what they expect, an admin promotes the posture to `enforce` in
|
|
250
250
|
the Ory Console (Agent Security). It is a permission on the project, read on
|
|
251
251
|
every session, so the worker picks it up on its next run with no redeploy.
|
|
252
252
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
253
|
+
Activities are allowed by default. Add `blockedSubjects` relations for Activity
|
|
254
|
+
names that must not run, then review them before promoting the posture. See
|
|
255
|
+
{{REF_PERMISSIONS_ONBOARDING}}.
|
|
256
256
|
|
|
257
|
-
To exercise the deny path locally,
|
|
258
|
-
|
|
259
|
-
|
|
257
|
+
To exercise the deny path locally, add a `blockedSubjects` relation for an
|
|
258
|
+
Activity name, kick the Workflow, and watch the explicit-block error in the
|
|
259
|
+
Temporal Web UI.
|
|
260
260
|
|
|
261
261
|
## Step 7 — Beyond the dev server
|
|
262
262
|
|
|
@@ -1,195 +1,119 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ory-permissions-onboarding
|
|
3
|
-
description: Onboard
|
|
3
|
+
description: Onboard native agent-tool authorization with default allow and explicit deny overrides. Covers connecting the plugin, reviewing allowed and blocked tools, writing blocks in the Ory Console, and promoting the project from observe to enforce. MCP and PermissionMode remain grant-based.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Onboard
|
|
6
|
+
# Onboard Ory Permissions for Agent Tool Calls
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
first-run failure mode of getting every tool call blocked because the
|
|
11
|
-
project has no grants yet.
|
|
8
|
+
Help the user connect Agent Security and roll out explicit native-tool blocks
|
|
9
|
+
without treating missing grants as denials.
|
|
12
10
|
|
|
13
|
-
**The plugin only reads permissions. It never writes them.**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Console** (Agent Security) by someone with access to the project. That
|
|
17
|
-
split is deliberate: it means installing and connecting a plugin needs
|
|
18
|
-
nothing but project connection details and a public OAuth2 client id, with no
|
|
19
|
-
workspace privilege and no project API key.
|
|
11
|
+
**The plugin only reads permissions. It never writes them.** The permission
|
|
12
|
+
model, native-tool blocks, MCP grants, and observe/enforce posture are managed
|
|
13
|
+
in the **Ory Console** (Agent Security).
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
Native policy is simple:
|
|
22
16
|
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
the MCP server work either way — they don't need a project at all.
|
|
29
|
-
- **`permissionMode`** — `observe` or `enforce`, **read from the Ory
|
|
30
|
-
project** on every session, and only meaningful once connected.
|
|
31
|
-
`observe` (the default) runs every check, records denials, and lets the
|
|
32
|
-
tool proceed. `enforce` blocks on deny. There is no local override — an
|
|
33
|
-
admin sets it in the Console.
|
|
17
|
+
- `AgentTool` and `ShellTool` have no `users` relation.
|
|
18
|
+
- Their `use` permit is `!blockedSubjects.includes(subject)`.
|
|
19
|
+
- No matching block means allowed; a matching block means denied.
|
|
20
|
+
- `observe` logs the block and proceeds; `enforce` vetoes the call.
|
|
21
|
+
- MCP permissions and `PermissionMode#enforcedSubjects` remain grant-based.
|
|
34
22
|
|
|
35
|
-
|
|
36
|
-
enforce in the Console.**
|
|
23
|
+
## 1. Connect Agent Security
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
A fresh install has the developer-experience half working and Agent
|
|
41
|
-
Security not connected. Copy the connection details shown in the Ory Console
|
|
42
|
-
under **Agent Security**:
|
|
25
|
+
Copy the connection details shown in the Ory Console under **Agent Security**:
|
|
43
26
|
|
|
44
27
|
```sh
|
|
45
28
|
{{NPX}} configure --project-url <URL> --agent-security-url <URL>
|
|
46
29
|
```
|
|
47
30
|
|
|
48
|
-
|
|
49
|
-
- **Agent Security URL** — the canonical broker origin (`https://agents.console.ory.com` in production).
|
|
50
|
-
- **OAuth2 client id** — the project's public login client, provisioned
|
|
51
|
-
in the Console. The per-session user login needs it to complete its
|
|
52
|
-
PKCE browser flow.
|
|
53
|
-
|
|
54
|
-
Then confirm what the plugin resolved:
|
|
31
|
+
Then inspect the resolved configuration:
|
|
55
32
|
|
|
56
33
|
```sh
|
|
57
34
|
{{NPX}} status
|
|
58
35
|
```
|
|
59
36
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- **Project URL**: a real Ory URL or your local dev gateway (not "NOT SET").
|
|
63
|
-
- **OAuth2 Client**: the project's public login client (not "NOT SET").
|
|
64
|
-
- **Agent Security**: connected. If it says "not connected", it names the
|
|
65
|
-
unresolved connection value.
|
|
66
|
-
|
|
67
|
-
If the user doesn't have those values, they need them from whoever
|
|
68
|
-
administers the project. Don't try to create the client from here — the
|
|
69
|
-
plugin has no privilege to, by design.
|
|
37
|
+
Confirm that Agent Security is connected and the public OAuth2 login client is
|
|
38
|
+
resolved. The plugin needs no workspace privilege or project API key.
|
|
70
39
|
|
|
71
|
-
##
|
|
40
|
+
## 2. Review Native Tool Access
|
|
72
41
|
|
|
73
42
|
```sh
|
|
74
43
|
{{NPX}} permissions
|
|
75
44
|
```
|
|
76
45
|
|
|
77
|
-
|
|
46
|
+
The report shows:
|
|
78
47
|
|
|
79
|
-
- The live
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
blocked / errored**, from a real check against the project right now.
|
|
84
|
-
- A one-line coverage summary for decomposed shell command words.
|
|
48
|
+
- The live permission mode and its source.
|
|
49
|
+
- The user subject being checked.
|
|
50
|
+
- Each built-in native tool as `allowed`, `blocked`, or `errored`.
|
|
51
|
+
- A compact allowed/blocked summary for decomposed shell command words.
|
|
85
52
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
be blocked under enforce.
|
|
53
|
+
A fresh project normally reports native tools as **allowed**. There is no grant
|
|
54
|
+
coverage step and the local seed writes no baseline `AgentTool` or `ShellTool`
|
|
55
|
+
grants.
|
|
90
56
|
|
|
91
|
-
|
|
92
|
-
**are** being blocked right now. The fix is in the Console (grant the
|
|
93
|
-
tools, or move the project back to observe) — not from the CLI.
|
|
57
|
+
## 3. Add Explicit Blocks in the Console
|
|
94
58
|
|
|
95
|
-
|
|
59
|
+
To block a native tool, write a `blockedSubjects` relation in the Ory Console:
|
|
96
60
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
{{NPX}} watch # live event, decision, and error view
|
|
61
|
+
```text
|
|
62
|
+
namespace: AgentTool
|
|
63
|
+
object: <tool name> e.g. WebFetch
|
|
64
|
+
relation: blockedSubjects
|
|
65
|
+
subject: User:<id>
|
|
103
66
|
```
|
|
104
67
|
|
|
105
|
-
|
|
106
|
-
the watched log and stderr. Denials appear as `permission.observe_deny` activity events (and
|
|
107
|
-
`permission.block_observed` for an explicit block), with the tool name
|
|
108
|
-
and the subject. That list is exactly what has to be granted before
|
|
109
|
-
enforce is safe.
|
|
110
|
-
|
|
111
|
-
## Step 4: Grant the tools in the Ory Console
|
|
68
|
+
To block a command word reached through a shell tool:
|
|
112
69
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
object: <tool name> (e.g. Read, Bash, WebFetch)
|
|
119
|
-
relation: use
|
|
120
|
-
subject: User:<id> (a SubjectSet — namespace `User`, object the user id)
|
|
70
|
+
```text
|
|
71
|
+
namespace: ShellTool
|
|
72
|
+
object: <command word> e.g. curl
|
|
73
|
+
relation: blockedSubjects
|
|
74
|
+
subject: User:<id>
|
|
121
75
|
```
|
|
122
76
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
Console's *Add relationship* dialog emits and exactly what the runtime
|
|
127
|
-
check uses. A bare subject id will not match.
|
|
128
|
-
- Granting a **group** and putting users in it scales better than
|
|
129
|
-
per-user grants; the check follows subject-set expansion.
|
|
130
|
-
- **Shell command words** are authorized separately in the `ShellTool`
|
|
131
|
-
namespace (`ShellTool:curl#use`, …) because a denied surface is
|
|
132
|
-
otherwise reachable through `bash -c "curl …"`.
|
|
133
|
-
- **MCP server tools** are discovered dynamically per session, so they
|
|
134
|
-
are not in the built-in catalog and need granting as they come into
|
|
135
|
-
scope.
|
|
136
|
-
|
|
137
|
-
Re-run `{{NPX}} permissions` afterwards to confirm coverage.
|
|
77
|
+
Blocks can also target the project, an agent credential, an agent session, a
|
|
78
|
+
sub-agent credential, or a sub-agent spawn. Group/SubjectSet expansion is
|
|
79
|
+
supported. A native deny is always an explicit block, never a missing grant.
|
|
138
80
|
|
|
139
|
-
|
|
81
|
+
MCP is intentionally different: continue granting MCP server/tool access with
|
|
82
|
+
its existing `users`/`use` and `invoke` relations.
|
|
140
83
|
|
|
141
|
-
|
|
142
|
-
the posture in the Console (Agent Security). The mode is a permission on
|
|
143
|
-
the project, so it can be set **project-wide** — covering principals
|
|
144
|
-
that don't exist yet — or **per principal** as an exception.
|
|
84
|
+
## 4. Observe Before Enforcing
|
|
145
85
|
|
|
146
|
-
|
|
147
|
-
reinstalling or reconfiguring anything. Confirm it landed:
|
|
86
|
+
While the project is in `observe`, run normal sessions and inspect:
|
|
148
87
|
|
|
149
88
|
```sh
|
|
150
89
|
{{NPX}} permissions
|
|
90
|
+
{{NPX}} watch
|
|
151
91
|
```
|
|
152
92
|
|
|
153
|
-
|
|
93
|
+
Matching blocks emit `permission.block_observed` but the tool still runs. Review
|
|
94
|
+
those events, then promote the project or selected principals to `enforce` in
|
|
95
|
+
the Ory Console. Every gate reads the project-controlled posture.
|
|
154
96
|
|
|
155
|
-
|
|
97
|
+
## 5. Verify Enforcement
|
|
156
98
|
|
|
157
|
-
|
|
158
|
-
- **Denied tools** are blocked with a clear "Ory: permission denied"
|
|
159
|
-
message naming the tool (or the shell command word).
|
|
160
|
-
- The activity log shows `permission.check` events per tool call and
|
|
161
|
-
`tool.block` events for denials.
|
|
99
|
+
Under `enforce`:
|
|
162
100
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
101
|
+
- Tools without a matching block run normally.
|
|
102
|
+
- Explicitly blocked tools produce a clear "explicitly blocked" message.
|
|
103
|
+
- A blocked `ShellTool` word blocks the outer shell command and names the word.
|
|
104
|
+
- Infrastructure failures retain the core fail-open behavior.
|
|
166
105
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
Granting is additive. To express the opposite — a subject is **blocked**
|
|
170
|
-
from a tool, beating any grant, including one inherited through a group —
|
|
171
|
-
an admin writes a `blockedSubjects` relation in the Console. Ory evaluates it
|
|
172
|
-
natively (the `use` permit is "granted AND not blocked"), so it holds
|
|
173
|
-
no matter how the grant was reached.
|
|
174
|
-
|
|
175
|
-
A block behaves like any other deny: under **observe** it is recorded (a
|
|
176
|
-
`permission.block_observed` activity event) but the tool still runs; under
|
|
177
|
-
**enforce** it blocks. `permissions` marks blocked tools so they
|
|
178
|
-
are distinguishable from a plain missing grant at a glance.
|
|
106
|
+
If a tool is unexpectedly blocked, use `{{NPX}} permissions` to identify the
|
|
107
|
+
block and remove or narrow its `blockedSubjects` relation in the Console.
|
|
179
108
|
|
|
180
109
|
## Reference
|
|
181
110
|
|
|
182
111
|
| Command | Effect |
|
|
183
112
|
|---|---|
|
|
184
|
-
| `{{NPX}} configure --project-url <URL> --agent-security-url <URL>` | Connect
|
|
185
|
-
| `{{NPX}} configure --disconnect` |
|
|
186
|
-
| `{{NPX}} permissions` | Print
|
|
187
|
-
| `{{NPX}} status` |
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
and setting the observe/enforce posture.
|
|
192
|
-
|
|
193
|
-
For deeper background on the authentication side of the flow (which
|
|
194
|
-
identity is the subject, how the user gate resolves it), see
|
|
195
|
-
{{REF_AUTH_SETUP}} and {{REF_LOGIN_FLOW}}.
|
|
113
|
+
| `{{NPX}} configure --project-url <URL> --agent-security-url <URL>` | Connect Agent Security. |
|
|
114
|
+
| `{{NPX}} configure --disconnect` | Disconnect sign-in and permission checks. |
|
|
115
|
+
| `{{NPX}} permissions` | Print live mode plus native allowed/blocked state. Read-only. |
|
|
116
|
+
| `{{NPX}} status` | Print configuration, identities, native tool access, and recent activity. |
|
|
117
|
+
|
|
118
|
+
For authentication and subject-resolution details, see {{REF_AUTH_SETUP}} and
|
|
119
|
+
{{REF_LOGIN_FLOW}}.
|
package/dist/adapters.js
CHANGED
|
@@ -366,7 +366,7 @@ async function decideTool(client, opts) {
|
|
|
366
366
|
const label = (0, subject_js_1.subjectLabel)(subjectRef);
|
|
367
367
|
const canBlock = opts.canBlock ?? true;
|
|
368
368
|
const activityAttrs = gateActivityAttributes(opts);
|
|
369
|
-
const check = { namespace, object: opts.toolName, relation: "
|
|
369
|
+
const check = { namespace, object: opts.toolName, relation: "use", ...subjectRef };
|
|
370
370
|
if (!(0, config_js_1.isSecurityConnected)()) {
|
|
371
371
|
const outcome = {
|
|
372
372
|
kind: "not_connected",
|
package/dist/bash-parser.js
CHANGED
|
@@ -172,6 +172,16 @@ function resolveCommandName(nameNode) {
|
|
|
172
172
|
return basename(child.text);
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
+
function commandNameIsAmbiguous(nameNode, name) {
|
|
176
|
+
const raw = nameNode.text;
|
|
177
|
+
if (!/[\\'"$]/.test(raw))
|
|
178
|
+
return false;
|
|
179
|
+
if (!name)
|
|
180
|
+
return true;
|
|
181
|
+
if (raw === `"${name}"` || raw === `'${name}'`)
|
|
182
|
+
return false;
|
|
183
|
+
return !raw.endsWith(`/${name}"`) && !raw.endsWith(`/${name}'`);
|
|
184
|
+
}
|
|
175
185
|
function argNodes(cmd) {
|
|
176
186
|
return cmd.childrenForFieldName("argument").filter((n) => n !== null);
|
|
177
187
|
}
|
|
@@ -271,7 +281,7 @@ function walk(node, pipeline, parser, state, depth, treeDepth) {
|
|
|
271
281
|
}
|
|
272
282
|
const pid = pipeline >= 0 ? pipeline : state.pipelineCounter++;
|
|
273
283
|
const name = resolveCommandName(nameNode);
|
|
274
|
-
if (name === null)
|
|
284
|
+
if (name === null || commandNameIsAmbiguous(nameNode, name))
|
|
275
285
|
state.tooComplex = true;
|
|
276
286
|
const args = argNodes(node);
|
|
277
287
|
state.commands.push({
|
|
@@ -361,6 +371,8 @@ async function extractShellCommands(command) {
|
|
|
361
371
|
if (!tree)
|
|
362
372
|
return { commands: [], tooComplex: true };
|
|
363
373
|
const state = { commands: [], tooComplex: false, pipelineCounter: 0 };
|
|
374
|
+
if (command.includes("\\\n"))
|
|
375
|
+
state.tooComplex = true;
|
|
364
376
|
if (tree.rootNode.hasError)
|
|
365
377
|
state.tooComplex = true;
|
|
366
378
|
// The walk must never throw out of this function — an uncaught error would
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repo": "ory-agent-plugins",
|
|
3
|
-
"commit": "
|
|
4
|
-
"commitShort": "
|
|
3
|
+
"commit": "fc87fd73772d79007d1072c605ede76de42792ce",
|
|
4
|
+
"commitShort": "fc87fd7",
|
|
5
5
|
"branch": "main",
|
|
6
|
-
"commitDate": "2026-08-
|
|
6
|
+
"commitDate": "2026-08-30T17:10:01-07:00",
|
|
7
7
|
"dirty": false,
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-31T00:13:34.583Z"
|
|
9
9
|
}
|
package/dist/cli.js
CHANGED
|
@@ -125,7 +125,7 @@ function runConfigureCommand(binName, args) {
|
|
|
125
125
|
console.log(`User subject ns: ${resolved.userSubjectNamespace ?? "User (default)"} [${resolved.userSubjectNamespaceSource}]`);
|
|
126
126
|
console.log("");
|
|
127
127
|
if (resolved.security.connected) {
|
|
128
|
-
console.log("Sign-in and permission checks are running.
|
|
128
|
+
console.log("Sign-in and permission checks are running. Native-tool blocks, MCP grants,");
|
|
129
129
|
console.log("and the observe/enforce posture are managed in the Ory Console (Agent Security).");
|
|
130
130
|
console.log("");
|
|
131
131
|
console.log("To disconnect (skills, commands, the local stack, and activity logging keep working):");
|
package/dist/contract-suite.js
CHANGED
|
@@ -200,9 +200,12 @@ function runHarnessContractSuite(adapter) {
|
|
|
200
200
|
else {
|
|
201
201
|
(0, vitest_1.expect)(blockEvents).toHaveLength(0);
|
|
202
202
|
}
|
|
203
|
-
//
|
|
203
|
+
// Native tools are default-allow, so a denied use check is an
|
|
204
|
+
// explicit block and receives the block-specific observe event.
|
|
204
205
|
const observeEvents = (0, testing_js_1.getActivityEvents)(client, "permission.observe_deny");
|
|
205
|
-
(0,
|
|
206
|
+
const blockObservedEvents = (0, testing_js_1.getActivityEvents)(client, "permission.block_observed");
|
|
207
|
+
(0, vitest_1.expect)(observeEvents).toHaveLength(0);
|
|
208
|
+
(0, vitest_1.expect)(blockObservedEvents).toHaveLength(check === "denied" && mode === "observe" ? 1 : 0);
|
|
206
209
|
});
|
|
207
210
|
(0, vitest_1.it)("records tool.invoke when an interactive tool proceeds", async () => {
|
|
208
211
|
process.env.ORY_INTERACTIVE_TOOLS = adapter.tool;
|
package/dist/local/manager.js
CHANGED
|
@@ -849,7 +849,7 @@ function printSeedResult(result) {
|
|
|
849
849
|
console.log(` User password: ${result.user.password}`);
|
|
850
850
|
console.log(` User OAuth2: ${result.user.client.clientId} (PKCE)`);
|
|
851
851
|
console.log("");
|
|
852
|
-
console.log(`
|
|
852
|
+
console.log(` Native tool grants: none (default allow; explicit blocks only)`);
|
|
853
853
|
console.log("");
|
|
854
854
|
console.log("To use with any Ory agent plugin, set these environment variables:");
|
|
855
855
|
console.log("");
|
package/dist/local/seed.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export interface SeedResult {
|
|
|
53
53
|
/** OAuth2 authorization_code+PKCE client for interactive logins. */
|
|
54
54
|
client: SeededOAuth2Client;
|
|
55
55
|
};
|
|
56
|
-
/**
|
|
56
|
+
/** Native baseline permission tuples written against the user's subject (always zero). */
|
|
57
57
|
permissions: {
|
|
58
58
|
namespace: string;
|
|
59
59
|
subject: string;
|
package/dist/local/seed.js
CHANGED
|
@@ -27,14 +27,11 @@ exports.USER_SUBJECT_NAMESPACE = exports.USER_CLIENT_ID = void 0;
|
|
|
27
27
|
exports.seedLocalEnvironment = seedLocalEnvironment;
|
|
28
28
|
const auth_js_1 = require("../auth.js");
|
|
29
29
|
const branding_js_1 = require("../branding.js");
|
|
30
|
-
const tool_catalog_js_1 = require("../tool-catalog.js");
|
|
31
30
|
const subject_js_1 = require("../subject.js");
|
|
32
|
-
const opl_js_1 = require("../opl.js");
|
|
33
31
|
const configs_js_1 = require("./configs.js");
|
|
34
32
|
const ports_js_1 = require("./ports.js");
|
|
35
33
|
const health_js_1 = require("./health.js");
|
|
36
34
|
const KRATOS_ADMIN = (0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.KRATOS_ADMIN_PORT);
|
|
37
|
-
const KETO_WRITE = (0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.KETO_WRITE_PORT);
|
|
38
35
|
const HYDRA_ADMIN = (0, ports_js_1.hostUrl)(configs_js_1.LOCAL_STACK_HOST, configs_js_1.HYDRA_ADMIN_PORT);
|
|
39
36
|
const USER_EMAIL = "user@ory-local.dev";
|
|
40
37
|
const USER_PASSWORD = "ory-user-local-dev-password!";
|
|
@@ -49,11 +46,6 @@ const USER_PASSWORD = "ory-user-local-dev-password!";
|
|
|
49
46
|
* dragging the value out of the `local up` banner.
|
|
50
47
|
*/
|
|
51
48
|
exports.USER_CLIENT_ID = branding_js_1.USER_LOGIN_CLIENT_ID;
|
|
52
|
-
/**
|
|
53
|
-
* Tools the explicit local-stack command seeds tuples for. Sourced from the
|
|
54
|
-
* shared catalog so one seed covers every harness's built-in tool set.
|
|
55
|
-
*/
|
|
56
|
-
const COMMON_TOOLS = tool_catalog_js_1.ALL_TOOLS;
|
|
57
49
|
async function jsonFetch(url, opts = {}) {
|
|
58
50
|
const res = await fetch(url, {
|
|
59
51
|
...opts,
|
|
@@ -94,44 +86,6 @@ async function seedIdentity(email, password, name) {
|
|
|
94
86
|
* install config all address the user under the same namespace.
|
|
95
87
|
*/
|
|
96
88
|
exports.USER_SUBJECT_NAMESPACE = subject_js_1.DEFAULT_USER_SUBJECT_NAMESPACE;
|
|
97
|
-
/**
|
|
98
|
-
* Write permission tuples granting the user identity access to all
|
|
99
|
-
* common tools. The subject is written as a SubjectSet
|
|
100
|
-
* `User:<userId>` (no relation) so the Console UI's *Add relationship*
|
|
101
|
-
* dialog — which only emits SubjectSet subjects — can recreate the same
|
|
102
|
-
* tuple by hand during the runbook walkthrough.
|
|
103
|
-
*/
|
|
104
|
-
async function seedPermissions(userId, namespace, objects = COMMON_TOOLS) {
|
|
105
|
-
let created = 0;
|
|
106
|
-
for (const object of objects) {
|
|
107
|
-
try {
|
|
108
|
-
await jsonFetch(`${KETO_WRITE}/admin/relation-tuples`, {
|
|
109
|
-
method: "PUT",
|
|
110
|
-
body: JSON.stringify({
|
|
111
|
-
namespace,
|
|
112
|
-
object,
|
|
113
|
-
relation: opl_js_1.RELATION_USERS,
|
|
114
|
-
subject_set: {
|
|
115
|
-
namespace: exports.USER_SUBJECT_NAMESPACE,
|
|
116
|
-
object: userId,
|
|
117
|
-
relation: "",
|
|
118
|
-
},
|
|
119
|
-
}),
|
|
120
|
-
});
|
|
121
|
-
created++;
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
125
|
-
if (!msg.includes("409")) {
|
|
126
|
-
process.stderr.write(` Warning: failed to create permission for ${object}: ${msg}\n`);
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
created++;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return created;
|
|
134
|
-
}
|
|
135
89
|
/**
|
|
136
90
|
* Delete a Hydra OAuth2 client if it exists, then create it at a known
|
|
137
91
|
* client_id (with a known secret, for a confidential client). We always
|
|
@@ -209,22 +163,10 @@ async function seedLocalEnvironment(namespace = "AgentTool") {
|
|
|
209
163
|
process.stderr.write(" Creating user identity...\n");
|
|
210
164
|
const userIdentity = await seedIdentity(USER_EMAIL, USER_PASSWORD, "Local User");
|
|
211
165
|
process.stderr.write(` User: ${userIdentity.id} (${userIdentity.email})\n`);
|
|
212
|
-
// 2.
|
|
213
|
-
//
|
|
214
|
-
// Subject is a SubjectSet `User:<id>` so the Console UI can rewrite
|
|
215
|
-
// the same tuple shape by hand during the runbook demo.
|
|
166
|
+
// 2. Native tools are allowed by default. Seed no AgentTool or ShellTool
|
|
167
|
+
// baseline grants; local behavior must match hosted default-allow policy.
|
|
216
168
|
const subjectLabel = `${exports.USER_SUBJECT_NAMESPACE}:${userIdentity.id}`;
|
|
217
|
-
process.stderr.write(
|
|
218
|
-
const tupleCount = await seedPermissions(userIdentity.id, namespace);
|
|
219
|
-
process.stderr.write(` Permissions: ${tupleCount} entries in '${namespace}' for ${subjectLabel}\n`);
|
|
220
|
-
// Shell-command sub-tools (issue #76): grant every catalog word in the
|
|
221
|
-
// ShellTool namespace so a launched harness can run `bash -c "curl …"`
|
|
222
|
-
// out of the box, and the enforce demo (revoke `ShellTool:curl#users`,
|
|
223
|
-
// watch it block) works without hand-writing tuples first.
|
|
224
|
-
const shellWords = (0, tool_catalog_js_1.getShellCommandCatalog)();
|
|
225
|
-
const shellNamespace = (0, tool_catalog_js_1.resolveShellCommandNamespace)();
|
|
226
|
-
const shellCount = await seedPermissions(userIdentity.id, shellNamespace, shellWords);
|
|
227
|
-
process.stderr.write(` Shell commands: ${shellCount} entries in '${shellNamespace}' for ${subjectLabel}\n`);
|
|
169
|
+
process.stderr.write(" Native tools: allowed unless explicitly blocked (no baseline grants)\n");
|
|
228
170
|
// 3. User OAuth2 client (PKCE) — pre-registered so the user gate's
|
|
229
171
|
// PKCE flow has somewhere to authenticate against. The agent's
|
|
230
172
|
// OAuth2 client is intentionally NOT pre-registered; the harness
|
|
@@ -253,6 +195,6 @@ async function seedLocalEnvironment(namespace = "AgentTool") {
|
|
|
253
195
|
}
|
|
254
196
|
return {
|
|
255
197
|
user: { identity: userIdentity, password: USER_PASSWORD, client: userClient },
|
|
256
|
-
permissions: { namespace, subject: subjectLabel, tuples:
|
|
198
|
+
permissions: { namespace, subject: subjectLabel, tuples: 0 },
|
|
257
199
|
};
|
|
258
200
|
}
|
package/dist/opl.d.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ory Permission Language (OPL) schema for the Agent Security permission model.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* permission is otherwise purely existence-based (no path ⇒ no access, any path
|
|
7
|
-
* ⇒ access), so the deny-override is modeled natively in OPL as a permit:
|
|
4
|
+
* Native agent and shell tools are allowed by default and may be explicitly
|
|
5
|
+
* blocked. The deny is modeled natively in OPL as a permit:
|
|
8
6
|
*
|
|
9
|
-
* use =
|
|
7
|
+
* use = !blockedSubjects.includes(subject)
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* `resolveCheckRelation`), so this model must be applied to the project for
|
|
15
|
-
* checks to resolve; with nothing blocked, `use` resolves identically to a bare
|
|
16
|
-
* `users` existence check, so grants alone allow.
|
|
9
|
+
* Argus always checks the **`use` permit**, so a missing block allows and a
|
|
10
|
+
* matching `blockedSubjects` relation denies. This model must be applied to the
|
|
11
|
+
* project for checks to resolve.
|
|
17
12
|
*
|
|
18
13
|
* ## Naming conventions
|
|
19
14
|
*
|
|
@@ -194,8 +189,7 @@ export declare const PERMISSION_MODE_OBJECT = "mode";
|
|
|
194
189
|
*/
|
|
195
190
|
export declare const PROJECT_POSTURE_OBJECT = "project";
|
|
196
191
|
/**
|
|
197
|
-
* The permit Argus checks:
|
|
198
|
-
* Keto. A verb, as Keto's own `view` is — it names the question the gate asks.
|
|
192
|
+
* The native-tool permit Argus checks: `!blockedSubjects`, evaluated by Keto.
|
|
199
193
|
*/
|
|
200
194
|
export declare const PERMIT_USE = "use";
|
|
201
195
|
/**
|
|
@@ -236,9 +230,9 @@ export declare function renderOplImport(): string;
|
|
|
236
230
|
/** Render a principal-namespace class (an empty `implements Namespace`). */
|
|
237
231
|
export declare function renderPrincipalClass(name: string): string;
|
|
238
232
|
/**
|
|
239
|
-
* Render a tool-namespace class body: the `
|
|
240
|
-
*
|
|
241
|
-
*
|
|
233
|
+
* Render a native tool-namespace class body: the `blockedSubjects` relation and
|
|
234
|
+
* the default-allow `use` permit Argus checks. The single definition is used for
|
|
235
|
+
* both AgentTool and ShellTool; MCP namespaces remain grant-based elsewhere.
|
|
242
236
|
*/
|
|
243
237
|
export declare function renderToolNamespaceClass(namespace?: string): string;
|
|
244
238
|
/**
|
|
@@ -248,8 +242,7 @@ export declare function renderToolNamespaceClass(namespace?: string): string;
|
|
|
248
242
|
*/
|
|
249
243
|
export declare function renderDelegationNamespaceClass(namespace?: string): string;
|
|
250
244
|
/**
|
|
251
|
-
* Render the role class:
|
|
252
|
-
* One class, one relation — a role is a group of subjects and nothing else.
|
|
245
|
+
* Render the role class: a reusable group whose members can be blocked together.
|
|
253
246
|
*/
|
|
254
247
|
export declare function renderAgentRoleNamespaceClass(namespace?: string): string;
|
|
255
248
|
/** Render a harness/sub-agent type group whose members inherit posture. */
|
package/dist/opl.js
CHANGED
|
@@ -2,19 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Ory Permission Language (OPL) schema for the Agent Security permission model.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* permission is otherwise purely existence-based (no path ⇒ no access, any path
|
|
8
|
-
* ⇒ access), so the deny-override is modeled natively in OPL as a permit:
|
|
5
|
+
* Native agent and shell tools are allowed by default and may be explicitly
|
|
6
|
+
* blocked. The deny is modeled natively in OPL as a permit:
|
|
9
7
|
*
|
|
10
|
-
* use =
|
|
8
|
+
* use = !blockedSubjects.includes(subject)
|
|
11
9
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `resolveCheckRelation`), so this model must be applied to the project for
|
|
16
|
-
* checks to resolve; with nothing blocked, `use` resolves identically to a bare
|
|
17
|
-
* `users` existence check, so grants alone allow.
|
|
10
|
+
* Argus always checks the **`use` permit**, so a missing block allows and a
|
|
11
|
+
* matching `blockedSubjects` relation denies. This model must be applied to the
|
|
12
|
+
* project for checks to resolve.
|
|
18
13
|
*
|
|
19
14
|
* ## Naming conventions
|
|
20
15
|
*
|
|
@@ -207,8 +202,7 @@ exports.PERMISSION_MODE_OBJECT = "mode";
|
|
|
207
202
|
*/
|
|
208
203
|
exports.PROJECT_POSTURE_OBJECT = "project";
|
|
209
204
|
/**
|
|
210
|
-
* The permit Argus checks:
|
|
211
|
-
* Keto. A verb, as Keto's own `view` is — it names the question the gate asks.
|
|
205
|
+
* The native-tool permit Argus checks: `!blockedSubjects`, evaluated by Keto.
|
|
212
206
|
*/
|
|
213
207
|
exports.PERMIT_USE = "use";
|
|
214
208
|
/** Conventional user namespace used for SubjectSet-addressed users (`User:<id>`). */
|
|
@@ -263,30 +257,25 @@ function renderPrincipalClass(name) {
|
|
|
263
257
|
return `class ${name} implements Namespace {}`;
|
|
264
258
|
}
|
|
265
259
|
/**
|
|
266
|
-
* Render a tool-namespace class body: the `
|
|
267
|
-
*
|
|
268
|
-
*
|
|
260
|
+
* Render a native tool-namespace class body: the `blockedSubjects` relation and
|
|
261
|
+
* the default-allow `use` permit Argus checks. The single definition is used for
|
|
262
|
+
* both AgentTool and ShellTool; MCP namespaces remain grant-based elsewhere.
|
|
269
263
|
*/
|
|
270
264
|
function renderToolNamespaceClass(namespace = exports.DEFAULT_NAMESPACE) {
|
|
271
265
|
return `class ${namespace} implements Namespace {
|
|
272
266
|
related: {
|
|
273
|
-
// Machine principals are
|
|
267
|
+
// Machine principals are blockable in their own right: \`${subject_js_1.AGENT_NAMESPACE}:<clientId>\` is
|
|
274
268
|
// one session's registered agent credential and \`${subject_js_1.SUBAGENT_NAMESPACE}:<clientId>\` one
|
|
275
269
|
// typed sub-agent credential in that session. Their narrower session- and spawn-scoped forms
|
|
276
270
|
// (\`${subject_js_1.AGENT_NAMESPACE}:<clientId>|<session>\`, \`${subject_js_1.SUBAGENT_NAMESPACE}:<clientId>|<session>|<type>\`) are
|
|
277
271
|
// members of the same namespaces, which is what lets a block name one run.
|
|
278
|
-
// A
|
|
279
|
-
// tool in every tool namespace: \`@(${exports.AGENT_ROLE_NAMESPACE}:<role>#${exports.RELATION_MEMBERS})\`.
|
|
280
|
-
${exports.RELATION_USERS}: (${USER_NAMESPACE} | ${subject_js_1.SESSION_NAMESPACE} | ${subject_js_1.AGENT_NAMESPACE} | ${subject_js_1.SUBAGENT_NAMESPACE} | SubjectSet<${exports.AGENT_ROLE_NAMESPACE}, "${exports.RELATION_MEMBERS}"> | SubjectSet<${namespace}, "${exports.RELATION_USERS}">)[]
|
|
272
|
+
// A block may also name the shared role's members.
|
|
281
273
|
${exports.RELATION_BLOCKED_SUBJECTS}: (${USER_NAMESPACE} | ${subject_js_1.SESSION_NAMESPACE} | ${subject_js_1.AGENT_NAMESPACE} | ${subject_js_1.SUBAGENT_NAMESPACE} | SubjectSet<${exports.AGENT_ROLE_NAMESPACE}, "${exports.RELATION_MEMBERS}"> | SubjectSet<${namespace}, "${exports.RELATION_BLOCKED_SUBJECTS}">)[]
|
|
282
274
|
}
|
|
283
275
|
|
|
284
276
|
permits = {
|
|
285
|
-
//
|
|
286
|
-
// \`${exports.RELATION_USERS}\` grant, regardless of how the grant was reached (direct or
|
|
287
|
-
// via a set).
|
|
277
|
+
// Native tools are allowed unless an explicit block matches.
|
|
288
278
|
${exports.PERMIT_USE}: (ctx: Context): boolean =>
|
|
289
|
-
this.related.${exports.RELATION_USERS}.includes(ctx.subject) &&
|
|
290
279
|
!this.related.${exports.RELATION_BLOCKED_SUBJECTS}.includes(ctx.subject),
|
|
291
280
|
}
|
|
292
281
|
}`;
|
|
@@ -316,15 +305,13 @@ function renderDelegationNamespaceClass(namespace = exports.DELEGATION_NAMESPACE
|
|
|
316
305
|
}`;
|
|
317
306
|
}
|
|
318
307
|
/**
|
|
319
|
-
* Render the role class:
|
|
320
|
-
* One class, one relation — a role is a group of subjects and nothing else.
|
|
308
|
+
* Render the role class: a reusable group whose members can be blocked together.
|
|
321
309
|
*/
|
|
322
310
|
function renderAgentRoleNamespaceClass(namespace = exports.AGENT_ROLE_NAMESPACE) {
|
|
323
311
|
return `class ${namespace} implements Namespace {
|
|
324
312
|
related: {
|
|
325
|
-
// The subjects that belong to the role. A tool
|
|
326
|
-
// \`(${namespace}:<role>#${exports.RELATION_MEMBERS})\`
|
|
327
|
-
// every tool the role holds, in either tool namespace, from one relation.
|
|
313
|
+
// The subjects that belong to the role. A native-tool block can name
|
|
314
|
+
// \`(${namespace}:<role>#${exports.RELATION_MEMBERS})\` to block the group.
|
|
328
315
|
${exports.RELATION_MEMBERS}: (${USER_NAMESPACE} | ${subject_js_1.AGENT_NAMESPACE} | ${subject_js_1.SUBAGENT_NAMESPACE} | SubjectSet<${namespace}, "${exports.RELATION_MEMBERS}">)[]
|
|
329
316
|
}
|
|
330
317
|
}`;
|
|
@@ -408,20 +395,18 @@ function buildAgentSecurityOpl(namespace = exports.DEFAULT_NAMESPACE, shellNames
|
|
|
408
395
|
"// that has no such relation, rather than storing a tuple that never matches.",
|
|
409
396
|
...exports.NODE_REFERENCE_NAMESPACES.map(renderPrincipalClass),
|
|
410
397
|
"",
|
|
411
|
-
`// Harness tool objects (\`${namespace}:Bash\`). \`${exports.
|
|
412
|
-
`//
|
|
413
|
-
|
|
414
|
-
"// blocked.",
|
|
398
|
+
`// Harness tool objects (\`${namespace}:Bash\`). \`${exports.RELATION_BLOCKED_SUBJECTS}\` is an explicit`,
|
|
399
|
+
`// deny. The \`${exports.PERMIT_USE}\` permit is the effective decision Argus checks: allowed`,
|
|
400
|
+
"// unless blocked.",
|
|
415
401
|
renderToolNamespaceClass(namespace),
|
|
416
402
|
"",
|
|
417
403
|
"// Shell command sub-tools (issue #76): each program/builtin word a shell",
|
|
418
404
|
`// command runs is checked as \`${shellNamespace}:<word>#${exports.PERMIT_USE}\`, so a blocked`,
|
|
419
405
|
'// binary (e.g. curl) can\'t be reached through `bash -c "curl …"`. Same',
|
|
420
|
-
"//
|
|
406
|
+
"// default-allow / explicit-block model as the tool namespace above.",
|
|
421
407
|
renderToolNamespaceClass(shellNamespace),
|
|
422
408
|
"",
|
|
423
|
-
"// The shared role
|
|
424
|
-
"// instead of re-granting every tool per identity.",
|
|
409
|
+
"// The shared role whose members can be blocked as a group.",
|
|
425
410
|
renderAgentRoleNamespaceClass(),
|
|
426
411
|
"",
|
|
427
412
|
"// Delegation nodes: the current-state graph of who delegated to whom, written",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* v0.14, and the report is the same.)
|
|
11
11
|
*
|
|
12
12
|
* Everything that *writes* to the project is provisioned in the **Ory Console**
|
|
13
|
-
* (Agent Security), not from the plugin: the permission model,
|
|
14
|
-
*
|
|
13
|
+
* (Agent Security), not from the plugin: the permission model, explicit native
|
|
14
|
+
* blocks, MCP grants, and the observe/enforce posture. That is what lets a
|
|
15
15
|
* plugin be installed with nothing but a project URL — the public OAuth2 client
|
|
16
16
|
* id has a reserved default, with no workspace privilege, project API key, or admin write path. The
|
|
17
17
|
* plugin is a read-only client of the project's permission state.
|
package/dist/permissions-cli.js
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* v0.14, and the report is the same.)
|
|
12
12
|
*
|
|
13
13
|
* Everything that *writes* to the project is provisioned in the **Ory Console**
|
|
14
|
-
* (Agent Security), not from the plugin: the permission model,
|
|
15
|
-
*
|
|
14
|
+
* (Agent Security), not from the plugin: the permission model, explicit native
|
|
15
|
+
* blocks, MCP grants, and the observe/enforce posture. That is what lets a
|
|
16
16
|
* plugin be installed with nothing but a project URL — the public OAuth2 client
|
|
17
17
|
* id has a reserved default, with no workspace privilege, project API key, or admin write path. The
|
|
18
18
|
* plugin is a read-only client of the project's permission state.
|
|
@@ -26,7 +26,6 @@ const logger_js_1 = require("./logger.js");
|
|
|
26
26
|
const agent_auth_js_1 = require("./agent-auth.js");
|
|
27
27
|
const tool_catalog_js_1 = require("./tool-catalog.js");
|
|
28
28
|
const subject_js_1 = require("./subject.js");
|
|
29
|
-
const permissions_js_1 = require("./permissions.js");
|
|
30
29
|
const opl_js_1 = require("./opl.js");
|
|
31
30
|
function resolveNamespace() {
|
|
32
31
|
return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTool";
|
|
@@ -79,7 +78,7 @@ function printPermissionsHelp(binName) {
|
|
|
79
78
|
console.log(" observe — Log an audit event and allow the tool through. The default.");
|
|
80
79
|
console.log(" enforce — Block the tool. The production posture.");
|
|
81
80
|
console.log("");
|
|
82
|
-
console.log("The plugin only ever *reads* permissions.
|
|
81
|
+
console.log("The plugin only ever *reads* permissions. Native tool blocks, MCP grants,");
|
|
83
82
|
console.log("the permission model, and the observe/enforce posture are all managed in");
|
|
84
83
|
console.log("the Ory Console (Agent Security) by someone with access to the project.");
|
|
85
84
|
console.log("");
|
|
@@ -97,8 +96,8 @@ function printProvisioningMovedToConsole(binName, attempted) {
|
|
|
97
96
|
console.error("");
|
|
98
97
|
console.error("Permissions are provisioned in the Ory Console (Agent Security):");
|
|
99
98
|
console.error(" - the permission model applied to the project");
|
|
100
|
-
console.error(" -
|
|
101
|
-
console.error(" -
|
|
99
|
+
console.error(" - explicit native-tool blocks for a user, agent, or group");
|
|
100
|
+
console.error(" - MCP grants");
|
|
102
101
|
console.error(" - the observe / enforce posture, project-wide or per principal");
|
|
103
102
|
console.error("");
|
|
104
103
|
console.error("The plugin reads that state and enforces it. To see what it resolves:");
|
|
@@ -119,7 +118,7 @@ async function runPermissionsStatus(binName, harness) {
|
|
|
119
118
|
console.log("");
|
|
120
119
|
if (!resolved.security.connected) {
|
|
121
120
|
console.log("Ory Agent Security is not connected, so no permission checks run and there");
|
|
122
|
-
console.log("is no
|
|
121
|
+
console.log("is no native tool access to report. The rest of the plugin is unaffected.");
|
|
123
122
|
console.log("");
|
|
124
123
|
console.log(`Connect with: ${binName} configure --project-url <URL>`);
|
|
125
124
|
console.log("The project URL comes from the Ory Console (Agent Security).");
|
|
@@ -157,7 +156,6 @@ async function runPermissionsStatus(binName, harness) {
|
|
|
157
156
|
const results = await probeCatalog(client, catalog, namespace, subject);
|
|
158
157
|
const allowed = results.filter((r) => r.error === undefined && r.allowed).length;
|
|
159
158
|
const blocked = results.filter((r) => r.blocked).length;
|
|
160
|
-
const denied = results.filter((r) => r.error === undefined && !r.allowed && !r.blocked).length;
|
|
161
159
|
const errored = results.filter((r) => r.error !== undefined).length;
|
|
162
160
|
const widest = Math.max(...catalog.map((t) => t.length), 4);
|
|
163
161
|
console.log(` ${"Tool".padEnd(widest)} Status`);
|
|
@@ -167,53 +165,38 @@ async function runPermissionsStatus(binName, harness) {
|
|
|
167
165
|
? `error (${r.error})`
|
|
168
166
|
: r.blocked
|
|
169
167
|
? "blocked"
|
|
170
|
-
:
|
|
171
|
-
? "allowed"
|
|
172
|
-
: "denied";
|
|
168
|
+
: "allowed";
|
|
173
169
|
console.log(` ${r.tool.padEnd(widest)} ${status}`);
|
|
174
170
|
}
|
|
175
171
|
console.log("");
|
|
176
|
-
console.log(`Summary: ${allowed} allowed, ${
|
|
172
|
+
console.log(`Summary: ${allowed} allowed, ${blocked} blocked, ${errored} errored.`);
|
|
177
173
|
// Shell sub-tools (issue #76): a compact count rather than 100+ rows.
|
|
178
|
-
let
|
|
174
|
+
let shellBlocked = 0;
|
|
179
175
|
if ((0, tool_catalog_js_1.getShellToolCatalog)(harness).length > 0) {
|
|
180
176
|
const shellNamespace = (0, tool_catalog_js_1.resolveShellCommandNamespace)();
|
|
181
177
|
const shellWords = (0, tool_catalog_js_1.getShellCommandCatalog)();
|
|
182
178
|
const shellResults = await probeCatalog(client, shellWords, shellNamespace, subject);
|
|
183
179
|
const sAllowed = shellResults.filter((r) => r.allowed).length;
|
|
184
|
-
|
|
180
|
+
shellBlocked = shellResults.filter((r) => r.blocked).length;
|
|
185
181
|
const sErrored = shellResults.filter((r) => r.error !== undefined).length;
|
|
186
|
-
console.log(`Shell commands (${shellNamespace}): ${sAllowed} allowed, ${
|
|
182
|
+
console.log(`Shell commands (${shellNamespace}): ${sAllowed} allowed, ${shellBlocked} blocked, ` +
|
|
187
183
|
`${sErrored} errored of ${shellWords.length}.`);
|
|
188
184
|
}
|
|
189
|
-
if (denied > 0 || shellDenied > 0) {
|
|
190
|
-
console.log("");
|
|
191
|
-
console.log("Denied tools have no grant for this subject. Grant them in the Ory");
|
|
192
|
-
console.log("Console (Agent Security) — the plugin cannot write permissions.");
|
|
193
|
-
}
|
|
194
185
|
if (blocked > 0) {
|
|
195
186
|
console.log("");
|
|
196
|
-
console.log("'blocked' tools carry an explicit block
|
|
187
|
+
console.log("'blocked' tools carry an explicit native-tool block.");
|
|
197
188
|
console.log("Remove it in the Ory Console (Agent Security).");
|
|
198
189
|
}
|
|
199
190
|
return 0;
|
|
200
191
|
}
|
|
201
192
|
async function probeCatalog(client, catalog, namespace, subject) {
|
|
202
|
-
// Evaluate the
|
|
203
|
-
const relation =
|
|
193
|
+
// Evaluate the default-allow `use` permit, exactly as the runtime gate does.
|
|
194
|
+
const relation = opl_js_1.PERMIT_USE;
|
|
204
195
|
const rows = [];
|
|
205
196
|
for (const tool of catalog) {
|
|
206
197
|
try {
|
|
207
198
|
const result = await client.checkPermission({ namespace, object: tool, relation, ...subject }, { activityAttributes: { toolName: tool, source: "permissions_status" } });
|
|
208
|
-
|
|
209
|
-
if (!result.allowed) {
|
|
210
|
-
// Distinguish an explicit block from a plain missing grant.
|
|
211
|
-
blocked = await client
|
|
212
|
-
.checkPermission({ namespace, object: tool, relation: opl_js_1.RELATION_BLOCKED_SUBJECTS, ...subject }, { activityAttributes: { toolName: tool, source: "permissions_status_block" } })
|
|
213
|
-
.then((r) => r.allowed)
|
|
214
|
-
.catch(() => false);
|
|
215
|
-
}
|
|
216
|
-
rows.push({ tool, allowed: result.allowed, blocked });
|
|
199
|
+
rows.push({ tool, allowed: result.allowed, blocked: !result.allowed });
|
|
217
200
|
}
|
|
218
201
|
catch (err) {
|
|
219
202
|
const ory = err;
|
package/dist/permissions.d.ts
CHANGED
|
@@ -34,10 +34,10 @@ import type { OryError, OryErrorCode, PermissionCheck, PermissionResult } from "
|
|
|
34
34
|
* Why a permission check came back denied, once the block-aware OPL schema is
|
|
35
35
|
* in play:
|
|
36
36
|
*
|
|
37
|
-
* - `not_granted` —
|
|
38
|
-
*
|
|
39
|
-
* - `explicit_block` — a `
|
|
40
|
-
* `
|
|
37
|
+
* - `not_granted` — compatibility for grant-based checks outside native
|
|
38
|
+
* AgentTool/ShellTool policy (notably MCP).
|
|
39
|
+
* - `explicit_block` — a native `use` permit denied because a
|
|
40
|
+
* `blockedSubjects` relation matched.
|
|
41
41
|
*
|
|
42
42
|
* Threaded onto the decision's activity attributes and into the observe-mode
|
|
43
43
|
* audit event so "explicitly blocked" is distinguishable from "never granted"
|
|
@@ -46,12 +46,9 @@ import type { OryError, OryErrorCode, PermissionCheck, PermissionResult } from "
|
|
|
46
46
|
*/
|
|
47
47
|
export type BlockReason = "not_granted" | "explicit_block";
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* always respected — the permission model must therefore be applied to the
|
|
53
|
-
* project (done in the Ory Console, Agent Security) for checks to resolve. Any
|
|
54
|
-
* other relation passes through unchanged.
|
|
49
|
+
* Compatibility shim for callers that still pass the former native `users`
|
|
50
|
+
* relation. Native adapters now pass `use` directly; mapping `users` preserves
|
|
51
|
+
* the public helper contract without reintroducing grant-based native checks.
|
|
55
52
|
*/
|
|
56
53
|
export declare function resolveCheckRelation(relation: string): string;
|
|
57
54
|
/**
|
package/dist/permissions.js
CHANGED
|
@@ -42,12 +42,9 @@ const opl_js_1 = require("./opl.js");
|
|
|
42
42
|
const subject_js_1 = require("./subject.js");
|
|
43
43
|
const tool_catalog_js_1 = require("./tool-catalog.js");
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* always respected — the permission model must therefore be applied to the
|
|
49
|
-
* project (done in the Ory Console, Agent Security) for checks to resolve. Any
|
|
50
|
-
* other relation passes through unchanged.
|
|
45
|
+
* Compatibility shim for callers that still pass the former native `users`
|
|
46
|
+
* relation. Native adapters now pass `use` directly; mapping `users` preserves
|
|
47
|
+
* the public helper contract without reintroducing grant-based native checks.
|
|
51
48
|
*/
|
|
52
49
|
function resolveCheckRelation(relation) {
|
|
53
50
|
return relation === opl_js_1.RELATION_USERS ? opl_js_1.PERMIT_USE : relation;
|
|
@@ -428,10 +425,7 @@ async function decomposeAndCheck(client, args) {
|
|
|
428
425
|
const results = [];
|
|
429
426
|
for (let start = 0; start < words.length; start += batchSize) {
|
|
430
427
|
const chunk = words.slice(start, start + batchSize);
|
|
431
|
-
|
|
432
|
-
// top-level check), so an explicit `ShellTool:<word>#blocked` overrides
|
|
433
|
-
// a `use` grant. With no block present, `access` == bare `use`.
|
|
434
|
-
const wordRelation = resolveCheckRelation("users");
|
|
428
|
+
const wordRelation = opl_js_1.PERMIT_USE;
|
|
435
429
|
const checks = chunk.map((word) => ({
|
|
436
430
|
namespace,
|
|
437
431
|
object: word,
|
|
@@ -482,7 +476,7 @@ async function decomposeAndCheck(client, args) {
|
|
|
482
476
|
const decision = applyPermissionMode(client, blocked ? false : allowed, {
|
|
483
477
|
namespace,
|
|
484
478
|
object: word,
|
|
485
|
-
relation: blocked ? opl_js_1.RELATION_BLOCKED_SUBJECTS : opl_js_1.
|
|
479
|
+
relation: blocked ? opl_js_1.RELATION_BLOCKED_SUBJECTS : opl_js_1.PERMIT_USE,
|
|
486
480
|
subjectId: blocked?.subject ?? subject.subjectId,
|
|
487
481
|
subjectSet: blocked ? undefined : subject.subjectSet,
|
|
488
482
|
activityAttributes: {
|
|
@@ -494,7 +488,7 @@ async function decomposeAndCheck(client, args) {
|
|
|
494
488
|
} : {}),
|
|
495
489
|
},
|
|
496
490
|
modeOverride: args.modeOverride,
|
|
497
|
-
blockReason: blocked ? "explicit_block" : undefined,
|
|
491
|
+
blockReason: blocked || !allowed ? "explicit_block" : undefined,
|
|
498
492
|
});
|
|
499
493
|
if (decision.kind === "deny") {
|
|
500
494
|
deniedWords.push(word);
|
|
@@ -510,17 +504,9 @@ async function decomposeAndCheck(client, args) {
|
|
|
510
504
|
const primaryBlock = primaryBlockedWord ? matchedBlocks.get(primaryBlockedWord) : undefined;
|
|
511
505
|
// Aggregate. Enforce: any denied word blocks. Observe: pass through.
|
|
512
506
|
if (deniedWords.length > 0) {
|
|
513
|
-
//
|
|
514
|
-
// denied word, so the deny event and denial message say "explicitly blocked".
|
|
507
|
+
// A native `use` deny can only be an explicit block.
|
|
515
508
|
const primaryWord = primaryBlockedWord ?? deniedWords[0];
|
|
516
|
-
const blockReason =
|
|
517
|
-
? "explicit_block"
|
|
518
|
-
: await probeBlockReason(client, {
|
|
519
|
-
namespace,
|
|
520
|
-
object: primaryWord,
|
|
521
|
-
relation: opl_js_1.RELATION_USERS,
|
|
522
|
-
...subject,
|
|
523
|
-
});
|
|
509
|
+
const blockReason = "explicit_block";
|
|
524
510
|
return {
|
|
525
511
|
kind: "deny",
|
|
526
512
|
result: {
|
|
@@ -533,7 +519,7 @@ async function decomposeAndCheck(client, args) {
|
|
|
533
519
|
relation: opl_js_1.RELATION_BLOCKED_SUBJECTS,
|
|
534
520
|
subjectId: primaryBlock.subject,
|
|
535
521
|
}
|
|
536
|
-
: { namespace, object: primaryWord, relation: opl_js_1.
|
|
522
|
+
: { namespace, object: primaryWord, relation: opl_js_1.PERMIT_USE, ...subject },
|
|
537
523
|
},
|
|
538
524
|
mode: "enforce",
|
|
539
525
|
activityAttributes: {
|
|
@@ -588,8 +574,8 @@ async function checkAndDecide(client, check, opts = {}) {
|
|
|
588
574
|
const resolved = (0, config_js_1.resolveConfig)();
|
|
589
575
|
const mode = opts.modeOverride ?? resolved.permissionMode;
|
|
590
576
|
const activityAttributes = buildDecisionAttributes(mode, check.subjectId, check.subjectSet);
|
|
591
|
-
//
|
|
592
|
-
//
|
|
577
|
+
// Native adapters pass `use`; `resolveCheckRelation` keeps older public
|
|
578
|
+
// callers that pass `users` on the same default-allow permit.
|
|
593
579
|
const effectiveCheck = {
|
|
594
580
|
...check,
|
|
595
581
|
relation: resolveCheckRelation(check.relation),
|
|
@@ -624,11 +610,13 @@ async function checkAndDecide(client, check, opts = {}) {
|
|
|
624
610
|
// Report the decision against the caller's original check (relation `use`),
|
|
625
611
|
// not the internal permit rewrite, so callers see the coordinates they passed.
|
|
626
612
|
result = { ...result, check };
|
|
627
|
-
//
|
|
628
|
-
//
|
|
613
|
+
// A deny from the native `use` permit is necessarily an explicit block.
|
|
614
|
+
// Retain the diagnostic probe only for other grant-based public checks.
|
|
629
615
|
const blockReason = result.allowed
|
|
630
616
|
? undefined
|
|
631
|
-
:
|
|
617
|
+
: effectiveCheck.relation === opl_js_1.PERMIT_USE
|
|
618
|
+
? "explicit_block"
|
|
619
|
+
: await probeBlockReason(client, check);
|
|
632
620
|
const inner = applyPermissionMode(client, result.allowed, {
|
|
633
621
|
namespace: check.namespace,
|
|
634
622
|
object: check.object,
|
package/dist/post-install.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* plugin logs activity locally and gates nothing. Nothing else changes either way.
|
|
17
17
|
*
|
|
18
18
|
* Everything *else* an Ory project needs to serve Agent Security — the login
|
|
19
|
-
* OAuth2 client, the permission model,
|
|
19
|
+
* OAuth2 client, the permission model, native-tool blocks, MCP grants,
|
|
20
20
|
* and the observe/enforce posture — is provisioned in the **Ory Console**
|
|
21
21
|
* (Agent Security), by someone with access to the project. The plugin never
|
|
22
22
|
* writes project configuration, which is why installing it needs only a
|
package/dist/post-install.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* plugin logs activity locally and gates nothing. Nothing else changes either way.
|
|
18
18
|
*
|
|
19
19
|
* Everything *else* an Ory project needs to serve Agent Security — the login
|
|
20
|
-
* OAuth2 client, the permission model,
|
|
20
|
+
* OAuth2 client, the permission model, native-tool blocks, MCP grants,
|
|
21
21
|
* and the observe/enforce posture — is provisioned in the **Ory Console**
|
|
22
22
|
* (Agent Security), by someone with access to the project. The plugin never
|
|
23
23
|
* writes project configuration, which is why installing it needs only a
|
|
@@ -112,7 +112,7 @@ async function runPostInstall(binName, _harness, args = []) {
|
|
|
112
112
|
if (security.connected) {
|
|
113
113
|
ui.success("Ory Agent Security is connected.");
|
|
114
114
|
ui.hint("Sign-in and permission checks run on the next session.");
|
|
115
|
-
ui.hint("
|
|
115
|
+
ui.hint("Native-tool blocks, MCP grants, and the observe/enforce posture are managed in the Ory Console (Agent Security).");
|
|
116
116
|
ui.hint(`Inspect what this machine resolves with \`${binName} permissions status\`.`);
|
|
117
117
|
}
|
|
118
118
|
else {
|
package/dist/setup.js
CHANGED
|
@@ -441,7 +441,7 @@ function printConfiguredNextStepsNow(harnessName, uninstallCmd, opts) {
|
|
|
441
441
|
else {
|
|
442
442
|
ui.hint("It starts in observe mode: every tool call is checked, but nothing is blocked yet.");
|
|
443
443
|
ui.blank();
|
|
444
|
-
ui.bullet("1. See what's happening — config and
|
|
444
|
+
ui.bullet("1. See what's happening — config and native tool access:");
|
|
445
445
|
ui.command(`${npx} status`);
|
|
446
446
|
ui.blank();
|
|
447
447
|
ui.bullet("2. Turn on enforcement once the activity log looks right:");
|
package/dist/status-cli.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare function printAgentIdentitySection(harness: string): void;
|
|
|
20
20
|
export declare function printSubAgentIdentitiesSection(harness: string): void;
|
|
21
21
|
/**
|
|
22
22
|
* Render the "Permissions" block: the deny posture, the namespace, and the
|
|
23
|
-
*
|
|
23
|
+
* allowed/blocked state of the harness's built-in tool catalog.
|
|
24
24
|
*
|
|
25
25
|
* The network probe runs only when a project URL is configured *and* a user
|
|
26
26
|
* identity is available (cached PKCE tokens or `ORY_USER_SUBJECT_ID`). When it
|
package/dist/status-cli.js
CHANGED
|
@@ -161,7 +161,7 @@ function printSubAgentIdentitiesSection(harness) {
|
|
|
161
161
|
}
|
|
162
162
|
/**
|
|
163
163
|
* Render the "Permissions" block: the deny posture, the namespace, and the
|
|
164
|
-
*
|
|
164
|
+
* allowed/blocked state of the harness's built-in tool catalog.
|
|
165
165
|
*
|
|
166
166
|
* The network probe runs only when a project URL is configured *and* a user
|
|
167
167
|
* identity is available (cached PKCE tokens or `ORY_USER_SUBJECT_ID`). When it
|
|
@@ -207,32 +207,32 @@ async function printPermissionsSection(binName, harness) {
|
|
|
207
207
|
if (!probe) {
|
|
208
208
|
return printNoCoverage(cachedMode, namespace, "unavailable (no user identity could be resolved)");
|
|
209
209
|
}
|
|
210
|
-
const { total, allowed,
|
|
210
|
+
const { total, allowed, blocked, errored } = probe;
|
|
211
211
|
console.log(` Mode: ${probe.mode}${formatModeSuffix(probe.modeSource)}`);
|
|
212
212
|
console.log(` Namespace: ${namespace}`);
|
|
213
213
|
console.log(` Subject: ${probe.subject}`);
|
|
214
|
-
if (errored > 0 && allowed === 0 &&
|
|
215
|
-
console.log(`
|
|
214
|
+
if (errored > 0 && allowed === 0 && blocked === 0) {
|
|
215
|
+
console.log(` Tool access: probe failed (${probe.errorCode ?? "unknown"})`);
|
|
216
216
|
return;
|
|
217
217
|
}
|
|
218
218
|
const status = allowed === total
|
|
219
|
-
? `
|
|
219
|
+
? `all allowed (${allowed}/${total} built-in tools)`
|
|
220
220
|
: allowed === 0
|
|
221
|
-
? `
|
|
222
|
-
:
|
|
223
|
-
console.log(`
|
|
224
|
-
if (
|
|
225
|
-
console.log("
|
|
221
|
+
? `all blocked (${blocked}/${total} built-in tools)`
|
|
222
|
+
: `${allowed} allowed, ${blocked} blocked of ${total} built-in tools`;
|
|
223
|
+
console.log(` Tool access: ${status}`);
|
|
224
|
+
if (blocked > 0) {
|
|
225
|
+
console.log(" remove unwanted blocks in the Ory Console (Agent Security)");
|
|
226
226
|
}
|
|
227
227
|
else if (errored > 0) {
|
|
228
228
|
console.log(` ${errored} probe error(s) — run "${(0, cli_invocation_js_1.oryNpx)(binName)} permissions" for details`);
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
|
-
/** The Mode / Namespace /
|
|
231
|
+
/** The Mode / Namespace / Tool access lines for every path that can't probe. */
|
|
232
232
|
function printNoCoverage(mode, namespace, coverage) {
|
|
233
233
|
console.log(` Mode: ${mode}`);
|
|
234
234
|
console.log(` Namespace: ${namespace}`);
|
|
235
|
-
console.log(`
|
|
235
|
+
console.log(` Tool access: ${coverage}`);
|
|
236
236
|
}
|
|
237
237
|
function isUserTokenUsable() {
|
|
238
238
|
const tokens = (0, auth_store_js_1.loadTokens)();
|
package/dist/status-data.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ import { type StatusSystemSection } from "./status-system.js";
|
|
|
16
16
|
export interface PermissionsCoverage {
|
|
17
17
|
total: number;
|
|
18
18
|
allowed: number;
|
|
19
|
+
blocked: number;
|
|
20
|
+
/** @deprecated Native false results are explicit blocks; use `blocked`. */
|
|
19
21
|
denied: number;
|
|
20
22
|
errored: number;
|
|
21
23
|
errorCode?: string;
|
|
@@ -32,14 +34,13 @@ export interface PermissionsCoverage {
|
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* Probe the harness's built-in tool catalog against Ory, returning how many
|
|
35
|
-
* tools the resolved user subject is allowed /
|
|
37
|
+
* tools the resolved user subject is allowed / blocked / errored on, plus the
|
|
36
38
|
* live permission mode and the subject that was checked. Returns undefined when
|
|
37
39
|
* a probe can't run (no project URL, empty catalog, no user identity). Never
|
|
38
40
|
* throws.
|
|
39
41
|
*
|
|
40
|
-
* Checks
|
|
41
|
-
*
|
|
42
|
-
* block is reported as denied here too, exactly as a tool call would be.
|
|
42
|
+
* Checks use the same default-allow `use` permit as the runtime gate. A false
|
|
43
|
+
* result therefore means an explicit block, not missing grant coverage.
|
|
43
44
|
*/
|
|
44
45
|
export declare function probePermissionsCoverage(harness: string): Promise<PermissionsCoverage | undefined>;
|
|
45
46
|
export interface StatusConfigSection {
|
|
@@ -141,7 +142,9 @@ export interface StatusSubAgentSection {
|
|
|
141
142
|
}
|
|
142
143
|
export type PermissionsCoverageStatus =
|
|
143
144
|
/** Agent Security isn't connected — no project URL and/or OAuth2 client id. */
|
|
144
|
-
"not_connected" | "no_catalog" | "no_user" | "
|
|
145
|
+
"not_connected" | "no_catalog" | "no_user" | "fully_allowed" | "partially_blocked" | "fully_blocked"
|
|
146
|
+
/** @deprecated Pre-default-allow status names retained for API compatibility. */
|
|
147
|
+
| "fully_granted" | "partial" | "no_grants" | "probe_failed";
|
|
145
148
|
export interface StatusPermissionsSection {
|
|
146
149
|
mode: "observe" | "enforce";
|
|
147
150
|
/** Where `mode` came from: a live project read, the local cache, or the default. */
|
package/dist/status-data.js
CHANGED
|
@@ -50,7 +50,6 @@ exports.collectStatusReport = collectStatusReport;
|
|
|
50
50
|
*/
|
|
51
51
|
const config_js_1 = require("./config.js");
|
|
52
52
|
const permission_mode_js_1 = require("./permission-mode.js");
|
|
53
|
-
const permissions_js_1 = require("./permissions.js");
|
|
54
53
|
const auth_store_js_1 = require("./auth-store.js");
|
|
55
54
|
const agent_auth_js_1 = require("./agent-auth.js");
|
|
56
55
|
const subject_js_1 = require("./subject.js");
|
|
@@ -69,14 +68,13 @@ function resolveNamespace() {
|
|
|
69
68
|
}
|
|
70
69
|
/**
|
|
71
70
|
* Probe the harness's built-in tool catalog against Ory, returning how many
|
|
72
|
-
* tools the resolved user subject is allowed /
|
|
71
|
+
* tools the resolved user subject is allowed / blocked / errored on, plus the
|
|
73
72
|
* live permission mode and the subject that was checked. Returns undefined when
|
|
74
73
|
* a probe can't run (no project URL, empty catalog, no user identity). Never
|
|
75
74
|
* throws.
|
|
76
75
|
*
|
|
77
|
-
* Checks
|
|
78
|
-
*
|
|
79
|
-
* block is reported as denied here too, exactly as a tool call would be.
|
|
76
|
+
* Checks use the same default-allow `use` permit as the runtime gate. A false
|
|
77
|
+
* result therefore means an explicit block, not missing grant coverage.
|
|
80
78
|
*/
|
|
81
79
|
async function probePermissionsCoverage(harness) {
|
|
82
80
|
const resolved = (0, config_js_1.resolveConfig)();
|
|
@@ -106,10 +104,11 @@ async function probePermissionsCoverage(harness) {
|
|
|
106
104
|
additionalSubjects: agentSubject ? [{ scope: "agent", subject: agentSubject }] : [],
|
|
107
105
|
});
|
|
108
106
|
const namespace = resolveNamespace();
|
|
109
|
-
const relation =
|
|
107
|
+
const relation = opl_js_1.PERMIT_USE;
|
|
110
108
|
const result = {
|
|
111
109
|
total: catalog.length,
|
|
112
110
|
allowed: 0,
|
|
111
|
+
blocked: 0,
|
|
113
112
|
denied: 0,
|
|
114
113
|
errored: 0,
|
|
115
114
|
mode: live.mode,
|
|
@@ -124,8 +123,10 @@ async function probePermissionsCoverage(harness) {
|
|
|
124
123
|
});
|
|
125
124
|
if (probe.allowed)
|
|
126
125
|
result.allowed++;
|
|
127
|
-
else
|
|
126
|
+
else {
|
|
127
|
+
result.blocked++;
|
|
128
128
|
result.denied++;
|
|
129
|
+
}
|
|
129
130
|
}
|
|
130
131
|
catch (err) {
|
|
131
132
|
const ory = err;
|
|
@@ -274,14 +275,14 @@ async function collectPermissions(harness) {
|
|
|
274
275
|
return { ...base, coverageStatus: "no_user" };
|
|
275
276
|
// The probe read the mode live; prefer it over the cached config value.
|
|
276
277
|
const withMode = { ...base, mode: coverage.mode, modeSource: coverage.modeSource };
|
|
277
|
-
if (coverage.errored > 0 && coverage.allowed === 0 && coverage.
|
|
278
|
+
if (coverage.errored > 0 && coverage.allowed === 0 && coverage.blocked === 0) {
|
|
278
279
|
return { ...withMode, coverageStatus: "probe_failed", coverage };
|
|
279
280
|
}
|
|
280
281
|
const status = coverage.allowed === coverage.total
|
|
281
|
-
? "
|
|
282
|
+
? "fully_allowed"
|
|
282
283
|
: coverage.allowed === 0
|
|
283
|
-
? "
|
|
284
|
-
: "
|
|
284
|
+
? "fully_blocked"
|
|
285
|
+
: "partially_blocked";
|
|
285
286
|
return { ...withMode, coverageStatus: status, coverage };
|
|
286
287
|
}
|
|
287
288
|
function isUserTokenUsable() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/argus",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Ory Argus: the core API for building authentication, authorization, and audit into AI agent harness plugins, extensions, and custom integrations",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ory.com",
|