@ory/argus 0.13.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -45
- package/assets/commands/temporal-up.md +1 -1
- package/assets/skills/auth-setup/SKILL.md +1 -1
- package/assets/skills/local-dev/SKILL.md +17 -7
- package/assets/skills/ory-build-agent/SKILL.md +43 -97
- package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -18
- package/assets/skills/ory-temporal-worker/SKILL.md +38 -42
- package/assets/skills/permissions-onboarding/SKILL.md +131 -104
- package/dist/adapters.d.ts +93 -30
- package/dist/adapters.js +464 -136
- package/dist/agent-auth.d.ts +258 -68
- package/dist/agent-auth.js +998 -202
- package/dist/auth-store.d.ts +37 -2
- package/dist/auth-store.js +37 -3
- package/dist/auth.d.ts +40 -4
- package/dist/auth.js +247 -19
- package/dist/bash-parser.d.ts +98 -0
- package/dist/bash-parser.js +396 -0
- package/dist/branding.d.ts +128 -0
- package/dist/branding.js +151 -0
- package/dist/build-info.json +4 -4
- package/dist/cli-invocation.d.ts +1 -1
- package/dist/cli-invocation.js +2 -1
- package/dist/cli.d.ts +20 -29
- package/dist/cli.js +271 -278
- package/dist/client.d.ts +175 -138
- package/dist/client.js +672 -391
- package/dist/config.d.ts +249 -57
- package/dist/config.js +486 -62
- package/dist/context.d.ts +10 -0
- package/dist/context.js +21 -0
- package/dist/contract-suite.d.ts +8 -8
- package/dist/contract-suite.js +88 -69
- package/dist/denial.d.ts +36 -3
- package/dist/denial.js +79 -10
- package/dist/event-reporter.d.ts +77 -0
- package/dist/event-reporter.js +776 -0
- package/dist/external-registrations-main.d.ts +10 -0
- package/dist/external-registrations-main.js +38 -0
- package/dist/external-registrations.d.ts +79 -0
- package/dist/external-registrations.js +188 -0
- package/dist/help-cli.d.ts +39 -0
- package/dist/help-cli.js +55 -0
- package/dist/hook-timeout.d.ts +64 -0
- package/dist/hook-timeout.js +88 -0
- package/dist/index.d.ts +31 -19
- package/dist/index.js +182 -31
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +38 -6
- package/dist/local/cli.js +11 -6
- package/dist/local/configs.d.ts +74 -18
- package/dist/local/configs.js +291 -84
- package/dist/local/health.d.ts +14 -0
- package/dist/local/health.js +50 -4
- package/dist/local/index.d.ts +2 -2
- package/dist/local/index.js +24 -10
- package/dist/local/manager.d.ts +20 -1
- package/dist/local/manager.js +160 -39
- package/dist/local/ports.d.ts +158 -0
- package/dist/local/ports.js +443 -0
- package/dist/local/seed.d.ts +22 -25
- package/dist/local/seed.js +88 -56
- package/dist/logger.d.ts +54 -25
- package/dist/logger.js +329 -63
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +10 -5
- package/dist/mirror-bootstrap.d.ts +48 -0
- package/dist/mirror-bootstrap.js +254 -0
- package/dist/opl.d.ts +289 -0
- package/dist/opl.js +446 -0
- package/dist/permission-mode.d.ts +87 -0
- package/dist/permission-mode.js +307 -0
- package/dist/permissions-cli.d.ts +13 -49
- package/dist/permissions-cli.js +154 -348
- package/dist/permissions.d.ts +148 -38
- package/dist/permissions.js +591 -45
- package/dist/post-install.d.ts +33 -0
- package/dist/post-install.js +127 -0
- package/dist/read-credential.d.ts +65 -0
- package/dist/read-credential.js +86 -0
- package/dist/registry/cli.js +5 -2
- package/dist/registry/config.d.ts +0 -17
- package/dist/registry/config.js +0 -23
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.js +2 -2
- package/dist/registry/manager.d.ts +4 -21
- package/dist/registry/manager.js +83 -55
- package/dist/runtime-credential.d.ts +140 -0
- package/dist/runtime-credential.js +572 -0
- package/dist/runtime.d.ts +408 -0
- package/dist/runtime.js +748 -0
- package/dist/setup.d.ts +23 -28
- package/dist/setup.js +57 -84
- package/dist/status-cli.d.ts +29 -13
- package/dist/status-cli.js +124 -144
- package/dist/status-data.d.ts +195 -0
- package/dist/status-data.js +333 -0
- package/dist/status-system.d.ts +24 -0
- package/dist/status-system.js +56 -0
- package/dist/subject.d.ts +126 -20
- package/dist/subject.js +215 -30
- package/dist/testing.d.ts +74 -38
- package/dist/testing.js +185 -68
- package/dist/tool-catalog.d.ts +53 -11
- package/dist/tool-catalog.js +164 -13
- package/dist/tool-metadata.d.ts +7 -6
- package/dist/tool-metadata.js +6 -5
- package/dist/types.d.ts +11 -1
- package/dist/uninstall.d.ts +74 -19
- package/dist/uninstall.js +224 -49
- package/dist/user-login.d.ts +22 -16
- package/dist/user-login.js +67 -96
- package/dist/watch-cli.d.ts +6 -0
- package/dist/watch-cli.js +217 -0
- package/package.json +3 -11
- package/dist/dev.d.ts +0 -103
- package/dist/dev.js +0 -584
- package/dist/interactive-setup.d.ts +0 -165
- package/dist/interactive-setup.js +0 -1546
- package/dist/local/jaeger-main.d.ts +0 -13
- package/dist/local/jaeger-main.js +0 -85
- package/dist/local/jaeger.d.ts +0 -50
- package/dist/local/jaeger.js +0 -162
- package/dist/otel/exporter.d.ts +0 -17
- package/dist/otel/exporter.js +0 -12
- package/dist/otel/index.d.ts +0 -2
- package/dist/otel/index.js +0 -8
- package/dist/otel/otlp.d.ts +0 -103
- package/dist/otel/otlp.js +0 -385
- package/dist/tracer.d.ts +0 -190
- package/dist/tracer.js +0 -481
- package/dist/watch-sandbox.d.ts +0 -9
- package/dist/watch-sandbox.js +0 -81
package/dist/permissions-cli.js
CHANGED
|
@@ -1,76 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Shared `permissions` CLI implementation. Each harness's `cli/main.ts`
|
|
4
|
-
* delegates to {@link runPermissionsCommand}, passing the harness's name
|
|
5
|
-
*
|
|
4
|
+
* delegates to {@link runPermissionsCommand}, passing the harness's name so the
|
|
5
|
+
* report can scope to that harness's tool catalog.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* `permissions` takes **no subcommand** — it is a single read-only report: the
|
|
8
|
+
* live permission mode (read from the Ory project), the subject being checked,
|
|
9
|
+
* and the allow / deny / blocked breakdown across the harness's known tool
|
|
10
|
+
* catalog. (`permissions status` is still accepted; it was the name through
|
|
11
|
+
* v0.14, and the report is the same.)
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* cached; can be re-run by hand any time.
|
|
16
|
-
* - `observe` — Persist `permissionMode = "observe"` in the shared
|
|
17
|
-
* config (denies log but don't block).
|
|
18
|
-
* - `enforce` — Persist `permissionMode = "enforce"` in the shared
|
|
19
|
-
* config (denies block — the production posture).
|
|
20
|
-
*
|
|
21
|
-
* All four are non-destructive on existing config and idempotent on
|
|
22
|
-
* Keto: re-running them is always safe.
|
|
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
|
|
16
|
+
* plugin be installed with nothing but a project URL — the public OAuth2 client
|
|
17
|
+
* id has a reserved default, with no workspace privilege, project API key, or admin write path. The
|
|
18
|
+
* plugin is a read-only client of the project's permission state.
|
|
23
19
|
*/
|
|
24
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
25
|
-
if (k2 === undefined) k2 = k;
|
|
26
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
27
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
28
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
29
|
-
}
|
|
30
|
-
Object.defineProperty(o, k2, desc);
|
|
31
|
-
}) : (function(o, m, k, k2) {
|
|
32
|
-
if (k2 === undefined) k2 = k;
|
|
33
|
-
o[k2] = m[k];
|
|
34
|
-
}));
|
|
35
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
36
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
37
|
-
}) : function(o, v) {
|
|
38
|
-
o["default"] = v;
|
|
39
|
-
});
|
|
40
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
41
|
-
var ownKeys = function(o) {
|
|
42
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
43
|
-
var ar = [];
|
|
44
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
45
|
-
return ar;
|
|
46
|
-
};
|
|
47
|
-
return ownKeys(o);
|
|
48
|
-
};
|
|
49
|
-
return function (mod) {
|
|
50
|
-
if (mod && mod.__esModule) return mod;
|
|
51
|
-
var result = {};
|
|
52
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
53
|
-
__setModuleDefault(result, mod);
|
|
54
|
-
return result;
|
|
55
|
-
};
|
|
56
|
-
})();
|
|
57
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
21
|
exports.runPermissionsCommand = runPermissionsCommand;
|
|
59
|
-
exports.isUserIdentityCached = isUserIdentityCached;
|
|
60
|
-
exports.printPermissionsOnboardingHelp = printPermissionsOnboardingHelp;
|
|
61
|
-
exports.maybeAutoBootstrap = maybeAutoBootstrap;
|
|
62
22
|
const config_js_1 = require("./config.js");
|
|
23
|
+
const permission_mode_js_1 = require("./permission-mode.js");
|
|
63
24
|
const client_js_1 = require("./client.js");
|
|
64
25
|
const logger_js_1 = require("./logger.js");
|
|
65
26
|
const agent_auth_js_1 = require("./agent-auth.js");
|
|
66
|
-
const auth_store_js_1 = require("./auth-store.js");
|
|
67
27
|
const tool_catalog_js_1 = require("./tool-catalog.js");
|
|
68
28
|
const subject_js_1 = require("./subject.js");
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const ENV_PERMISSION_MODE = "ORY_PERMISSION_MODE";
|
|
29
|
+
const permissions_js_1 = require("./permissions.js");
|
|
30
|
+
const opl_js_1 = require("./opl.js");
|
|
72
31
|
function resolveNamespace() {
|
|
73
|
-
return process.env.ORY_PERMISSION_NAMESPACE ?? "
|
|
32
|
+
return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTool";
|
|
74
33
|
}
|
|
75
34
|
/**
|
|
76
35
|
* Entry point invoked by each harness CLI's `permissions` case.
|
|
@@ -81,23 +40,28 @@ function resolveNamespace() {
|
|
|
81
40
|
*/
|
|
82
41
|
async function runPermissionsCommand(binName, harness, args) {
|
|
83
42
|
const sub = args[0];
|
|
84
|
-
if (
|
|
43
|
+
if (sub === "--help" || sub === "-h") {
|
|
85
44
|
printPermissionsHelp(binName);
|
|
86
|
-
return
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
// No subcommand is the command: print the report. The report spins up a client
|
|
48
|
+
// and probes per tool, emitting debug/info events — quiet the stderr firehose
|
|
49
|
+
// so those don't interleave with the human-facing output under
|
|
50
|
+
// ORY_AGENT_DEBUG=true; the log file still captures everything.
|
|
51
|
+
//
|
|
52
|
+
// `status` is accepted as the v0.14 spelling of the same thing.
|
|
53
|
+
if (!sub || sub === "status") {
|
|
54
|
+
return await (0, logger_js_1.withQuietStderr)(() => runPermissionsStatus(binName, harness));
|
|
87
55
|
}
|
|
88
56
|
switch (sub) {
|
|
89
|
-
// `status` and `bootstrap` spin up a client and probe/grant per tool,
|
|
90
|
-
// emitting debug/info events. Quiet the stderr firehose so those don't
|
|
91
|
-
// interleave with the command's human-facing output under
|
|
92
|
-
// ORY_AGENT_DEBUG=true; the log file still captures everything.
|
|
93
|
-
case "status":
|
|
94
|
-
return await (0, logger_js_1.withQuietStderr)(() => runPermissionsStatus(binName, harness));
|
|
95
57
|
case "bootstrap":
|
|
96
|
-
|
|
58
|
+
case "block":
|
|
59
|
+
case "unblock":
|
|
97
60
|
case "observe":
|
|
98
|
-
return runPermissionsSetMode(binName, "observe");
|
|
99
61
|
case "enforce":
|
|
100
|
-
|
|
62
|
+
case "install-schema":
|
|
63
|
+
printProvisioningMovedToConsole(binName, sub);
|
|
64
|
+
return 1;
|
|
101
65
|
default:
|
|
102
66
|
console.error(`Unknown permissions subcommand: ${sub}`);
|
|
103
67
|
console.error(`Run "${binName} permissions --help" for usage.`);
|
|
@@ -105,39 +69,40 @@ async function runPermissionsCommand(binName, harness, args) {
|
|
|
105
69
|
}
|
|
106
70
|
}
|
|
107
71
|
function printPermissionsHelp(binName) {
|
|
108
|
-
console.log(`Usage: ${binName} permissions
|
|
72
|
+
console.log(`Usage: ${binName} permissions`);
|
|
109
73
|
console.log("");
|
|
110
|
-
console.log("
|
|
111
|
-
console.log("
|
|
112
|
-
console.log("
|
|
113
|
-
console.log(" observe Switch to observe mode (denies log but don't block)");
|
|
114
|
-
console.log(" enforce Switch to enforce mode (denies block — production posture)");
|
|
74
|
+
console.log("Prints the live permission mode, the subject being checked, and the");
|
|
75
|
+
console.log("allow / deny / blocked breakdown across this harness's built-in tools.");
|
|
76
|
+
console.log("Read-only — it changes nothing.");
|
|
115
77
|
console.log("");
|
|
116
78
|
console.log("Permission mode controls what happens when an Ory check returns deny:");
|
|
117
|
-
console.log(" observe — Log
|
|
79
|
+
console.log(" observe — Log an audit event and allow the tool through. The default.");
|
|
118
80
|
console.log(" enforce — Block the tool. The production posture.");
|
|
119
81
|
console.log("");
|
|
120
|
-
console.log(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
(0, config_js_1.saveConfig)({ permissionMode: mode });
|
|
124
|
-
const configPath = (0, config_js_1.getConfigPath)();
|
|
125
|
-
console.log(`Permission mode set to '${mode}'.`);
|
|
126
|
-
console.log(` Saved to: ${configPath}`);
|
|
82
|
+
console.log("The plugin only ever *reads* permissions. Tool grants, explicit blocks,");
|
|
83
|
+
console.log("the permission model, and the observe/enforce posture are all managed in");
|
|
84
|
+
console.log("the Ory Console (Agent Security) by someone with access to the project.");
|
|
127
85
|
console.log("");
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
console.
|
|
139
|
-
console.
|
|
140
|
-
|
|
86
|
+
console.log(`A subject is in 'enforce' when granted ${opl_js_1.PERMISSION_MODE_NAMESPACE}:${opl_js_1.PERMISSION_MODE_OBJECT}#${opl_js_1.RELATION_ENFORCED_SUBJECTS},`);
|
|
87
|
+
console.log("otherwise 'observe'.");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The subcommands that used to write to the project — grants, explicit blocks,
|
|
91
|
+
* the deny posture, and the permission-model installer. All of that is now
|
|
92
|
+
* provisioned in the Ory Console, so these print where to go and exit non-zero
|
|
93
|
+
* rather than silently doing nothing.
|
|
94
|
+
*/
|
|
95
|
+
function printProvisioningMovedToConsole(binName, attempted) {
|
|
96
|
+
console.error(`'permissions ${attempted}' is no longer part of the plugin.`);
|
|
97
|
+
console.error("");
|
|
98
|
+
console.error("Permissions are provisioned in the Ory Console (Agent Security):");
|
|
99
|
+
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)");
|
|
102
|
+
console.error(" - the observe / enforce posture, project-wide or per principal");
|
|
103
|
+
console.error("");
|
|
104
|
+
console.error("The plugin reads that state and enforces it. To see what it resolves:");
|
|
105
|
+
console.error(` ${binName} permissions`);
|
|
141
106
|
}
|
|
142
107
|
async function runPermissionsStatus(binName, harness) {
|
|
143
108
|
const resolved = (0, config_js_1.resolveConfig)();
|
|
@@ -146,20 +111,18 @@ async function runPermissionsStatus(binName, harness) {
|
|
|
146
111
|
console.log(`Permission status (${harness})`);
|
|
147
112
|
console.log("==========================================");
|
|
148
113
|
console.log("");
|
|
149
|
-
console.log(`Mode: ${resolved.permissionMode}${formatModeSource(resolved.permissionModeSource)}`);
|
|
150
|
-
console.log(` (other modes: ${otherModes(resolved.permissionMode).join(", ")})`);
|
|
151
114
|
console.log(`Namespace: ${namespace}`);
|
|
152
115
|
console.log(`Project URL: ${resolved.projectUrl ?? "(not set)"}${formatSourceSuffix(resolved.projectUrlSource)}`);
|
|
153
|
-
console.log(`
|
|
116
|
+
console.log(`OAuth2 Client: ${resolved.oauth2ClientId ?? "(not set)"}${formatSourceSuffix(resolved.oauth2ClientIdSource)}`);
|
|
117
|
+
console.log(`Agent Security: ${resolved.security.connected ? "connected" : `not connected (${(0, config_js_1.describeSecurityGap)(resolved.security)})`}`);
|
|
118
|
+
console.log(`Permission model: checks route through the '${opl_js_1.PERMIT_USE}' permit (explicit blocks honored)`);
|
|
154
119
|
console.log("");
|
|
155
|
-
if (resolved.
|
|
156
|
-
console.log("Ory is
|
|
157
|
-
console.log(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
console.log("No project URL configured — cannot probe permission coverage.");
|
|
162
|
-
console.log(`Configure first: ${binName} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`);
|
|
120
|
+
if (!resolved.security.connected) {
|
|
121
|
+
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.");
|
|
123
|
+
console.log("");
|
|
124
|
+
console.log(`Connect with: ${binName} configure --project-url <URL>`);
|
|
125
|
+
console.log("The project URL comes from the Ory Console (Agent Security).");
|
|
163
126
|
return 0;
|
|
164
127
|
}
|
|
165
128
|
if (catalog.length === 0) {
|
|
@@ -179,12 +142,22 @@ async function runPermissionsStatus(binName, harness) {
|
|
|
179
142
|
console.log("ORY_USER_SUBJECT_ID to probe against a known subject.");
|
|
180
143
|
return 0;
|
|
181
144
|
}
|
|
145
|
+
// Resolve the LIVE mode (a Keto read) rather than the cached config value, so
|
|
146
|
+
// status reflects exactly what the gate would decide.
|
|
147
|
+
const agentSubject = (0, subject_js_1.resolveAgentSubject)(client);
|
|
148
|
+
const live = await (0, permission_mode_js_1.resolvePermissionMode)(client, subject, {
|
|
149
|
+
additionalSubjects: agentSubject ? [{ scope: "agent", subject: agentSubject }] : [],
|
|
150
|
+
});
|
|
182
151
|
console.log(`Subject: ${subjectId}`);
|
|
152
|
+
console.log(`Mode: ${live.mode}${formatModeSource(live.source, resolved.permissionModeCache?.fetchedAt)}`);
|
|
153
|
+
console.log(` (other modes: ${otherModes(live.mode).join(", ")})`);
|
|
154
|
+
console.log(` Set in the Ory Console (Agent Security): grant ${opl_js_1.PERMISSION_MODE_NAMESPACE}:${opl_js_1.PERMISSION_MODE_OBJECT}#${opl_js_1.RELATION_ENFORCED_SUBJECTS} to enforce.`);
|
|
183
155
|
console.log(`Tool catalog: ${catalog.length} tools`);
|
|
184
156
|
console.log("");
|
|
185
157
|
const results = await probeCatalog(client, catalog, namespace, subject);
|
|
186
|
-
const allowed = results.filter((r) => r.allowed).length;
|
|
187
|
-
const
|
|
158
|
+
const allowed = results.filter((r) => r.error === undefined && r.allowed).length;
|
|
159
|
+
const blocked = results.filter((r) => r.blocked).length;
|
|
160
|
+
const denied = results.filter((r) => r.error === undefined && !r.allowed && !r.blocked).length;
|
|
188
161
|
const errored = results.filter((r) => r.error !== undefined).length;
|
|
189
162
|
const widest = Math.max(...catalog.map((t) => t.length), 4);
|
|
190
163
|
console.log(` ${"Tool".padEnd(widest)} Status`);
|
|
@@ -192,34 +165,55 @@ async function runPermissionsStatus(binName, harness) {
|
|
|
192
165
|
for (const r of results) {
|
|
193
166
|
const status = r.error
|
|
194
167
|
? `error (${r.error})`
|
|
195
|
-
: r.
|
|
196
|
-
? "
|
|
197
|
-
:
|
|
168
|
+
: r.blocked
|
|
169
|
+
? "blocked"
|
|
170
|
+
: r.allowed
|
|
171
|
+
? "allowed"
|
|
172
|
+
: "denied";
|
|
198
173
|
console.log(` ${r.tool.padEnd(widest)} ${status}`);
|
|
199
174
|
}
|
|
200
175
|
console.log("");
|
|
201
|
-
console.log(`Summary: ${allowed} allowed, ${denied} denied, ${errored} errored.`);
|
|
202
|
-
|
|
176
|
+
console.log(`Summary: ${allowed} allowed, ${denied} denied, ${blocked} blocked, ${errored} errored.`);
|
|
177
|
+
// Shell sub-tools (issue #76): a compact count rather than 100+ rows.
|
|
178
|
+
let shellDenied = 0;
|
|
179
|
+
if ((0, tool_catalog_js_1.getShellToolCatalog)(harness).length > 0) {
|
|
180
|
+
const shellNamespace = (0, tool_catalog_js_1.resolveShellCommandNamespace)();
|
|
181
|
+
const shellWords = (0, tool_catalog_js_1.getShellCommandCatalog)();
|
|
182
|
+
const shellResults = await probeCatalog(client, shellWords, shellNamespace, subject);
|
|
183
|
+
const sAllowed = shellResults.filter((r) => r.allowed).length;
|
|
184
|
+
shellDenied = shellResults.filter((r) => r.error === undefined && !r.allowed).length;
|
|
185
|
+
const sErrored = shellResults.filter((r) => r.error !== undefined).length;
|
|
186
|
+
console.log(`Shell commands (${shellNamespace}): ${sAllowed} allowed, ${shellDenied} denied, ` +
|
|
187
|
+
`${sErrored} errored of ${shellWords.length}.`);
|
|
188
|
+
}
|
|
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
|
+
if (blocked > 0) {
|
|
203
195
|
console.log("");
|
|
204
|
-
console.log(
|
|
205
|
-
console.log("
|
|
196
|
+
console.log("'blocked' tools carry an explicit block that overrides any grant.");
|
|
197
|
+
console.log("Remove it in the Ory Console (Agent Security).");
|
|
206
198
|
}
|
|
207
199
|
return 0;
|
|
208
200
|
}
|
|
209
201
|
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");
|
|
210
204
|
const rows = [];
|
|
211
205
|
for (const tool of catalog) {
|
|
212
|
-
const check = {
|
|
213
|
-
namespace,
|
|
214
|
-
object: tool,
|
|
215
|
-
relation: "use",
|
|
216
|
-
...subject,
|
|
217
|
-
};
|
|
218
206
|
try {
|
|
219
|
-
const result = await client.checkPermission(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
207
|
+
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 });
|
|
223
217
|
}
|
|
224
218
|
catch (err) {
|
|
225
219
|
const ory = err;
|
|
@@ -228,198 +222,39 @@ async function probeCatalog(client, catalog, namespace, subject) {
|
|
|
228
222
|
}
|
|
229
223
|
return rows;
|
|
230
224
|
}
|
|
231
|
-
async function runPermissionsBootstrap(binName, harness, args) {
|
|
232
|
-
const dryRun = args.includes("--dry-run") || args.includes("-n");
|
|
233
|
-
const resolved = (0, config_js_1.resolveConfig)();
|
|
234
|
-
const namespace = resolveNamespace();
|
|
235
|
-
const catalog = (0, tool_catalog_js_1.getToolCatalog)(harness);
|
|
236
|
-
if (resolved.auditOnly) {
|
|
237
|
-
console.error("Ory is in audit-only mode (kill-switch). Nothing to bootstrap.");
|
|
238
|
-
console.error(`Re-enable Ory: ${binName} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`);
|
|
239
|
-
return 1;
|
|
240
|
-
}
|
|
241
|
-
if (!resolved.projectUrl) {
|
|
242
|
-
console.error("No ORY_PROJECT_URL configured — cannot write permissions.");
|
|
243
|
-
console.error(`Configure first: ${binName} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`);
|
|
244
|
-
return 1;
|
|
245
|
-
}
|
|
246
|
-
if (catalog.length === 0) {
|
|
247
|
-
console.error(`No tool catalog known for harness "${harness}".`);
|
|
248
|
-
console.error(`Known harnesses: ${tool_catalog_js_1.KNOWN_HARNESSES.join(", ")}`);
|
|
249
|
-
return 1;
|
|
250
|
-
}
|
|
251
|
-
const client = client_js_1.OryAgentClient.fromEnv(harness);
|
|
252
|
-
await (0, agent_auth_js_1.ensureAgentIdentity)(client, { projectUrl: resolved.projectUrl }).catch(() => {
|
|
253
|
-
/* best-effort; the write may still succeed with the API key or no auth (local Keto) */
|
|
254
|
-
});
|
|
255
|
-
const subject = (0, subject_js_1.resolveUserSubject)(client);
|
|
256
|
-
const subjectId = (0, subject_js_1.subjectLabel)(subject);
|
|
257
|
-
if (subjectId === "agent:unknown") {
|
|
258
|
-
console.error("No user identity resolved — refusing to write permissions for an unknown subject.");
|
|
259
|
-
console.error("");
|
|
260
|
-
console.error("Either:");
|
|
261
|
-
console.error(" - run the harness once with ORY_USER_LOGIN=true so a user token is cached, or");
|
|
262
|
-
console.error(" - set ORY_USER_SUBJECT_ID=<id> to target a known subject.");
|
|
263
|
-
return 1;
|
|
264
|
-
}
|
|
265
|
-
console.log(`Bootstrapping ${catalog.length} permissions for ${harness}`);
|
|
266
|
-
console.log("==========================================");
|
|
267
|
-
console.log("");
|
|
268
|
-
console.log(`Namespace: ${namespace}`);
|
|
269
|
-
console.log(`Subject: ${subjectId}`);
|
|
270
|
-
console.log(`Tools: ${catalog.join(", ")}`);
|
|
271
|
-
if (dryRun) {
|
|
272
|
-
console.log("");
|
|
273
|
-
console.log("(--dry-run) No permissions will be written.");
|
|
274
|
-
}
|
|
275
|
-
console.log("");
|
|
276
|
-
let created = 0;
|
|
277
|
-
let existed = 0;
|
|
278
|
-
const failures = [];
|
|
279
|
-
for (const tool of catalog) {
|
|
280
|
-
const check = {
|
|
281
|
-
namespace,
|
|
282
|
-
object: tool,
|
|
283
|
-
relation: "use",
|
|
284
|
-
...subject,
|
|
285
|
-
};
|
|
286
|
-
if (dryRun) {
|
|
287
|
-
console.log(` ~ would write: ${namespace}:${tool}#use@${subjectId}`);
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
// Read-before-write: Keto's create API is not idempotent (every PUT
|
|
292
|
-
// inserts a new row and never 409s), so a plain createRelationship on
|
|
293
|
-
// re-install would duplicate the whole catalog. ensureRelationship
|
|
294
|
-
// probes for the exact tuple first and only writes when it is absent.
|
|
295
|
-
const spanAttributes = { toolName: tool, source: "permissions_bootstrap" };
|
|
296
|
-
let res = await client.ensureRelationship(check, { spanAttributes });
|
|
297
|
-
if (res.probeError) {
|
|
298
|
-
// Couldn't confirm existence — fall back to a direct write so a
|
|
299
|
-
// needed grant is still created (bootstrap's job is to guarantee it).
|
|
300
|
-
res = await client.createRelationship(check, { spanAttributes });
|
|
301
|
-
}
|
|
302
|
-
if (res.alreadyExisted) {
|
|
303
|
-
existed++;
|
|
304
|
-
console.log(` = ${tool} (already exists)`);
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
created++;
|
|
308
|
-
console.log(` + ${tool} (created)`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
catch (err) {
|
|
312
|
-
const ory = err;
|
|
313
|
-
const reason = `${ory.code ?? "unknown"}${ory.status ? ` (HTTP ${ory.status})` : ""}`;
|
|
314
|
-
failures.push({ tool, reason });
|
|
315
|
-
console.log(` ! ${tool} (failed: ${reason})`);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
console.log("");
|
|
319
|
-
if (dryRun) {
|
|
320
|
-
console.log(`Dry run: ${catalog.length} permissions would be written.`);
|
|
321
|
-
return 0;
|
|
322
|
-
}
|
|
323
|
-
console.log(`Created: ${created} Already existed: ${existed} Failed: ${failures.length}`);
|
|
324
|
-
if (failures.length > 0) {
|
|
325
|
-
const writeAuthFailure = failures.some((f) => /(unknown|forbidden|session_inactive)/.test(f.reason) ||
|
|
326
|
-
/401|403/.test(f.reason));
|
|
327
|
-
if (writeAuthFailure) {
|
|
328
|
-
console.log("");
|
|
329
|
-
console.log("Some writes were rejected — your current credentials may lack");
|
|
330
|
-
console.log("write scope on the permission namespace. You can apply the");
|
|
331
|
-
console.log("permissions manually using the snippet below (one per line):");
|
|
332
|
-
console.log("");
|
|
333
|
-
for (const tool of catalog) {
|
|
334
|
-
console.log(` ${namespace}:${tool}#use@${subjectId}`);
|
|
335
|
-
}
|
|
336
|
-
console.log("");
|
|
337
|
-
console.log("Or use the Ory Console: open the project, go to Permissions →");
|
|
338
|
-
console.log(`Relationships, set namespace='${namespace}', object=<tool>, relation='use',`);
|
|
339
|
-
console.log(`and subject='${subjectId}'.`);
|
|
340
|
-
}
|
|
341
|
-
return failures.length === catalog.length ? 1 : 0;
|
|
342
|
-
}
|
|
343
|
-
console.log("");
|
|
344
|
-
console.log(`Permissions are in place. Promote to enforce mode when ready:`);
|
|
345
|
-
console.log(` ${binName} permissions enforce`);
|
|
346
|
-
return 0;
|
|
347
|
-
}
|
|
348
225
|
/**
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
226
|
+
* Render the trailing source annotation on a Mode line. `server` means a live
|
|
227
|
+
* read from the Ory project; `cache` means the server was unreachable and the
|
|
228
|
+
* last-read value is being used (annotated with when it was fetched); `default`
|
|
229
|
+
* means nothing has ever been read (so `observe`).
|
|
353
230
|
*/
|
|
354
|
-
function
|
|
355
|
-
if (process.env.ORY_USER_SUBJECT_ID)
|
|
356
|
-
return true;
|
|
357
|
-
const tokens = (0, auth_store_js_1.loadTokens)();
|
|
358
|
-
return !!tokens && !(0, auth_store_js_1.isExpired)(tokens);
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Print the "permission mode" section of the install banner.
|
|
362
|
-
*
|
|
363
|
-
* Tells the user what observe vs enforce mean, and prints the two
|
|
364
|
-
* follow-up commands they'll typically reach for next:
|
|
365
|
-
*
|
|
366
|
-
* - `<bin> permissions bootstrap` to seed tuples for the harness's
|
|
367
|
-
* built-in tools (the line is shown whether or not we just ran it
|
|
368
|
-
* ourselves, so the user can re-run after editing the catalog).
|
|
369
|
-
* - `<bin> permissions enforce` once they're satisfied with the
|
|
370
|
-
* observe-mode trace output and want denies to actually block.
|
|
371
|
-
*/
|
|
372
|
-
function printPermissionsOnboardingHelp(binName, harness, opts = {}) {
|
|
373
|
-
const resolved = (0, config_js_1.resolveConfig)();
|
|
374
|
-
const catalog = (0, tool_catalog_js_1.getToolCatalog)(harness);
|
|
375
|
-
ui.heading("Permissions:");
|
|
376
|
-
ui.summary([
|
|
377
|
-
["Mode", `${resolved.permissionMode}${formatModeSource(resolved.permissionModeSource)}`],
|
|
378
|
-
["Other modes", otherModes(resolved.permissionMode).join(", ")],
|
|
379
|
-
]);
|
|
380
|
-
if (resolved.auditOnly) {
|
|
381
|
-
ui.hint("Ory is in audit-only mode (kill switch). Permission checks are disabled.");
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
if (resolved.permissionMode === "observe") {
|
|
385
|
-
ui.hint("Observe mode: permission denies are logged + traced, but tools still run.");
|
|
386
|
-
ui.hint("This is the safe default — promote to 'enforce' once your permission set is correct.");
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
ui.hint("Enforce mode: permission denies block tool execution.");
|
|
390
|
-
}
|
|
391
|
-
if (opts.bootstrappedAutomatically) {
|
|
392
|
-
ui.blank();
|
|
393
|
-
ui.success(`Permissions bootstrapped for ${catalog.length} built-in tools.`);
|
|
394
|
-
}
|
|
395
|
-
else if (catalog.length > 0) {
|
|
396
|
-
ui.blank();
|
|
397
|
-
ui.bullet("Grant the current user 'use' on every built-in tool (idempotent):");
|
|
398
|
-
ui.command(`${(0, cli_invocation_js_1.oryNpx)(binName)} permissions bootstrap`);
|
|
399
|
-
}
|
|
400
|
-
ui.blank();
|
|
401
|
-
ui.bullet("Inspect permission coverage:");
|
|
402
|
-
ui.command(`${(0, cli_invocation_js_1.oryNpx)(binName)} permissions status`);
|
|
403
|
-
ui.blank();
|
|
404
|
-
ui.bullet("Promote to enforcing once observe-mode logs look right:");
|
|
405
|
-
ui.command(`${(0, cli_invocation_js_1.oryNpx)(binName)} permissions enforce`);
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* Render the trailing source annotation on a Mode line. Stays silent
|
|
409
|
-
* when the mode came from the built-in default, since saying "default"
|
|
410
|
-
* just adds noise. When the mode was overridden by env or config, name
|
|
411
|
-
* the override so the reader knows where to look.
|
|
412
|
-
*/
|
|
413
|
-
function formatModeSource(source) {
|
|
231
|
+
function formatModeSource(source, fetchedAt) {
|
|
414
232
|
switch (source) {
|
|
415
|
-
case "
|
|
416
|
-
return
|
|
417
|
-
case "
|
|
418
|
-
return
|
|
233
|
+
case "server":
|
|
234
|
+
return " (read from the Ory project)";
|
|
235
|
+
case "cache":
|
|
236
|
+
return ` (cached — project unreachable${formatFetchedAt(fetchedAt)})`;
|
|
419
237
|
case "default":
|
|
420
|
-
return " (
|
|
238
|
+
return " (default — not yet read from the project)";
|
|
421
239
|
}
|
|
422
240
|
}
|
|
241
|
+
/** Human-readable "fetched N ago" suffix for the cached-mode annotation. */
|
|
242
|
+
function formatFetchedAt(fetchedAt) {
|
|
243
|
+
if (!fetchedAt)
|
|
244
|
+
return "";
|
|
245
|
+
const ageMs = Date.now() - fetchedAt;
|
|
246
|
+
if (ageMs < 0)
|
|
247
|
+
return "";
|
|
248
|
+
const mins = Math.floor(ageMs / 60_000);
|
|
249
|
+
if (mins < 1)
|
|
250
|
+
return "; fetched <1m ago";
|
|
251
|
+
if (mins < 60)
|
|
252
|
+
return `; fetched ${mins}m ago`;
|
|
253
|
+
const hours = Math.floor(mins / 60);
|
|
254
|
+
if (hours < 24)
|
|
255
|
+
return `; fetched ${hours}h ago`;
|
|
256
|
+
return `; fetched ${Math.floor(hours / 24)}d ago`;
|
|
257
|
+
}
|
|
423
258
|
/**
|
|
424
259
|
* Generic source-suffix helper for other config fields (project URL,
|
|
425
260
|
* API key). Source 'none' means the value is unset — don't add a suffix.
|
|
@@ -430,48 +265,19 @@ function formatSourceSuffix(source) {
|
|
|
430
265
|
return " (from env)";
|
|
431
266
|
case "config":
|
|
432
267
|
return " (from config)";
|
|
268
|
+
case "default":
|
|
269
|
+
return " (default)";
|
|
433
270
|
case "none":
|
|
434
271
|
return "";
|
|
435
272
|
}
|
|
436
273
|
}
|
|
437
274
|
/**
|
|
438
|
-
* Lists the
|
|
439
|
-
* banner can hint "Other modes: …" without re-listing the active one.
|
|
440
|
-
*
|
|
441
|
-
*
|
|
275
|
+
* Lists the deny postures other than the active one, in stable order, so the
|
|
276
|
+
* banner can hint "Other modes: …" without re-listing the active one. Only the
|
|
277
|
+
* two postures — whether Agent Security is connected at all is a separate
|
|
278
|
+
* question, and this is only reached once it is.
|
|
442
279
|
*/
|
|
443
280
|
function otherModes(active) {
|
|
444
|
-
const all = ["observe", "enforce"
|
|
281
|
+
const all = ["observe", "enforce"];
|
|
445
282
|
return all.filter((m) => m !== active);
|
|
446
283
|
}
|
|
447
|
-
/**
|
|
448
|
-
* If a user identity is already cached and the plugin is configured,
|
|
449
|
-
* opportunistically run `permissions bootstrap` so the user lands in a
|
|
450
|
-
* "tools work out of the box" state. Otherwise a no-op (the install
|
|
451
|
-
* banner still prints the manual command).
|
|
452
|
-
*
|
|
453
|
-
* Always best-effort: any failure is logged and swallowed so install
|
|
454
|
-
* never aborts because of a permissions write problem.
|
|
455
|
-
*/
|
|
456
|
-
async function maybeAutoBootstrap(binName, harness) {
|
|
457
|
-
const resolved = (0, config_js_1.resolveConfig)();
|
|
458
|
-
if (resolved.auditOnly)
|
|
459
|
-
return false;
|
|
460
|
-
if (!resolved.projectUrl)
|
|
461
|
-
return false;
|
|
462
|
-
if (!isUserIdentityCached())
|
|
463
|
-
return false;
|
|
464
|
-
if ((0, tool_catalog_js_1.getToolCatalog)(harness).length === 0)
|
|
465
|
-
return false;
|
|
466
|
-
try {
|
|
467
|
-
console.log("");
|
|
468
|
-
console.log("Bootstrapping permissions (cached user identity detected)...");
|
|
469
|
-
const code = await runPermissionsCommand(binName, harness, ["bootstrap"]);
|
|
470
|
-
return code === 0;
|
|
471
|
-
}
|
|
472
|
-
catch (err) {
|
|
473
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
474
|
-
console.warn(`Bootstrap skipped: ${msg}`);
|
|
475
|
-
return false;
|
|
476
|
-
}
|
|
477
|
-
}
|