@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/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/auth.d.ts +4 -0
- package/dist/auth.js +4 -0
- 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 +23 -37
- 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
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",
|
|
@@ -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
|
-
|
|
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,
|
|
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. */
|