@ory/argus 1.5.1 → 1.5.2
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 +5 -2
- package/dist/adapters.js +22 -0
- package/dist/agent-auth.d.ts +45 -0
- package/dist/agent-auth.js +186 -0
- package/dist/build-info.json +4 -4
- package/dist/client.d.ts +16 -1
- package/dist/client.js +26 -5
- package/dist/config.d.ts +22 -0
- package/dist/config.js +42 -0
- package/dist/runtime-credential.d.ts +2 -0
- package/dist/runtime-credential.js +1 -0
- package/dist/subject.d.ts +2 -2
- package/dist/subject.js +14 -10
- package/dist/uninstall.js +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,8 +112,11 @@ Headless deployments normally supply only `ORY_AGENT_API_KEY` (or an injected ru
|
|
|
112
112
|
authenticator such as SPIFFE) plus `ORY_PROJECT_URL` and `ORY_AGENT_SECURITY_URL`.
|
|
113
113
|
Before PKCE, Argus resolves the administrator-provisioned subject and opaque root from
|
|
114
114
|
Agent Security, keeps them in process memory, and reuses the root unchanged for child
|
|
115
|
-
lineage.
|
|
116
|
-
|
|
115
|
+
lineage. Subprocess hooks restore that context from a non-secret, credential-bound
|
|
116
|
+
per-session record in `config.json` instead of requesting it per tool call. Without a
|
|
117
|
+
user, tools are checked as the deployment owner. `ORY_AGENT_SUBJECT_ID` and
|
|
118
|
+
`ORY_AGENT_ROOT_DELEGATION` remain compatibility overrides; a complete legacy package
|
|
119
|
+
skips the context request.
|
|
117
120
|
|
|
118
121
|
### Logger
|
|
119
122
|
|
package/dist/adapters.js
CHANGED
|
@@ -119,6 +119,15 @@ async function sessionStart(client, opts) {
|
|
|
119
119
|
fetchImpl: opts.runtimeContextFetch,
|
|
120
120
|
});
|
|
121
121
|
rootDelegation = context?.rootDelegation;
|
|
122
|
+
// Subprocess harnesses gate tools in later processes; let them restore
|
|
123
|
+
// this context instead of resolving it again on every tool call.
|
|
124
|
+
if (context) {
|
|
125
|
+
(0, agent_auth_js_1.saveRuntimeContext)(client, context, {
|
|
126
|
+
harness: opts.harness,
|
|
127
|
+
sessionId: opts.sessionId ?? client.sessionId,
|
|
128
|
+
projectUrl,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
122
131
|
}
|
|
123
132
|
catch (err) {
|
|
124
133
|
client.logger.warn("agent.runtime_context.failed", describeThrown(err));
|
|
@@ -290,6 +299,13 @@ async function writeUserDelegatesAgent(client, harness, sessionId, env = process
|
|
|
290
299
|
async function registerSubagent(client, opts) {
|
|
291
300
|
const projectUrl = opts.projectUrl ?? (0, config_js_1.resolveConfig)().projectUrl;
|
|
292
301
|
const subAgentGate = opts.subAgentGate ?? agent_auth_js_1.ensureSubAgentIdentity;
|
|
302
|
+
// A child of a headless deployment hangs off the deployment root; a
|
|
303
|
+
// subprocess restores it from the session's persisted runtime context.
|
|
304
|
+
await (0, agent_auth_js_1.ensureRuntimeContext)(client, {
|
|
305
|
+
harness: opts.harness,
|
|
306
|
+
sessionId: opts.sessionId,
|
|
307
|
+
env: opts.env,
|
|
308
|
+
});
|
|
293
309
|
let identity;
|
|
294
310
|
try {
|
|
295
311
|
identity = await subAgentGate(client, {
|
|
@@ -403,6 +419,12 @@ function gateActivityAttributes(opts) {
|
|
|
403
419
|
* separately reports actual execution; {@link gate} adds the standard events.
|
|
404
420
|
*/
|
|
405
421
|
async function decideTool(client, opts) {
|
|
422
|
+
if ((0, config_js_1.isSecurityConnected)()) {
|
|
423
|
+
// A subprocess hook of a headless deployment starts without the context
|
|
424
|
+
// its session-start process resolved. Restore it (or resolve it once) so
|
|
425
|
+
// the deployment owner, not a session placeholder, is checked (#338).
|
|
426
|
+
await (0, agent_auth_js_1.ensureRuntimeContext)(client, { harness: opts.harness, allowResolve: true });
|
|
427
|
+
}
|
|
406
428
|
const namespace = resolveNamespace();
|
|
407
429
|
const subjectRef = (0, subject_js_1.resolveUserSubject)(client, opts.subjectFallback);
|
|
408
430
|
const label = (0, subject_js_1.subjectLabel)(subjectRef);
|
package/dist/agent-auth.d.ts
CHANGED
|
@@ -25,6 +25,51 @@ export interface ResolvePreprovisionedRuntimeContextOptions {
|
|
|
25
25
|
}
|
|
26
26
|
/** Resolve missing headless deployment metadata using the existing runtime proof. */
|
|
27
27
|
export declare function resolvePreprovisionedRuntimeContext(client: OryAgentClient, options: ResolvePreprovisionedRuntimeContextOptions): Promise<RuntimeContext | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* SHA-256 binding of a runtime credential for persisted runtime context. It is
|
|
30
|
+
* derived from the credential's existing non-secret fingerprint and is never
|
|
31
|
+
* the secret itself. Credentials without a stable fingerprint (programmatic
|
|
32
|
+
* injected authenticators) are not persisted.
|
|
33
|
+
*/
|
|
34
|
+
export declare function runtimeCredentialBinding(credential: RuntimeCredential | undefined): string | undefined;
|
|
35
|
+
export interface SaveRuntimeContextOptions {
|
|
36
|
+
harness: string;
|
|
37
|
+
sessionId?: string;
|
|
38
|
+
/** Project the context was resolved for. Defaults to the client's project. */
|
|
39
|
+
projectUrl?: string;
|
|
40
|
+
}
|
|
41
|
+
/** Persist a resolved runtime context for later hook processes of the same session. */
|
|
42
|
+
export declare function saveRuntimeContext(client: OryAgentClient, context: RuntimeContext, options: SaveRuntimeContextOptions): boolean;
|
|
43
|
+
export interface RestoreRuntimeContextOptions {
|
|
44
|
+
harness: string;
|
|
45
|
+
sessionId?: string;
|
|
46
|
+
/** Authoritative agent type required of the context. Defaults to `harness`. */
|
|
47
|
+
agentType?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Restore a persisted runtime context into a fresh client. Loads only: it
|
|
51
|
+
* succeeds when the persisted entry is bound to the active runtime credential,
|
|
52
|
+
* the client's project and Agent Security URLs, and the expected agent type.
|
|
53
|
+
*/
|
|
54
|
+
export declare function restoreRuntimeContext(client: OryAgentClient, options: RestoreRuntimeContextOptions): RuntimeContext | undefined;
|
|
55
|
+
export interface EnsureRuntimeContextOptions extends RestoreRuntimeContextOptions {
|
|
56
|
+
env?: NodeJS.ProcessEnv;
|
|
57
|
+
fetchImpl?: typeof fetch;
|
|
58
|
+
/** Allow one bounded runtime-context request when nothing is persisted. */
|
|
59
|
+
allowResolve?: boolean;
|
|
60
|
+
timeoutMs?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Make the headless deployment context available to a tool or event path.
|
|
64
|
+
*
|
|
65
|
+
* Restores the session's persisted context (no network). When none exists —
|
|
66
|
+
* for example a harness that skipped its session-start hook — and
|
|
67
|
+
* `allowResolve` is set, it resolves the context once with a bounded request
|
|
68
|
+
* and persists it so later hook processes only load. A failed resolution is
|
|
69
|
+
* not retried by the same client for {@link LAZY_RUNTIME_CONTEXT_RETRY_MS}.
|
|
70
|
+
* Never throws.
|
|
71
|
+
*/
|
|
72
|
+
export declare function ensureRuntimeContext(client: OryAgentClient, options: EnsureRuntimeContextOptions): Promise<RuntimeContext | undefined>;
|
|
28
73
|
/**
|
|
29
74
|
* Delegation context handed to {@link registerAgentClient} so the freshly
|
|
30
75
|
* minted OAuth2 client is stamped with its delegator at the identity layer.
|
package/dist/agent-auth.js
CHANGED
|
@@ -16,6 +16,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
16
16
|
exports.DEFAULT_SESSION_RETENTION = exports.AGENT_TOKEN_EXPIRY_SKEW_SEC = void 0;
|
|
17
17
|
exports.resolvePreprovisionedRootDelegation = resolvePreprovisionedRootDelegation;
|
|
18
18
|
exports.resolvePreprovisionedRuntimeContext = resolvePreprovisionedRuntimeContext;
|
|
19
|
+
exports.runtimeCredentialBinding = runtimeCredentialBinding;
|
|
20
|
+
exports.saveRuntimeContext = saveRuntimeContext;
|
|
21
|
+
exports.restoreRuntimeContext = restoreRuntimeContext;
|
|
22
|
+
exports.ensureRuntimeContext = ensureRuntimeContext;
|
|
19
23
|
exports.resolveRuntimeAgentCredentials = resolveRuntimeAgentCredentials;
|
|
20
24
|
exports._resetAgentCredentialsCache = _resetAgentCredentialsCache;
|
|
21
25
|
exports.invalidateAgentAccessToken = invalidateAgentAccessToken;
|
|
@@ -46,6 +50,7 @@ exports.resolveAgentCredentials = resolveAgentCredentials;
|
|
|
46
50
|
exports.fetchClientCredentialsToken = fetchClientCredentialsToken;
|
|
47
51
|
exports.ensureAgentIdentity = ensureAgentIdentity;
|
|
48
52
|
exports.ensureSubAgentIdentity = ensureSubAgentIdentity;
|
|
53
|
+
const node_crypto_1 = require("node:crypto");
|
|
49
54
|
const promises_1 = require("node:fs/promises");
|
|
50
55
|
const node_os_1 = require("node:os");
|
|
51
56
|
const node_path_1 = require("node:path");
|
|
@@ -53,6 +58,7 @@ const promises_2 = require("node:timers/promises");
|
|
|
53
58
|
const client_1 = require("@ory/client");
|
|
54
59
|
const config_js_1 = require("./config.js");
|
|
55
60
|
const branding_js_1 = require("./branding.js");
|
|
61
|
+
const context_js_1 = require("./context.js");
|
|
56
62
|
const subject_js_1 = require("./subject.js");
|
|
57
63
|
const runtime_credential_js_1 = require("./runtime-credential.js");
|
|
58
64
|
const ENROLLMENT_LOCK_STALE_MS = 120_000;
|
|
@@ -147,12 +153,192 @@ async function resolvePreprovisionedRuntimeContext(client, options) {
|
|
|
147
153
|
if (context.agentType !== options.expectedAgentType) {
|
|
148
154
|
throw new Error("Runtime context agent type does not match the adapter type");
|
|
149
155
|
}
|
|
156
|
+
activateRuntimeContext(client, credential, context);
|
|
157
|
+
return context;
|
|
158
|
+
}
|
|
159
|
+
function activateRuntimeContext(client, credential, context) {
|
|
150
160
|
const contextualCredential = { ...credential, subject: context.subjectId };
|
|
151
161
|
validatedRuntimeSubjects.add(contextualCredential);
|
|
152
162
|
client.setRuntimeCredential(contextualCredential);
|
|
153
163
|
client.setRuntimeContext(context);
|
|
164
|
+
}
|
|
165
|
+
// ─── Persisted runtime context (subprocess hooks) ────────────────────
|
|
166
|
+
//
|
|
167
|
+
// Subprocess harnesses run every hook event in a new process, so the context
|
|
168
|
+
// resolved at session start would otherwise exist only in the session-start
|
|
169
|
+
// process. The non-secret context is persisted per (harness, session) and bound
|
|
170
|
+
// to a fingerprint of the runtime credential plus the canonical project and
|
|
171
|
+
// Agent Security URLs; a rotated key or another project never reuses it.
|
|
172
|
+
const LAZY_RUNTIME_CONTEXT_TIMEOUT_MS = 10_000;
|
|
173
|
+
const LAZY_RUNTIME_CONTEXT_RETRY_MS = 60_000;
|
|
174
|
+
const lazyRuntimeContextAttempts = new WeakMap();
|
|
175
|
+
/**
|
|
176
|
+
* SHA-256 binding of a runtime credential for persisted runtime context. It is
|
|
177
|
+
* derived from the credential's existing non-secret fingerprint and is never
|
|
178
|
+
* the secret itself. Credentials without a stable fingerprint (programmatic
|
|
179
|
+
* injected authenticators) are not persisted.
|
|
180
|
+
*/
|
|
181
|
+
function runtimeCredentialBinding(credential) {
|
|
182
|
+
let source;
|
|
183
|
+
if (credential?.kind === "talos" && credential.apiKeyFingerprint) {
|
|
184
|
+
source = `talos\0${credential.apiKeyFingerprint}`;
|
|
185
|
+
}
|
|
186
|
+
else if (credential?.kind === "injected" && credential.spiffeIdFingerprint) {
|
|
187
|
+
source = `spiffe\0${credential.spiffeIdFingerprint}`;
|
|
188
|
+
}
|
|
189
|
+
return source ? (0, node_crypto_1.createHash)("sha256").update(source).digest("hex") : undefined;
|
|
190
|
+
}
|
|
191
|
+
function canonicalUrlOrUndefined(url) {
|
|
192
|
+
if (!url)
|
|
193
|
+
return undefined;
|
|
194
|
+
try {
|
|
195
|
+
return (0, runtime_credential_js_1.canonicalProjectUrl)(url);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** Persist a resolved runtime context for later hook processes of the same session. */
|
|
202
|
+
function saveRuntimeContext(client, context, options) {
|
|
203
|
+
const credential = runtimeCredentialBinding(client.rootRuntimeCredential);
|
|
204
|
+
const projectUrl = canonicalUrlOrUndefined(options.projectUrl ?? client.projectUrl);
|
|
205
|
+
const agentSecurityUrl = canonicalUrlOrUndefined(client.agentSecurityUrl);
|
|
206
|
+
if (!credential || !projectUrl || !agentSecurityUrl)
|
|
207
|
+
return false;
|
|
208
|
+
const sessionKey = options.sessionId?.trim() || config_js_1.SESSIONLESS_KEY;
|
|
209
|
+
const entry = {
|
|
210
|
+
credential,
|
|
211
|
+
projectUrl,
|
|
212
|
+
agentSecurityUrl,
|
|
213
|
+
subjectId: context.subjectId,
|
|
214
|
+
rootDelegation: context.rootDelegation,
|
|
215
|
+
deploymentId: context.deploymentId,
|
|
216
|
+
agentType: context.agentType,
|
|
217
|
+
};
|
|
218
|
+
(0, config_js_1.mutateConfig)((current) => {
|
|
219
|
+
const existing = current.runtimeContexts?.[options.harness] ?? {};
|
|
220
|
+
const previous = existing[sessionKey];
|
|
221
|
+
if (previous && JSON.stringify(previous) === JSON.stringify(entry))
|
|
222
|
+
return current;
|
|
223
|
+
const merged = { ...existing };
|
|
224
|
+
delete merged[sessionKey];
|
|
225
|
+
merged[sessionKey] = entry;
|
|
226
|
+
const keys = Object.keys(merged);
|
|
227
|
+
const kept = keys.slice(Math.max(0, keys.length - config_js_1.DELEGATION_NODE_HISTORY));
|
|
228
|
+
return {
|
|
229
|
+
...current,
|
|
230
|
+
runtimeContexts: {
|
|
231
|
+
...(current.runtimeContexts ?? {}),
|
|
232
|
+
[options.harness]: Object.fromEntries(kept.map((key) => [key, merged[key]])),
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Restore a persisted runtime context into a fresh client. Loads only: it
|
|
240
|
+
* succeeds when the persisted entry is bound to the active runtime credential,
|
|
241
|
+
* the client's project and Agent Security URLs, and the expected agent type.
|
|
242
|
+
*/
|
|
243
|
+
function restoreRuntimeContext(client, options) {
|
|
244
|
+
const active = client.runtimeContext;
|
|
245
|
+
if (active)
|
|
246
|
+
return active;
|
|
247
|
+
const credential = client.rootRuntimeCredential;
|
|
248
|
+
const binding = runtimeCredentialBinding(credential);
|
|
249
|
+
if (!credential || !binding)
|
|
250
|
+
return undefined;
|
|
251
|
+
const sessionKey = (options.sessionId ?? client.sessionId)?.trim() || config_js_1.SESSIONLESS_KEY;
|
|
252
|
+
const entry = (0, config_js_1.loadConfig)().runtimeContexts?.[options.harness]?.[sessionKey];
|
|
253
|
+
if (!entry ||
|
|
254
|
+
entry.credential !== binding ||
|
|
255
|
+
entry.projectUrl !== canonicalUrlOrUndefined(client.projectUrl) ||
|
|
256
|
+
entry.agentSecurityUrl !== canonicalUrlOrUndefined(client.agentSecurityUrl) ||
|
|
257
|
+
entry.agentType !== (options.agentType ?? options.harness)) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
let context;
|
|
261
|
+
try {
|
|
262
|
+
context = parseRuntimeContext({
|
|
263
|
+
agent_type: entry.agentType,
|
|
264
|
+
authenticated: true,
|
|
265
|
+
deployment_id: entry.deploymentId,
|
|
266
|
+
root_delegation: entry.rootDelegation,
|
|
267
|
+
subject_id: entry.subjectId,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
activateRuntimeContext(client, credential, context);
|
|
154
274
|
return context;
|
|
155
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* True when a hook may act for a headless deployment owner: an
|
|
278
|
+
* administrator-supplied runtime credential (`ORY_AGENT_API_KEY` or an injected
|
|
279
|
+
* authenticator) is configured, no user principal is known, and no complete
|
|
280
|
+
* environment package already identifies the runtime. A Talos credential the
|
|
281
|
+
* plugin enrolled for an interactive user never qualifies.
|
|
282
|
+
*/
|
|
283
|
+
function runtimeOwnerContextEligible(client, env) {
|
|
284
|
+
if (client.userPrincipal.subject || env.ORY_USER_SUBJECT_ID?.trim())
|
|
285
|
+
return false;
|
|
286
|
+
if (resolvePreprovisionedRootDelegation(env, undefined, client.runtimeCredential))
|
|
287
|
+
return false;
|
|
288
|
+
return Boolean(env.ORY_AGENT_API_KEY?.trim()) || client.rootRuntimeCredential?.kind === "injected";
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Make the headless deployment context available to a tool or event path.
|
|
292
|
+
*
|
|
293
|
+
* Restores the session's persisted context (no network). When none exists —
|
|
294
|
+
* for example a harness that skipped its session-start hook — and
|
|
295
|
+
* `allowResolve` is set, it resolves the context once with a bounded request
|
|
296
|
+
* and persists it so later hook processes only load. A failed resolution is
|
|
297
|
+
* not retried by the same client for {@link LAZY_RUNTIME_CONTEXT_RETRY_MS}.
|
|
298
|
+
* Never throws.
|
|
299
|
+
*/
|
|
300
|
+
async function ensureRuntimeContext(client, options) {
|
|
301
|
+
const active = client.runtimeContext;
|
|
302
|
+
if (active)
|
|
303
|
+
return active;
|
|
304
|
+
const env = options.env ?? process.env;
|
|
305
|
+
try {
|
|
306
|
+
if (!runtimeOwnerContextEligible(client, env))
|
|
307
|
+
return undefined;
|
|
308
|
+
const restored = restoreRuntimeContext(client, options);
|
|
309
|
+
// Resolve only as the root runtime: an acting child credential must not
|
|
310
|
+
// stand in for its deployment's root.
|
|
311
|
+
if (restored || !options.allowResolve || (0, context_js_1.currentSessionContext)().runtimeCredential) {
|
|
312
|
+
return restored;
|
|
313
|
+
}
|
|
314
|
+
const sessionKey = (options.sessionId ?? client.sessionId)?.trim() || config_js_1.SESSIONLESS_KEY;
|
|
315
|
+
const attempts = lazyRuntimeContextAttempts.get(client) ?? new Map();
|
|
316
|
+
lazyRuntimeContextAttempts.set(client, attempts);
|
|
317
|
+
const lastAttempt = attempts.get(sessionKey);
|
|
318
|
+
if (lastAttempt !== undefined && Date.now() - lastAttempt < LAZY_RUNTIME_CONTEXT_RETRY_MS) {
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
attempts.set(sessionKey, Date.now());
|
|
322
|
+
const context = await resolvePreprovisionedRuntimeContext(client, {
|
|
323
|
+
projectUrl: client.projectUrl,
|
|
324
|
+
expectedAgentType: options.agentType ?? options.harness,
|
|
325
|
+
env,
|
|
326
|
+
fetchImpl: options.fetchImpl,
|
|
327
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? LAZY_RUNTIME_CONTEXT_TIMEOUT_MS),
|
|
328
|
+
});
|
|
329
|
+
if (context) {
|
|
330
|
+
attempts.delete(sessionKey);
|
|
331
|
+
saveRuntimeContext(client, context, { harness: options.harness, sessionId: sessionKey });
|
|
332
|
+
}
|
|
333
|
+
return context;
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
client.logger.warn("agent.runtime_context.unavailable", {
|
|
337
|
+
message: err instanceof Error ? err.message : String(err),
|
|
338
|
+
});
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
156
342
|
async function withEnrollmentLock(identity, work) {
|
|
157
343
|
const lockRoot = (0, node_path_1.join)((0, config_js_1.getDataDir)(), "runtime-credential-locks");
|
|
158
344
|
const lockPath = (0, node_path_1.join)(lockRoot, `${(0, runtime_credential_js_1.credentialAccount)(identity)}.lock`);
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repo": "ory-agent-plugins",
|
|
3
|
-
"commit": "
|
|
4
|
-
"commitShort": "
|
|
3
|
+
"commit": "b63441d792bda2410aab728652a6ca4ff633e418",
|
|
4
|
+
"commitShort": "b63441d7",
|
|
5
5
|
"branch": "main",
|
|
6
|
-
"commitDate": "2026-10-
|
|
6
|
+
"commitDate": "2026-10-22T11:27:22-07:00",
|
|
7
7
|
"dirty": false,
|
|
8
|
-
"builtAt": "2026-10-
|
|
8
|
+
"builtAt": "2026-10-22T18:31:32.704Z"
|
|
9
9
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -140,6 +140,8 @@ export declare class OryAgentClient {
|
|
|
140
140
|
constructor(config: OryAgentConfig);
|
|
141
141
|
/** Build a fresh set of Ory API instances using the current agent token. */
|
|
142
142
|
private buildApis;
|
|
143
|
+
/** Ory project URL this client sends with Agent Security requests. */
|
|
144
|
+
get projectUrl(): string;
|
|
143
145
|
/** Canonical broker origin, with the shipped project URL fallback applied. */
|
|
144
146
|
get agentSecurityUrl(): string;
|
|
145
147
|
private buildBrokerPermissionTransport;
|
|
@@ -154,9 +156,22 @@ export declare class OryAgentClient {
|
|
|
154
156
|
get agentPrincipal(): PrincipalIdentity;
|
|
155
157
|
/** Active noninteractive broker credential. It is process memory only. */
|
|
156
158
|
get runtimeCredential(): RuntimeCredential | undefined;
|
|
159
|
+
/** The session's root broker credential, ignoring an ambient child credential. */
|
|
160
|
+
get rootRuntimeCredential(): RuntimeCredential | undefined;
|
|
157
161
|
get hasRuntimeCredential(): boolean;
|
|
158
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* Active deployment context. It is never copied to the environment; a
|
|
164
|
+
* subprocess harness restores it from the credential-bound, non-secret
|
|
165
|
+
* per-session record written at session start (see `restoreRuntimeContext`).
|
|
166
|
+
*/
|
|
159
167
|
get runtimeContext(): RuntimeContext | undefined;
|
|
168
|
+
/**
|
|
169
|
+
* True when this client runs as an administrator-preprovisioned runtime whose
|
|
170
|
+
* owner only the broker knows: a resolved runtime context, or a complete
|
|
171
|
+
* `ORY_AGENT_ROOT_DELEGATION` + `ORY_AGENT_SUBJECT_ID` environment package
|
|
172
|
+
* with its runtime credential.
|
|
173
|
+
*/
|
|
174
|
+
get actsForRuntimeOwner(): boolean;
|
|
160
175
|
get runtimeRootDelegation(): string | undefined;
|
|
161
176
|
setRuntimeContext(context: RuntimeContext): void;
|
|
162
177
|
setRuntimeCredential(credential: RuntimeCredential | undefined): void;
|
package/dist/client.js
CHANGED
|
@@ -180,6 +180,10 @@ class OryAgentClient {
|
|
|
180
180
|
permission: this.buildBrokerPermissionTransport(),
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
|
+
/** Ory project URL this client sends with Agent Security requests. */
|
|
184
|
+
get projectUrl() {
|
|
185
|
+
return this.config.projectUrl;
|
|
186
|
+
}
|
|
183
187
|
/** Canonical broker origin, with the shipped project URL fallback applied. */
|
|
184
188
|
get agentSecurityUrl() {
|
|
185
189
|
return this.config.agentSecurityUrl ?? this.config.projectUrl;
|
|
@@ -268,7 +272,7 @@ class OryAgentClient {
|
|
|
268
272
|
}
|
|
269
273
|
// A preprovisioned runtime with no local user: the broker derives the
|
|
270
274
|
// owner from the authenticated runtime, and no owner ID is sent.
|
|
271
|
-
if (userSubject === undefined && subject.object === subject_js_1.RUNTIME_OWNER_SUBJECT && this.
|
|
275
|
+
if (userSubject === undefined && subject.object === subject_js_1.RUNTIME_OWNER_SUBJECT && this.actsForRuntimeOwner) {
|
|
272
276
|
return { kind: "owner", namespace: subject.namespace };
|
|
273
277
|
}
|
|
274
278
|
throw httpError(403, "Permission owner does not match the authenticated user");
|
|
@@ -331,9 +335,11 @@ class OryAgentClient {
|
|
|
331
335
|
}
|
|
332
336
|
/** Active noninteractive broker credential. It is process memory only. */
|
|
333
337
|
get runtimeCredential() {
|
|
338
|
+
return (0, context_js_1.currentSessionContext)().runtimeCredential ?? this.rootRuntimeCredential;
|
|
339
|
+
}
|
|
340
|
+
/** The session's root broker credential, ignoring an ambient child credential. */
|
|
341
|
+
get rootRuntimeCredential() {
|
|
334
342
|
const context = (0, context_js_1.currentSessionContext)();
|
|
335
|
-
if (context.runtimeCredential)
|
|
336
|
-
return context.runtimeCredential;
|
|
337
343
|
if (context.sessionId) {
|
|
338
344
|
const credential = this._runtimeCredentialsBySession.get(context.sessionId);
|
|
339
345
|
if (credential)
|
|
@@ -341,7 +347,7 @@ class OryAgentClient {
|
|
|
341
347
|
}
|
|
342
348
|
if (this._runtimeCredential)
|
|
343
349
|
return this._runtimeCredential;
|
|
344
|
-
const principal = this.
|
|
350
|
+
const principal = this.agentPrincipalForSession(this.sessionId);
|
|
345
351
|
return principal.token
|
|
346
352
|
? (0, runtime_credential_js_1.talosRuntimeCredential)(principal.token, { subject: principal.subject })
|
|
347
353
|
: undefined;
|
|
@@ -349,10 +355,25 @@ class OryAgentClient {
|
|
|
349
355
|
get hasRuntimeCredential() {
|
|
350
356
|
return this.runtimeCredential !== undefined;
|
|
351
357
|
}
|
|
352
|
-
/**
|
|
358
|
+
/**
|
|
359
|
+
* Active deployment context. It is never copied to the environment; a
|
|
360
|
+
* subprocess harness restores it from the credential-bound, non-secret
|
|
361
|
+
* per-session record written at session start (see `restoreRuntimeContext`).
|
|
362
|
+
*/
|
|
353
363
|
get runtimeContext() {
|
|
354
364
|
return this._runtimeContext ? { ...this._runtimeContext } : undefined;
|
|
355
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* True when this client runs as an administrator-preprovisioned runtime whose
|
|
368
|
+
* owner only the broker knows: a resolved runtime context, or a complete
|
|
369
|
+
* `ORY_AGENT_ROOT_DELEGATION` + `ORY_AGENT_SUBJECT_ID` environment package
|
|
370
|
+
* with its runtime credential.
|
|
371
|
+
*/
|
|
372
|
+
get actsForRuntimeOwner() {
|
|
373
|
+
if (this._runtimeContext)
|
|
374
|
+
return true;
|
|
375
|
+
return (0, agent_auth_js_1.resolvePreprovisionedRootDelegation)(process.env, undefined, this.runtimeCredential) !== undefined;
|
|
376
|
+
}
|
|
356
377
|
get runtimeRootDelegation() {
|
|
357
378
|
return this._runtimeContext?.rootDelegation;
|
|
358
379
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -104,6 +104,20 @@ export interface OryAgentDynamicCredentials {
|
|
|
104
104
|
* bounded so a long-lived install's config doesn't grow without limit.
|
|
105
105
|
*/
|
|
106
106
|
export declare const DELEGATION_NODE_HISTORY = 10;
|
|
107
|
+
/**
|
|
108
|
+
* A runtime context resolved for one headless session, persisted without any
|
|
109
|
+
* secret. `credential` is a SHA-256 binding of the runtime credential (never the
|
|
110
|
+
* credential itself); `projectUrl` and `agentSecurityUrl` are canonical URLs.
|
|
111
|
+
*/
|
|
112
|
+
export interface PersistedRuntimeContext {
|
|
113
|
+
credential: string;
|
|
114
|
+
projectUrl: string;
|
|
115
|
+
agentSecurityUrl: string;
|
|
116
|
+
subjectId: string;
|
|
117
|
+
rootDelegation: string;
|
|
118
|
+
deploymentId: string;
|
|
119
|
+
agentType: string;
|
|
120
|
+
}
|
|
107
121
|
/** Opaque, administrator-provisioned root delegation node for headless agents. */
|
|
108
122
|
export declare const AGENT_ROOT_DELEGATION_ENV_VAR = "ORY_AGENT_ROOT_DELEGATION";
|
|
109
123
|
/** Validate an opaque broker node without interpreting its server-owned format. */
|
|
@@ -213,6 +227,14 @@ export interface OryPluginConfig {
|
|
|
213
227
|
workspaceName?: string;
|
|
214
228
|
/** Opaque broker node ids keyed by harness and session. Contains no credentials. */
|
|
215
229
|
delegationAnchors?: Record<string, Record<string, string>>;
|
|
230
|
+
/**
|
|
231
|
+
* Non-secret headless deployment context keyed by harness and session, so a
|
|
232
|
+
* later subprocess hook of the same session can act for the deployment owner
|
|
233
|
+
* without a runtime-context request per tool call. Each entry is bound to a
|
|
234
|
+
* fingerprint of the runtime credential and to the project and Agent Security
|
|
235
|
+
* URLs it was resolved for; a different credential or project never reuses it.
|
|
236
|
+
*/
|
|
237
|
+
runtimeContexts?: Record<string, Record<string, PersistedRuntimeContext>>;
|
|
216
238
|
/**
|
|
217
239
|
* Public OAuth2 client id used by the user PKCE browser flow when
|
|
218
240
|
* the interactive user login runs (every session). The client must be registered ahead of time
|
package/dist/config.js
CHANGED
|
@@ -62,6 +62,46 @@ const cli_invocation_js_1 = require("./cli-invocation.js");
|
|
|
62
62
|
* bounded so a long-lived install's config doesn't grow without limit.
|
|
63
63
|
*/
|
|
64
64
|
exports.DELEGATION_NODE_HISTORY = 10;
|
|
65
|
+
const PERSISTED_RUNTIME_CONTEXT_KEYS = [
|
|
66
|
+
"agentSecurityUrl",
|
|
67
|
+
"agentType",
|
|
68
|
+
"credential",
|
|
69
|
+
"deploymentId",
|
|
70
|
+
"projectUrl",
|
|
71
|
+
"rootDelegation",
|
|
72
|
+
"subjectId",
|
|
73
|
+
];
|
|
74
|
+
function parsePersistedRuntimeContext(value) {
|
|
75
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
76
|
+
return undefined;
|
|
77
|
+
const raw = value;
|
|
78
|
+
const entry = {};
|
|
79
|
+
for (const key of PERSISTED_RUNTIME_CONTEXT_KEYS) {
|
|
80
|
+
const field = raw[key];
|
|
81
|
+
if (typeof field !== "string" || !field.trim())
|
|
82
|
+
return undefined;
|
|
83
|
+
entry[key] = field;
|
|
84
|
+
}
|
|
85
|
+
return entry;
|
|
86
|
+
}
|
|
87
|
+
function parseRuntimeContexts(value) {
|
|
88
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
89
|
+
return undefined;
|
|
90
|
+
const result = {};
|
|
91
|
+
for (const [harness, rawSessions] of Object.entries(value)) {
|
|
92
|
+
if (!rawSessions || typeof rawSessions !== "object" || Array.isArray(rawSessions))
|
|
93
|
+
continue;
|
|
94
|
+
const sessions = {};
|
|
95
|
+
for (const [session, rawEntry] of Object.entries(rawSessions)) {
|
|
96
|
+
const entry = parsePersistedRuntimeContext(rawEntry);
|
|
97
|
+
if (entry)
|
|
98
|
+
sessions[session] = entry;
|
|
99
|
+
}
|
|
100
|
+
if (Object.keys(sessions).length > 0)
|
|
101
|
+
result[harness] = sessions;
|
|
102
|
+
}
|
|
103
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
104
|
+
}
|
|
65
105
|
/** Opaque, administrator-provisioned root delegation node for headless agents. */
|
|
66
106
|
exports.AGENT_ROOT_DELEGATION_ENV_VAR = "ORY_AGENT_ROOT_DELEGATION";
|
|
67
107
|
/** Validate an opaque broker node without interpreting its server-owned format. */
|
|
@@ -197,6 +237,7 @@ function loadConfig() {
|
|
|
197
237
|
: undefined,
|
|
198
238
|
installedHarnesses: parseInstalledHarnesses(parsed.installedHarnesses),
|
|
199
239
|
delegationAnchors: parseDelegationAnchors(parsed.delegationAnchors),
|
|
240
|
+
runtimeContexts: parseRuntimeContexts(parsed.runtimeContexts),
|
|
200
241
|
user: parseUserCredentials(parsed),
|
|
201
242
|
agent: parseAgentCredentials(parsed),
|
|
202
243
|
};
|
|
@@ -928,6 +969,7 @@ function compactPersistedConfig(config) {
|
|
|
928
969
|
? { installedHarnesses: config.installedHarnesses }
|
|
929
970
|
: {}),
|
|
930
971
|
...(config.delegationAnchors ? { delegationAnchors: config.delegationAnchors } : {}),
|
|
972
|
+
...(config.runtimeContexts ? { runtimeContexts: config.runtimeContexts } : {}),
|
|
931
973
|
...(user ? { user } : {}),
|
|
932
974
|
...(agent ? { agent } : {}),
|
|
933
975
|
};
|
|
@@ -48,6 +48,8 @@ export interface RuntimeCredential {
|
|
|
48
48
|
subject?: string;
|
|
49
49
|
/** @internal Non-secret binding used to recognize the same Talos key after re-resolution. */
|
|
50
50
|
apiKeyFingerprint?: string;
|
|
51
|
+
/** @internal Non-secret binding of an environment-configured SPIFFE workload identity. */
|
|
52
|
+
spiffeIdFingerprint?: string;
|
|
51
53
|
authenticate: RuntimeRequestAuthenticator;
|
|
52
54
|
}
|
|
53
55
|
export interface ResolveRuntimeCredentialOptions {
|
|
@@ -666,6 +666,7 @@ function resolveRuntimeCredential(options = {}) {
|
|
|
666
666
|
return {
|
|
667
667
|
kind: "injected",
|
|
668
668
|
...(options.subject ? { subject: options.subject } : {}),
|
|
669
|
+
spiffeIdFingerprint: (0, node_crypto_1.createHash)("sha256").update(expectedClientSpiffeId).digest("hex"),
|
|
669
670
|
authenticate: createSpiffeFileRequestAuthenticator({
|
|
670
671
|
...(generationPath ? {
|
|
671
672
|
generationPath,
|
package/dist/subject.d.ts
CHANGED
|
@@ -61,8 +61,8 @@ export declare function runWithUserSubject<T>(subject: string | undefined, fn: (
|
|
|
61
61
|
* Resolve the user subject for permission checks. Prefers a per-call
|
|
62
62
|
* override installed via {@link runWithUserSubject}, then the user
|
|
63
63
|
* login's `userPrincipal.subject`, falls back to `ORY_USER_SUBJECT_ID`, then the
|
|
64
|
-
*
|
|
65
|
-
*
|
|
64
|
+
* deployment owner of a preprovisioned runtime ({@link RUNTIME_OWNER_SUBJECT}),
|
|
65
|
+
* then the legacy `ORY_AGENT_SUBJECT_ID`, then the caller-supplied `fallback`
|
|
66
66
|
* (typically `session:<id>`).
|
|
67
67
|
*
|
|
68
68
|
* Always returns a SubjectSet. A resolved principal is addressed under the
|
package/dist/subject.js
CHANGED
|
@@ -80,8 +80,8 @@ function subjectSet(namespace, object) {
|
|
|
80
80
|
* Resolve the user subject for permission checks. Prefers a per-call
|
|
81
81
|
* override installed via {@link runWithUserSubject}, then the user
|
|
82
82
|
* login's `userPrincipal.subject`, falls back to `ORY_USER_SUBJECT_ID`, then the
|
|
83
|
-
*
|
|
84
|
-
*
|
|
83
|
+
* deployment owner of a preprovisioned runtime ({@link RUNTIME_OWNER_SUBJECT}),
|
|
84
|
+
* then the legacy `ORY_AGENT_SUBJECT_ID`, then the caller-supplied `fallback`
|
|
85
85
|
* (typically `session:<id>`).
|
|
86
86
|
*
|
|
87
87
|
* Always returns a SubjectSet. A resolved principal is addressed under the
|
|
@@ -92,22 +92,26 @@ function subjectSet(namespace, object) {
|
|
|
92
92
|
* marker that must never match a stored tuple.
|
|
93
93
|
*/
|
|
94
94
|
function resolveUserSubject(client, fallback) {
|
|
95
|
-
const principal = perCallUserSubject.getStore()
|
|
96
|
-
?? client.userPrincipal.subject
|
|
97
|
-
?? process.env.ORY_USER_SUBJECT_ID
|
|
98
|
-
// Validated, not read raw: the same value feeds the agent subject, so a
|
|
99
|
-
// value rejected there must not slip in here (#241).
|
|
100
|
-
?? readAgentSubjectOverride();
|
|
101
95
|
const ns = process.env.ORY_USER_SUBJECT_NAMESPACE?.trim()
|
|
102
96
|
|| client.userSubjectNamespace
|
|
103
97
|
|| exports.DEFAULT_USER_SUBJECT_NAMESPACE;
|
|
98
|
+
const principal = perCallUserSubject.getStore()
|
|
99
|
+
?? client.userPrincipal.subject
|
|
100
|
+
?? process.env.ORY_USER_SUBJECT_ID;
|
|
104
101
|
if (principal)
|
|
105
102
|
return subjectSet(ns, principal);
|
|
106
103
|
// A preprovisioned runtime acts for its deployment's owner, whom only the
|
|
107
104
|
// broker knows. Without this, a headless deployment resolves no subject and
|
|
108
|
-
// every native tool is denied in enforce mode (#338).
|
|
109
|
-
|
|
105
|
+
// every native tool is denied in enforce mode (#338). Its
|
|
106
|
+
// `ORY_AGENT_SUBJECT_ID` names the runtime Agent, never a user, so the legacy
|
|
107
|
+
// fallback below does not apply to it.
|
|
108
|
+
if (client.actsForRuntimeOwner)
|
|
110
109
|
return subjectSet(ns, exports.RUNTIME_OWNER_SUBJECT);
|
|
110
|
+
// Validated, not read raw: the same value feeds the agent subject, so a
|
|
111
|
+
// value rejected there must not slip in here (#241).
|
|
112
|
+
const legacyAgentSubject = readAgentSubjectOverride();
|
|
113
|
+
if (legacyAgentSubject)
|
|
114
|
+
return subjectSet(ns, legacyAgentSubject);
|
|
111
115
|
// No principal — use the caller's fallback. A `session:<id>` fallback is
|
|
112
116
|
// addressed under the Session namespace so it is a well-formed SubjectSet that
|
|
113
117
|
// could match a Session-scoped grant, rather than being wrapped under `User`.
|
package/dist/uninstall.js
CHANGED
|
@@ -229,6 +229,14 @@ async function clearCredentialsForUninstall(options = {}) {
|
|
|
229
229
|
else
|
|
230
230
|
delete next.delegationAnchors;
|
|
231
231
|
}
|
|
232
|
+
if (current.runtimeContexts?.[harness]) {
|
|
233
|
+
const contexts = { ...current.runtimeContexts };
|
|
234
|
+
delete contexts[harness];
|
|
235
|
+
if (Object.keys(contexts).length > 0)
|
|
236
|
+
next.runtimeContexts = contexts;
|
|
237
|
+
else
|
|
238
|
+
delete next.runtimeContexts;
|
|
239
|
+
}
|
|
232
240
|
return next;
|
|
233
241
|
});
|
|
234
242
|
log(`Removed the ${harness} agent identities. Shared Ory configuration is kept for: ${remainingHarnesses.join(", ")}.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/argus",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "Ory Argus: the core API for building authentication, authorization, and audit into AI agent harness plugins, extensions, and custom integrations",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ory.com",
|