@ory/argus 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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. */
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
- * Argus relies on more than relation-tuple *existence*: a subject may be granted
6
- * a tool and separately **blocked** from it, and the block must win. Keto
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 = users.includes(subject) && !blockedSubjects.includes(subject)
8
+ * use = !blockedSubjects.includes(subject)
11
9
  *
12
- * Keto evaluates the permit in a single check, so a `blockedSubjects` relation
13
- * beats a `users` grant no matter how the grant was reached (direct subject or
14
- * subject-set expansion). Argus always checks the **`use` permit** (see
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: `users && !blockedSubjects`, evaluated natively by
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 `users` grant, the `blockedSubjects`
267
- * deny-override, and the `use` permit (`users && !blockedSubjects`) Argus
268
- * checks. The single definition of that class, used for both tool namespaces.
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 grantable in their own right: \`${subject_js_1.AGENT_NAMESPACE}:<clientId>\` is
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 grant may also name the shared role, so one membership covers every
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
- // Deny-override: any matching \`${exports.RELATION_BLOCKED_SUBJECTS}\` relation beats a
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: the members of a shared role a project grants tools to.
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 grant names
326
- // \`(${namespace}:<role>#${exports.RELATION_MEMBERS})\` as its subject, so adding a member grants
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.RELATION_USERS}\` is the set of subjects`,
412
- `// granted the tool; \`${exports.RELATION_BLOCKED_SUBJECTS}\` is an explicit deny-override. The`,
413
- `// \`${exports.PERMIT_USE}\` permit is the effective decision Argus checks: granted AND not`,
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
- "// grant / block / use model as the tool namespace above.",
406
+ "// default-allow / explicit-block model as the tool namespace above.",
421
407
  renderToolNamespaceClass(shellNamespace),
422
408
  "",
423
- "// The shared role a project grants tools to, so an admin adds a member",
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",
@@ -430,7 +415,8 @@ function buildAgentSecurityOpl(namespace = exports.DEFAULT_NAMESPACE, shellNames
430
415
  renderDelegationNamespaceClass(),
431
416
  "",
432
417
  "// Registered credential groups used to apply posture by harness or sub-agent type.",
433
- renderAgentTypeNamespaceClass(exports.AGENT_TYPE_NAMESPACE, subject_js_1.AGENT_NAMESPACE),
418
+ "// Delegated Sub-Agents also belong to their parent harness group.",
419
+ renderAgentTypeNamespaceClass(exports.AGENT_TYPE_NAMESPACE, `${subject_js_1.AGENT_NAMESPACE} | ${subject_js_1.SUBAGENT_NAMESPACE}`),
434
420
  "",
435
421
  renderAgentTypeNamespaceClass(exports.SUBAGENT_TYPE_NAMESPACE, subject_js_1.SUBAGENT_NAMESPACE),
436
422
  "",
@@ -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, the per-tool
14
- * grants, explicit blocks, and the observe/enforce posture. That is what lets a
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.
@@ -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, the per-tool
15
- * grants, explicit blocks, and the observe/enforce posture. That is what lets a
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. Tool grants, explicit blocks,");
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(" - per-tool grants for a user, agent, or group");
101
- console.error(" - explicit blocks (a deny that beats any grant)");
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 coverage to report. The rest of the plugin is unaffected.");
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
- : r.allowed
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, ${denied} denied, ${blocked} blocked, ${errored} errored.`);
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 shellDenied = 0;
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
- shellDenied = shellResults.filter((r) => r.error === undefined && !r.allowed).length;
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, ${shellDenied} denied, ` +
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 that overrides any grant.");
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 effective `use` permit, exactly as the runtime gate does.
203
- const relation = (0, permissions_js_1.resolveCheckRelation)("users");
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
- let blocked = false;
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;
@@ -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` — the subject simply has no `use` grant on the object
38
- * (the legacy "deny by absence" case).
39
- * - `explicit_block` — a `blocked` relation matched the subject, so the
40
- * `access` permit's deny-override fired even if a `use` grant exists.
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
- * Map the logical grant relation a caller passes (`use`) onto the relation or
50
- * permit Argus actually checks. A `use` check always routes through the
51
- * block-aware `access` permit (`use && !blocked`), so an explicit block is
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
  /**
@@ -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
- * Map the logical grant relation a caller passes (`use`) onto the relation or
46
- * permit Argus actually checks. A `use` check always routes through the
47
- * block-aware `access` permit (`use && !blocked`), so an explicit block is
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
- // Route each word through the block-aware `access` permit (like the
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.RELATION_USERS,
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
- // Disambiguate an explicit block from a plain missing grant on the first
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 = primaryBlock
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.RELATION_USERS, ...subject },
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
- // Route a `use` grant check through the block-aware `access` permit so
592
- // explicit blocks are always respected.
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
- // On a deny, disambiguate "explicitly blocked" from "never granted" so the
628
- // audit trail and denial messaging can tell them apart. Best-effort.
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
- : await probeBlockReason(client, check);
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,
@@ -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, the per-tool grants, explicit blocks,
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
@@ -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, the per-tool grants, explicit blocks,
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("Tool grants, explicit blocks, and the observe/enforce posture are managed in the Ory Console (Agent Security).");
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 permission coverage:");
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:");
@@ -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
- * coverage of the harness's built-in tool catalog.
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
@@ -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
- * coverage of the harness's built-in tool catalog.
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, denied, errored } = probe;
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 && denied === 0) {
215
- console.log(` Coverage: probe failed (${probe.errorCode ?? "unknown"})`);
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
- ? `fully granted (${allowed}/${total} built-in tools)`
219
+ ? `all allowed (${allowed}/${total} built-in tools)`
220
220
  : allowed === 0
221
- ? `no grants (0/${total} built-in tools)`
222
- : `partial (${allowed}/${total} built-in tools allowed)`;
223
- console.log(` Coverage: ${status}`);
224
- if (denied > 0) {
225
- console.log(" grant the missing tools in the Ory Console (Agent Security)");
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 / Coverage lines for every path that can't probe. */
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(` Coverage: ${coverage}`);
235
+ console.log(` Tool access: ${coverage}`);
236
236
  }
237
237
  function isUserTokenUsable() {
238
238
  const tokens = (0, auth_store_js_1.loadTokens)();
@@ -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 / denied / errored on, plus the
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 route through {@link resolveCheckRelation}, i.e. the `access` permit
41
- * the same relation the runtime gate evaluates so a tool carrying an explicit
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" | "fully_granted" | "partial" | "no_grants" | "probe_failed";
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. */