@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
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Server-read permission mode.
|
|
4
|
+
*
|
|
5
|
+
* `observe` vs `enforce` is no longer a local, per-machine setting — it is a
|
|
6
|
+
* **permission the plugin reads from Keto** before gating a tool call, so an
|
|
7
|
+
* admin controls the deny posture centrally from the Ory project (Ory Console /
|
|
8
|
+
* Agent Security), and every session inherits it. There is no local override
|
|
9
|
+
* and no `ORY_PERMISSION_MODE` env var.
|
|
10
|
+
*
|
|
11
|
+
* The mode lives in its own `PermissionMode` namespace — it is not a tool, so it
|
|
12
|
+
* is not a tool object (see the naming-conventions note in `opl.ts`). It is
|
|
13
|
+
* resolved by `checkPermission` against the `mode` anchor, at two scopes:
|
|
14
|
+
*
|
|
15
|
+
* project: PermissionMode:mode#enforcedSubjects@(PermissionMode:project#enforcedSubjects)
|
|
16
|
+
* applicable: PermissionMode:mode#enforcedSubjects@<identity|agent|subagent>
|
|
17
|
+
* allowed ⇒ enforce denied ⇒ observe
|
|
18
|
+
*
|
|
19
|
+
* The **project** grant is checked first and is the posture an admin normally
|
|
20
|
+
* sets: one tuple governs every principal, including ones that do not exist
|
|
21
|
+
* yet. That matters because a per-principal grant cannot cover a future
|
|
22
|
+
* principal — before the project scope existed, an agent connecting after an
|
|
23
|
+
* admin turned on enforce resolved `observe` and silently escaped enforcement
|
|
24
|
+
* until someone re-applied the grant for it. A **per-principal** grant remains
|
|
25
|
+
* meaningful as an exception: enforce one identity, agent harness, or sub-agent
|
|
26
|
+
* type while the project still observes. Type grants are inherited through the
|
|
27
|
+
* server-managed AgentType/SubAgentType membership relations.
|
|
28
|
+
* Neither scope can be set by the runtime — the plugin only ever reads, riding
|
|
29
|
+
* its own OAuth2 access token like every other Keto read.
|
|
30
|
+
*
|
|
31
|
+
* Resolution precedence, applied by {@link resolvePermissionMode}:
|
|
32
|
+
*
|
|
33
|
+
* 1. **In-memory TTL cache** (per scope) — avoids a Keto round-trip on
|
|
34
|
+
* literally every tool call while still re-reading the server every `ttlMs()`.
|
|
35
|
+
* The project scope caches under one key shared by every subject, so the
|
|
36
|
+
* common "whole project enforced" case costs a single read per TTL window.
|
|
37
|
+
* 2. **Server check** — project scope, then identity and acting machine scopes
|
|
38
|
+
* only if the project is not enforcing. On a clean result, refresh the in-memory cache
|
|
39
|
+
* and return it (`source: "server"`).
|
|
40
|
+
* 3. **Persistent cache** — when the server is unreachable, fall back to the
|
|
41
|
+
* last value read from the server, persisted in `config.json`
|
|
42
|
+
* (`source: "cache"`).
|
|
43
|
+
* 4. **`observe`** — nothing was ever cached (`source: "default"`).
|
|
44
|
+
*
|
|
45
|
+
* The persistent cache is written by {@link warmPermissionModeCache} at session
|
|
46
|
+
* start and when a gate first discovers a new acting-machine scope. It only
|
|
47
|
+
* writes when a cached mode changes, so normal per-tool checks do not churn the
|
|
48
|
+
* shared config while child-only posture still survives subprocess boundaries.
|
|
49
|
+
*/
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.resetPermissionModeCache = resetPermissionModeCache;
|
|
52
|
+
exports.resolvePermissionMode = resolvePermissionMode;
|
|
53
|
+
exports.warmPermissionModeCache = warmPermissionModeCache;
|
|
54
|
+
const config_js_1 = require("./config.js");
|
|
55
|
+
const read_credential_js_1 = require("./read-credential.js");
|
|
56
|
+
const opl_js_1 = require("./opl.js");
|
|
57
|
+
/**
|
|
58
|
+
* Default in-memory cache TTL (ms). Small enough that an admin's mode change is
|
|
59
|
+
* picked up within a session without a restart, large enough that a burst of
|
|
60
|
+
* tool calls doesn't issue a Keto read each. Override with
|
|
61
|
+
* `ORY_PERMISSION_MODE_TTL_MS`.
|
|
62
|
+
*/
|
|
63
|
+
const DEFAULT_TTL_MS = 30_000;
|
|
64
|
+
function ttlMs() {
|
|
65
|
+
const raw = process.env.ORY_PERMISSION_MODE_TTL_MS;
|
|
66
|
+
if (!raw)
|
|
67
|
+
return DEFAULT_TTL_MS;
|
|
68
|
+
const n = Number.parseInt(raw, 10);
|
|
69
|
+
if (!Number.isFinite(n) || n < 0)
|
|
70
|
+
return DEFAULT_TTL_MS;
|
|
71
|
+
return n;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The posture is read from the fixed `PermissionMode` namespace, **not** the tool
|
|
75
|
+
* namespace: `ORY_PERMISSION_NAMESPACE` names where an operator's *tool* objects
|
|
76
|
+
* live, and the deny posture is a property of the project rather than of any one
|
|
77
|
+
* tool namespace. Keeping it fixed also means the two tool namespaces
|
|
78
|
+
* (`AgentTool`, `ShellTool`) can never disagree about the posture.
|
|
79
|
+
*/
|
|
80
|
+
function resolveNamespace() {
|
|
81
|
+
return opl_js_1.PERMISSION_MODE_NAMESPACE;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* In-memory cache, keyed **by client** (so it is scoped to one session's
|
|
85
|
+
* lifecycle) and then by subject. A `WeakMap` means a discarded client's cache
|
|
86
|
+
* is collected with it — and, crucially, that each test's fresh client starts
|
|
87
|
+
* with an empty cache, so no stale mode bleeds across tests.
|
|
88
|
+
*/
|
|
89
|
+
let clientMemo = new WeakMap();
|
|
90
|
+
function memoFor(client) {
|
|
91
|
+
let m = clientMemo.get(client);
|
|
92
|
+
if (!m) {
|
|
93
|
+
m = new Map();
|
|
94
|
+
clientMemo.set(client, m);
|
|
95
|
+
}
|
|
96
|
+
return m;
|
|
97
|
+
}
|
|
98
|
+
function subjectKey(subject) {
|
|
99
|
+
if (subject.subjectId)
|
|
100
|
+
return `id:${subject.subjectId}`;
|
|
101
|
+
const s = subject.subjectSet;
|
|
102
|
+
if (s)
|
|
103
|
+
return `set:${s.namespace}:${s.object}#${s.relation}`;
|
|
104
|
+
return "anon";
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Cache key for the project-scope read. Distinct from every {@link subjectKey}
|
|
108
|
+
* (which is always `id:`/`set:`/`anon`-prefixed) and shared by all subjects, so
|
|
109
|
+
* one read per TTL window covers the whole session.
|
|
110
|
+
*/
|
|
111
|
+
const PROJECT_SCOPE_KEY = "project";
|
|
112
|
+
/** The sentinel subject a project-wide posture grant is written to. */
|
|
113
|
+
function projectPostureSubject(namespace) {
|
|
114
|
+
return {
|
|
115
|
+
subjectSet: {
|
|
116
|
+
namespace,
|
|
117
|
+
object: opl_js_1.PROJECT_POSTURE_OBJECT,
|
|
118
|
+
relation: opl_js_1.RELATION_ENFORCED_SUBJECTS,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/** Drop all in-memory caches. Test-only (reassigns the WeakMap). */
|
|
123
|
+
function resetPermissionModeCache() {
|
|
124
|
+
clientMemo = new WeakMap();
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve the current permission mode for `subject`. Never throws — a Keto
|
|
128
|
+
* error falls back to the persistent cache, then `observe`. See the module
|
|
129
|
+
* doc for the precedence. Does **not** persist (that is
|
|
130
|
+
* {@link warmPermissionModeCache}'s job); `opts.now` is injectable for tests.
|
|
131
|
+
*/
|
|
132
|
+
async function resolvePermissionMode(client, subject, opts = {}) {
|
|
133
|
+
const now = opts.now ?? Date.now();
|
|
134
|
+
const projectUrl = (0, config_js_1.resolveConfig)().projectUrl;
|
|
135
|
+
const namespace = resolveNamespace();
|
|
136
|
+
const key = subjectKey(subject);
|
|
137
|
+
const subjects = [
|
|
138
|
+
{ subject, scope: "principal", key },
|
|
139
|
+
...(opts.additionalSubjects ?? []).map((item) => ({
|
|
140
|
+
...item,
|
|
141
|
+
key: subjectKey(item.subject),
|
|
142
|
+
})),
|
|
143
|
+
].filter((item, index, all) => all.findIndex((candidate) => candidate.key === item.key) === index);
|
|
144
|
+
const memo = memoFor(client);
|
|
145
|
+
const fresh = (cacheKey) => {
|
|
146
|
+
const entry = memo.get(cacheKey);
|
|
147
|
+
if (entry && now - entry.fetchedAt < ttlMs())
|
|
148
|
+
return entry;
|
|
149
|
+
memo.delete(cacheKey);
|
|
150
|
+
return undefined;
|
|
151
|
+
};
|
|
152
|
+
// An enforcing project settles the mode for every subject, so a fresh project
|
|
153
|
+
// hit short-circuits before the subject is even considered.
|
|
154
|
+
const cachedProject = fresh(PROJECT_SCOPE_KEY);
|
|
155
|
+
if (cachedProject?.mode === "enforce") {
|
|
156
|
+
return { mode: "enforce", source: cachedProject.source };
|
|
157
|
+
}
|
|
158
|
+
const cachedSubjects = subjects.map((item) => fresh(item.key));
|
|
159
|
+
if (cachedProject && cachedSubjects.every(Boolean)) {
|
|
160
|
+
const enforcing = cachedSubjects.find((entry) => entry?.mode === "enforce");
|
|
161
|
+
return { mode: enforcing?.mode ?? "observe", source: enforcing?.source ?? cachedProject.source };
|
|
162
|
+
}
|
|
163
|
+
// The mode is read from the project, so it needs the same authenticated Keto
|
|
164
|
+
// read a tool check does. Without this a subprocess event past the user
|
|
165
|
+
// token's hour read nothing and fell back to the `observe` default — silently
|
|
166
|
+
// un-enforcing an `enforce` project (#242). After the short-circuits above, so
|
|
167
|
+
// a cache hit costs nothing.
|
|
168
|
+
await (0, read_credential_js_1.ensureReadCredential)(client);
|
|
169
|
+
const check = async (scope, modeSubject, cacheKey) => {
|
|
170
|
+
const result = await client.checkPermission({
|
|
171
|
+
namespace,
|
|
172
|
+
object: opl_js_1.PERMISSION_MODE_OBJECT,
|
|
173
|
+
relation: opl_js_1.RELATION_ENFORCED_SUBJECTS,
|
|
174
|
+
...modeSubject,
|
|
175
|
+
}, {
|
|
176
|
+
// This is a mode *read*, not a gate: a `false` result selects `observe`,
|
|
177
|
+
// it is not a tool denial. Mark it informational so the event ends `ok`
|
|
178
|
+
// and carry a note explaining the boolean, so live debug output is
|
|
179
|
+
// self-explanatory rather than looking like a blocked tool.
|
|
180
|
+
informational: true,
|
|
181
|
+
activityAttributes: {
|
|
182
|
+
source: "permission_mode",
|
|
183
|
+
scope,
|
|
184
|
+
note: "mode read — allowed=true ⇒ enforce, allowed=false ⇒ observe",
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
const mode = result.allowed ? "enforce" : "observe";
|
|
188
|
+
memo.set(cacheKey, { mode, source: "server", fetchedAt: now });
|
|
189
|
+
return mode;
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
const projectMode = cachedProject?.mode
|
|
193
|
+
?? (await check("project", projectPostureSubject(namespace), PROJECT_SCOPE_KEY));
|
|
194
|
+
let scope = "project";
|
|
195
|
+
let mode = projectMode;
|
|
196
|
+
if (projectMode !== "enforce") {
|
|
197
|
+
mode = "observe";
|
|
198
|
+
for (const [index, item] of subjects.entries()) {
|
|
199
|
+
const subjectMode = cachedSubjects[index]?.mode ?? (await check(item.scope, item.subject, item.key));
|
|
200
|
+
if (subjectMode === "enforce") {
|
|
201
|
+
mode = subjectMode;
|
|
202
|
+
scope = item.scope;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (mode === "observe")
|
|
207
|
+
scope = "principal";
|
|
208
|
+
}
|
|
209
|
+
client.logger.activity("permission.mode_resolve", "ok", {
|
|
210
|
+
attributes: {
|
|
211
|
+
permissionMode: mode,
|
|
212
|
+
enforceGranted: mode === "enforce",
|
|
213
|
+
scope,
|
|
214
|
+
source: "server",
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
return { mode, source: "server" };
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
const oryErr = err;
|
|
221
|
+
const cache = (0, config_js_1.resolveConfig)().permissionModeCache;
|
|
222
|
+
const scoped = cache && cache.projectUrl === projectUrl ? cache.scopes : undefined;
|
|
223
|
+
const project = scoped?.[PROJECT_SCOPE_KEY];
|
|
224
|
+
const subjectEntries = subjects.map((item) => scoped?.[item.key]);
|
|
225
|
+
const scopedMode = project?.mode === "enforce" || subjectEntries.some((entry) => entry?.mode === "enforce")
|
|
226
|
+
? "enforce"
|
|
227
|
+
: project && subjectEntries.every(Boolean)
|
|
228
|
+
? "observe"
|
|
229
|
+
: undefined;
|
|
230
|
+
// A legacy scalar enforce value is a conservative fallback for every scope.
|
|
231
|
+
// A scalar observe value is not: it may have been written for a different
|
|
232
|
+
// identity or harness and must not silently relax that scope.
|
|
233
|
+
const legacyMode = cache && !cache.scopes && (!cache.projectUrl || cache.projectUrl === projectUrl)
|
|
234
|
+
? cache.mode
|
|
235
|
+
: undefined;
|
|
236
|
+
const cachedMode = scopedMode ?? (legacyMode === "enforce" ? "enforce" : undefined);
|
|
237
|
+
if (cachedMode) {
|
|
238
|
+
client.logger.debug("permission.mode_cache", {
|
|
239
|
+
code: oryErr?.code,
|
|
240
|
+
note: "mode check failed; using last cached server value",
|
|
241
|
+
mode: cachedMode,
|
|
242
|
+
});
|
|
243
|
+
return { mode: cachedMode, source: "cache" };
|
|
244
|
+
}
|
|
245
|
+
client.logger.debug("permission.mode_default", {
|
|
246
|
+
code: oryErr?.code,
|
|
247
|
+
note: "mode check failed and no cached value; defaulting to observe",
|
|
248
|
+
});
|
|
249
|
+
return { mode: "observe", source: "default" };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Resolve the mode and, when it came from a live server read, persist it to
|
|
254
|
+
* `config.json` so a subprocess harness's later hook processes (and the
|
|
255
|
+
* synchronous MCP `applyPermissionMode` path) see the current posture, and so a
|
|
256
|
+
* subsequent offline session has a cached value to fall back to. Called once at
|
|
257
|
+
* session start. Best-effort; never throws. Only writes when the value changed,
|
|
258
|
+
* so it doesn't churn the config lockfile.
|
|
259
|
+
*/
|
|
260
|
+
async function warmPermissionModeCache(client, subject, opts = {}) {
|
|
261
|
+
const now = opts.now ?? Date.now();
|
|
262
|
+
const projectUrl = (0, config_js_1.resolveConfig)().projectUrl;
|
|
263
|
+
const resolved = await resolvePermissionMode(client, subject, {
|
|
264
|
+
now,
|
|
265
|
+
additionalSubjects: opts.additionalSubjects,
|
|
266
|
+
});
|
|
267
|
+
if (resolved.source === "server") {
|
|
268
|
+
const requestedKeys = [PROJECT_SCOPE_KEY, subjectKey(subject), ...(opts.additionalSubjects ?? []).map((item) => subjectKey(item.subject))];
|
|
269
|
+
const updates = {};
|
|
270
|
+
for (const key of new Set(requestedKeys)) {
|
|
271
|
+
const entry = memoFor(client).get(key);
|
|
272
|
+
if (entry?.source === "server")
|
|
273
|
+
updates[key] = { mode: entry.mode, fetchedAt: entry.fetchedAt };
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
(0, config_js_1.mutateConfig)((config) => {
|
|
277
|
+
const current = config.permissionModeCache;
|
|
278
|
+
const scopes = { ...(current && current.projectUrl === projectUrl ? current.scopes ?? {} : {}) };
|
|
279
|
+
const projectMode = updates[PROJECT_SCOPE_KEY]?.mode
|
|
280
|
+
?? scopes[PROJECT_SCOPE_KEY]?.mode
|
|
281
|
+
?? "observe";
|
|
282
|
+
let changed = current?.mode !== projectMode || current?.projectUrl !== projectUrl;
|
|
283
|
+
for (const [key, entry] of Object.entries(updates)) {
|
|
284
|
+
const refreshed = { mode: entry.mode, fetchedAt: now };
|
|
285
|
+
if (scopes[key]?.mode === refreshed.mode && scopes[key]?.fetchedAt === refreshed.fetchedAt)
|
|
286
|
+
continue;
|
|
287
|
+
scopes[key] = refreshed;
|
|
288
|
+
changed = true;
|
|
289
|
+
}
|
|
290
|
+
if (!changed)
|
|
291
|
+
return undefined;
|
|
292
|
+
const boundedScopes = Object.fromEntries(Object.entries(scopes)
|
|
293
|
+
.sort(([, left], [, right]) => right.fetchedAt - left.fetchedAt)
|
|
294
|
+
.slice(0, 256));
|
|
295
|
+
return {
|
|
296
|
+
...config,
|
|
297
|
+
permissionModeCache: { mode: projectMode, fetchedAt: now, projectUrl, scopes: boundedScopes },
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// Best-effort — a write failure just means the next process re-reads
|
|
303
|
+
// from the server (or starts at observe until it can).
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return resolved;
|
|
307
|
+
}
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared `permissions` CLI implementation. Each harness's `cli/main.ts`
|
|
3
|
-
* delegates to {@link runPermissionsCommand}, passing the harness's name
|
|
4
|
-
*
|
|
3
|
+
* delegates to {@link runPermissionsCommand}, passing the harness's name so the
|
|
4
|
+
* report can scope to that harness's tool catalog.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* `permissions` takes **no subcommand** — it is a single read-only report: the
|
|
7
|
+
* live permission mode (read from the Ory project), the subject being checked,
|
|
8
|
+
* and the allow / deny / blocked breakdown across the harness's known tool
|
|
9
|
+
* catalog. (`permissions status` is still accepted; it was the name through
|
|
10
|
+
* v0.14, and the report is the same.)
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* cached; can be re-run by hand any time.
|
|
15
|
-
* - `observe` — Persist `permissionMode = "observe"` in the shared
|
|
16
|
-
* config (denies log but don't block).
|
|
17
|
-
* - `enforce` — Persist `permissionMode = "enforce"` in the shared
|
|
18
|
-
* config (denies block — the production posture).
|
|
19
|
-
*
|
|
20
|
-
* All four are non-destructive on existing config and idempotent on
|
|
21
|
-
* Keto: re-running them is always safe.
|
|
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
|
|
15
|
+
* plugin be installed with nothing but a project URL — the public OAuth2 client
|
|
16
|
+
* id has a reserved default, with no workspace privilege, project API key, or admin write path. The
|
|
17
|
+
* plugin is a read-only client of the project's permission state.
|
|
22
18
|
*/
|
|
23
19
|
/**
|
|
24
20
|
* Entry point invoked by each harness CLI's `permissions` case.
|
|
@@ -28,35 +24,3 @@
|
|
|
28
24
|
* exit so the parent CLI doesn't need a try/catch.
|
|
29
25
|
*/
|
|
30
26
|
export declare function runPermissionsCommand(binName: string, harness: string, args: string[]): Promise<number>;
|
|
31
|
-
/**
|
|
32
|
-
* Whether a user identity is cached locally — used by `install` to
|
|
33
|
-
* decide whether to opportunistically bootstrap tuples without
|
|
34
|
-
* prompting. Returns true when persisted tokens exist and are not
|
|
35
|
-
* expired, or when ORY_USER_SUBJECT_ID is set explicitly.
|
|
36
|
-
*/
|
|
37
|
-
export declare function isUserIdentityCached(): boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Print the "permission mode" section of the install banner.
|
|
40
|
-
*
|
|
41
|
-
* Tells the user what observe vs enforce mean, and prints the two
|
|
42
|
-
* follow-up commands they'll typically reach for next:
|
|
43
|
-
*
|
|
44
|
-
* - `<bin> permissions bootstrap` to seed tuples for the harness's
|
|
45
|
-
* built-in tools (the line is shown whether or not we just ran it
|
|
46
|
-
* ourselves, so the user can re-run after editing the catalog).
|
|
47
|
-
* - `<bin> permissions enforce` once they're satisfied with the
|
|
48
|
-
* observe-mode trace output and want denies to actually block.
|
|
49
|
-
*/
|
|
50
|
-
export declare function printPermissionsOnboardingHelp(binName: string, harness: string, opts?: {
|
|
51
|
-
bootstrappedAutomatically?: boolean;
|
|
52
|
-
}): void;
|
|
53
|
-
/**
|
|
54
|
-
* If a user identity is already cached and the plugin is configured,
|
|
55
|
-
* opportunistically run `permissions bootstrap` so the user lands in a
|
|
56
|
-
* "tools work out of the box" state. Otherwise a no-op (the install
|
|
57
|
-
* banner still prints the manual command).
|
|
58
|
-
*
|
|
59
|
-
* Always best-effort: any failure is logged and swallowed so install
|
|
60
|
-
* never aborts because of a permissions write problem.
|
|
61
|
-
*/
|
|
62
|
-
export declare function maybeAutoBootstrap(binName: string, harness: string): Promise<boolean>;
|