@ory/argus 1.0.0 → 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.
@@ -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. Promote to enforce once the tools are granted. The posture is a
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 grant permissions. Those are provisioned in the Ory Console
380
- (Agent Security); `@ory/argus` only reads them. Locally, the dev stack's
381
- seed step grants the catalog for you.
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: denies pass through but each is
248
- recorded as a `permission.observe_deny` activity event. Once the user has confirmed
249
- the deny set is what they expect, an admin promotes the posture to `enforce` in
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
- On a hosted Ory project, grant `use` on each Activity name (`send_email`, …) in
254
- the Ory Console (Agent Security) before promoting the posture there — otherwise
255
- enforcement blocks every Activity. See {{REF_PERMISSIONS_ONBOARDING}}.
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, kick the Workflow with an Activity name the
258
- local stack never granted, and watch it fail with the `Ory denied use of …`
259
- error in the Temporal Web UI.
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 a fresh install onto Ory Permissions for AI agent tool calls. Use when the user has just installed the Ory plugin and wants per-tool authorization enforced without first getting blocked by missing permissions. Covers connecting the plugin, reading coverage in observe mode, and what has to happen in the Ory Console (grants, blocks, enforce posture).
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 onto Ory Permissions for Agent Tool Calls
6
+ # Onboard Ory Permissions for Agent Tool Calls
7
7
 
8
- You are helping a user move a freshly-installed Ory agent plugin from
9
- "disconnected" to "permissions are enforcing", without the common
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.** Everything
14
- that provisions the project the permission model, per-tool grants,
15
- explicit blocks, and the observe/enforce posture is done in the **Ory
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
- Two things govern behavior:
15
+ Native policy is simple:
22
16
 
23
- - **Whether Agent Security is connected** it runs when a project URL,
24
- canonical Agent Security origin, and public OAuth2 client id resolve. Existing
25
- configurations fall back to the project URL when the canonical origin is
26
- unset. Not connected means no login and no permission checks, with
27
- tool calls recorded locally. Skills, commands, the local dev stack, and
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
- The journey: **install connect → observe → grant in the Console →
36
- enforce in the Console.**
23
+ ## 1. Connect Agent Security
37
24
 
38
- ## Step 1: Connect the plugin to the project
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
- - **project URL** — the project's SDK URL (`https://<slug>.projects.oryapis.com`).
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
- What you want to see:
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
- ## Step 2: Look at the current permission posture
40
+ ## 2. Review Native Tool Access
72
41
 
73
42
  ```sh
74
43
  {{NPX}} permissions
75
44
  ```
76
45
 
77
- This prints:
46
+ The report shows:
78
47
 
79
- - The live **permission mode** (`observe` or `enforce`) and whether it
80
- came from the project, a cached value, or the default.
81
- - The **subject** the user identity permissions are checked against.
82
- - For each tool in this harness's built-in catalog, **allowed / denied /
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
- On a freshly-connected project you will usually see every tool reported
87
- as **denied** no grants exist for this user yet. In observe mode that
88
- blocks nobody: tool calls run through, and you are seeing what *would*
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
- If status prints `Mode: enforce` while tools are denied, tool calls
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
- ## Step 3: Run a real session in observe mode
59
+ To block a native tool, write a `blockedSubjects` relation in the Ory Console:
96
60
 
97
- Observe mode is the discovery phase. Use the harness normally, then look
98
- at what was recorded:
99
-
100
- ```sh
101
- {{NPX}} status # summary + recent activity
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
- Launch the harness with `ORY_AGENT_DEBUG=true` to include verbose diagnostics in
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
- In the Ory Console → **Agent Security**, grant the subject `use` on the
114
- tools it needs. The grant shape the runtime checks is:
115
-
116
- ```
117
- namespace: AgentTool (or whatever ORY_PERMISSION_NAMESPACE is set to)
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
- Notes worth passing on to the user:
124
-
125
- - The subject is a **SubjectSet** (`User:<id>`), which is what the
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
- ## Step 5: Promote to enforce in the Console
81
+ MCP is intentionally different: continue granting MCP server/tool access with
82
+ its existing `users`/`use` and `invoke` relations.
140
83
 
141
- Once the tools the user actually needs report `allowed`, an admin flips
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
- Every session reads the live mode, so the change takes effect without
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
- ## Step 6: Verify a real session under enforce
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
- Start a normal agent session and confirm:
97
+ ## 5. Verify Enforcement
156
98
 
157
- - **Allowed tools** invoke without complaint.
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
- If a tool is unexpectedly blocked, run `{{NPX}} permissions` to
164
- see whether it is `denied` (no grant) or `blocked` (an explicit
165
- deny-override), then fix it in the Console and re-test.
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
- ## Explicit blocks (deny-override)
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 the plugin to the canonical Agent Security broker. |
185
- | `{{NPX}} configure --disconnect` | Turn sign-in and permission checks back off. Skills, commands, the local stack, and activity logging are unaffected. |
186
- | `{{NPX}} permissions` | Print the live mode + per-tool allowed/denied/blocked breakdown. Read-only. |
187
- | `{{NPX}} status` | Configuration, identities, permission coverage, recent activity. |
188
-
189
- Done in the **Ory Console** (Agent Security), not from the CLI: applying
190
- the permission model, granting `use` on tools, writing explicit blocks,
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: "users", ...subjectRef };
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",
@@ -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
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "repo": "ory-agent-plugins",
3
- "commit": "6f11a1695f60ec11e7905c6a73f8683235c5e4df",
4
- "commitShort": "6f11a16",
3
+ "commit": "fc87fd73772d79007d1072c605ede76de42792ce",
4
+ "commitShort": "fc87fd7",
5
5
  "branch": "main",
6
- "commitDate": "2026-08-29T19:17:15-07:00",
6
+ "commitDate": "2026-08-30T17:10:01-07:00",
7
7
  "dirty": false,
8
- "builtAt": "2026-08-30T02:20:53.108Z"
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. Tool grants, explicit blocks,");
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):");
@@ -200,9 +200,12 @@ function runHarnessContractSuite(adapter) {
200
200
  else {
201
201
  (0, vitest_1.expect)(blockEvents).toHaveLength(0);
202
202
  }
203
- // Observe-mode denies also emit the audit event; enforce must not.
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, vitest_1.expect)(observeEvents).toHaveLength(check === "denied" && mode === "observe" ? 1 : 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;
@@ -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(` Permissions: ${result.permissions.tuples} entries in '${result.permissions.namespace}' for ${result.permissions.subject}`);
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("");
@@ -53,7 +53,7 @@ export interface SeedResult {
53
53
  /** OAuth2 authorization_code+PKCE client for interactive logins. */
54
54
  client: SeededOAuth2Client;
55
55
  };
56
- /** Permission tuples written against the user's subject. */
56
+ /** Native baseline permission tuples written against the user's subject (always zero). */
57
57
  permissions: {
58
58
  namespace: string;
59
59
  subject: string;
@@ -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. Permission tuples written against the user's identity since the
213
- // user is the subject of permission checks in the plugin handlers.
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(` Creating permissions for ${COMMON_TOOLS.length} tools...\n`);
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: tupleCount },
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
- * Argus relies on more than relation-tuple *existence*: a subject may be granted
5
- * a tool and separately **blocked** from it, and the block must win. Keto
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 = users.includes(subject) && !blockedSubjects.includes(subject)
7
+ * use = !blockedSubjects.includes(subject)
10
8
  *
11
- * Keto evaluates the permit in a single check, so a `blockedSubjects` relation
12
- * beats a `users` grant no matter how the grant was reached (direct subject or
13
- * subject-set expansion). Argus always checks the **`use` permit** (see
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: `users && !blockedSubjects`, evaluated natively by
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 `users` grant, the `blockedSubjects`
240
- * deny-override, and the `use` permit (`users && !blockedSubjects`) Argus
241
- * checks. The single definition of that class, used for both tool namespaces.
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: the members of a shared role a project grants tools to.
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. */