@ory/argus 1.4.1 → 1.4.3

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.
@@ -25,13 +25,14 @@ print, inspect, commit, or place in a prompt:
25
25
 
26
26
  - `ORY_USER_OAUTH2_TOKEN`
27
27
  - `ANTHROPIC_API_KEY`
28
- - any value from `.ory/credentials.env`
28
+ - any streamed deployment credential value
29
29
  - SPIFFE private keys or SVID file contents
30
30
 
31
31
  The user OAuth2 token administers deployments as the signed-in identity. The
32
32
  generated `ORY_AGENT_API_KEY` or customer-issued SVID is a separate runtime
33
33
  credential. Never exchange their roles. Do not revoke a deployment or workload
34
- registration unless the user explicitly asks.
34
+ registration that predates the current run. Always revoke the fresh deployment
35
+ created for the current streamed-credential run during cleanup.
35
36
 
36
37
  ## Prerequisites
37
38
 
@@ -145,8 +146,9 @@ to `0.0.0.0`, expose the application port, and define a health check. Ignore
145
146
  credentials, deployment state, dependencies, build output, and local environment
146
147
  files in Git and the Docker context.
147
148
 
148
- For Talos, load `.ory/credentials.env` with Compose `env_file` or the equivalent
149
- Kubernetes Secret. For SPIFFE, use the file-backed environment contract in
149
+ For Talos, the trusted deployment controller must inject the streamed values
150
+ directly into the workload's runtime secret facility without creating a local
151
+ credential file. For SPIFFE, use the file-backed environment contract in
150
152
  `examples/spiffe`: mount the parent of the atomically switched `current`
151
153
  generation read-only, and point `ORY_AGENT_SPIFFE_GENERATION_PATH` at `current`.
152
154
  TypeScript `OryAgentClient.fromEnv()` and Python `OryAgentClient.from_env()` use
@@ -212,39 +214,31 @@ explicit owner must equal the authenticated identity.
212
214
 
213
215
  ## Create and deploy
214
216
 
215
- Create and decrypt the one-time package:
217
+ Let the CLI create a fresh deployment and launch the trusted deployment
218
+ controller:
216
219
 
217
220
  ```bash
218
- ory-agent deployment ensure --credential-file .ory/credentials.env
221
+ ory-agent deployment run \
222
+ --result-file "$RUNNER_TEMP/ory-agent-result.json" \
223
+ -- trusted-deployer --credentials-stdin
219
224
  ```
220
225
 
221
- The CLI creates an ephemeral age identity, verifies the package digest, and
222
- atomically writes `.ory/credentials.env` and `.ory/deployment.json` with mode
223
- `0600`. Verify only that the files exist, are non-empty, ignored by Git, and
224
- excluded from the Docker build context.
225
-
226
- If an existing deployment is reused but the credential file is absent, stop.
227
- For Talos, the one-time secret cannot be reissued. Explain that replacement
228
- requires an explicit revoke, but do not revoke without approval. SPIFFE key
229
- material is never issued by this command; a missing SVID is an operator-side
230
- workload identity problem and must not trigger Talos fallback.
231
-
232
- Start and validate:
233
-
234
- ```bash
235
- docker compose up --build --detach
236
- ```
237
-
238
- Verify `/healthz` and invoke `/probe` first. The guarded probe makes the first
239
- authenticated runtime request that activates a ready deployment. Then run:
240
-
241
- ```bash
242
- ory-agent deployment wait --for active --timeout 10m
243
- ory-agent deployment status
244
- ```
245
-
246
- Finally invoke `/run` with a prompt that calls `deployment_probe`. Diagnose
247
- failures with secret-redacted logs.
226
+ The CLI sends the exact credential only to the child process's anonymous stdin,
227
+ never public stdout, a shell variable, log, or intermediate file. It strips its
228
+ deployment-administration credentials from the child environment. The optional
229
+ mode-`0600` result is non-secret and contains the newly created deployment ID.
230
+ Every invocation issues a new deployment and writes no credential, state,
231
+ pending journal, plaintext stage, or age identity.
232
+
233
+ The trusted controller must load stdin in memory, inject the values into the
234
+ workload secret facility, start and supervise the workload, verify `/healthz`,
235
+ invoke the guarded `/probe`, and exit only when that bounded workload run is
236
+ finished. For a long-running service, the controller must remain its supervisor;
237
+ do not detach and exit while the workload still needs the credential. After a
238
+ successful controller exit, the CLI waits for **Active** and automatically revokes.
239
+ It also revokes after controller failure, and a revoke failure makes the command
240
+ fail. Process termination before cleanup can still orphan a deployment; inventory
241
+ and explicitly revoke it rather than assuming a rerun will recover or reuse it.
248
242
 
249
243
  ## Customer-operated SPIFFE
250
244
 
@@ -257,7 +251,7 @@ customer exclusively owns and operates:
257
251
  - helper/CSI/sidecar availability and the permissions on mounted key material
258
252
 
259
253
  Ory records the expected workload identity and authenticates broker requests.
260
- Neither `ory-agent deployment ensure` nor the Ory packages install SPIRE, choose
254
+ Neither `ory-agent deployment run` nor the Ory packages install SPIRE, choose
261
255
  selectors, create registrations, issue SVIDs, rotate files, or revoke workload
262
256
  entries.
263
257
 
@@ -311,5 +305,6 @@ in the customer's non-production identity domain:
311
305
  Report changed files, format/typecheck/test/build results, Docker health,
312
306
  non-secret deployment ID and status, fixed-query and user-query probe results,
313
307
  activation status, and blockers. Never report credentials, environment values,
314
- authorization headers, or credential-file contents. Leave resources running for
315
- inspection unless the user asks for cleanup.
308
+ authorization headers, or credential-file contents. The trusted controller must
309
+ clean up its bounded workload before exiting; `deployment run` then revokes the
310
+ fresh Agent Security deployment automatically.
@@ -16,8 +16,9 @@ Use this playbook to turn the user's intent into a complete, secured deployment:
16
16
  The user should be able to ask for an agent in product terms. Inspect the repository,
17
17
  preserve its package manager and conventions, scaffold the application and controller,
18
18
  run validation, and deploy it through the commands below. Ask before overwriting an
19
- existing application or revoking a deployment. Ask for missing credentials; never
20
- fabricate them.
19
+ existing application or revoking a deployment that predates the current run. Always
20
+ revoke the fresh streamed-credential deployment during cleanup. Ask for missing
21
+ credentials; never fabricate them.
21
22
 
22
23
  Use `ory-build-agent` for another SDK and `ory-deploy-agent` for the canonical Claude
23
24
  Agent SDK Docker/Kubernetes deployment. Use `ory-e2b-sandbox` only when installing an
@@ -28,8 +29,8 @@ SDK application deployment.
28
29
 
29
30
  Keep these components separate:
30
31
 
31
- 1. The trusted controller holds `E2B_API_KEY`, `OPENAI_API_KEY`, and the decrypted
32
- `.ory/credentials.env` values.
32
+ 1. The trusted controller holds `E2B_API_KEY`, `OPENAI_API_KEY`, and the streamed
33
+ deployment values in memory.
33
34
  2. The E2B template contains only source and pinned dependencies. It contains no
34
35
  credentials and no Agent start command.
35
36
  3. The sandbox receives `OPENAI_API_KEY` and `ORY_*` only in the Agent command's
@@ -41,7 +42,7 @@ Never print, inspect, commit, upload, or place in a prompt:
41
42
 
42
43
  - `OPENAI_API_KEY` or `E2B_API_KEY`
43
44
  - `ORY_AGENT_API_KEY`
44
- - any value from `.ory/credentials.env`
45
+ - any streamed deployment credential value
45
46
  - a user OAuth2 token
46
47
 
47
48
  Never inject `ORY_USER_OAUTH2_TOKEN` into E2B. The deployment package is a complete
@@ -222,22 +223,28 @@ package_recipe_version: v1
222
223
  credential_method: talos_api_key
223
224
  ```
224
225
 
225
- Review the plan. Then create the one-time package:
226
+ Review the plan. Then let the CLI create a fresh deployment and launch the
227
+ trusted E2B controller:
226
228
 
227
229
  ```bash
228
- ory-agent deployment ensure --credential-file .ory/credentials.env
230
+ ory-agent deployment run \
231
+ --result-file "$RUNNER_TEMP/ory-agent-result.json" \
232
+ -- trusted-e2b-controller --credentials-stdin
229
233
  ```
230
234
 
231
- The CLI verifies the package and atomically writes `.ory/credentials.env` and the
232
- non-secret `.ory/deployment.json` with mode `0600`. Verify only existence, mode,
233
- non-empty size, Git ignore coverage, and exclusion from build contexts. Never read the
234
- credential file into chat or terminal output. If a deployment is reused but the one-time
235
- credential file is missing, stop and explain that replacement requires explicit
236
- revocation. Do not revoke without approval.
235
+ The CLI sends credentials only through the child process's anonymous stdin,
236
+ never public stdout, a shell variable, log, or intermediate file. It strips
237
+ deployment-administration credentials from the child environment. The atomic
238
+ mode-`0600` result contains the non-secret new deployment ID. `run` writes no
239
+ credential, state, pending journal, plaintext stage, or age identity. It waits
240
+ for **Active** after the controller succeeds and automatically revokes on every
241
+ normal success or failure path. A failed revoke fails the command. Process
242
+ termination before cleanup can still orphan a deployment, so inventory and
243
+ explicitly revoke it rather than assuming a rerun will recover it.
237
244
 
238
245
  ## Launch through the trusted controller
239
246
 
240
- Load `.ory/credentials.env` into a validated in-memory map without logging it. Require
247
+ Load credential stdin into a validated in-memory map without logging it. Require
241
248
  exactly the deployment variables emitted by Agent Security, including
242
249
  `ORY_AGENT_API_KEY`, `ORY_AGENT_SUBJECT_ID`, and `ORY_AGENT_ROOT_DELEGATION`. Add
243
250
  `OPENAI_API_KEY` only to the Agent command environment.
@@ -271,9 +278,8 @@ runtime request. Therefore use this order:
271
278
  1. Launch a canary sandbox from the immutable build.
272
279
  2. Run the fixed guarded probe inside it with command-scoped credentials.
273
280
  3. Verify the expected output files exist without dumping process environment.
274
- 4. Run `ory-agent deployment wait --for active --timeout 10m`.
275
- 5. Run `ory-agent deployment status`.
276
- 6. Run the user's page-generation prompt and validate the static output.
281
+ 4. Run the user's page-generation prompt and validate the static output.
282
+ 5. Exit successfully so `ory-agent deployment run` verifies **Active** and revokes.
277
283
 
278
284
  Start in Agent Security `observe`, review the recorded decision and activity, grant the
279
285
  expected tool, deliberately promote policy to `enforce`, and prove an explicit deny keeps
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "repo": "ory-agent-plugins",
3
- "commit": "5901fea77c25766073ad2f41fab14875d20f4652",
4
- "commitShort": "5901fea",
3
+ "commit": "6c598d14487dbc18be2d815b2014776642150ca0",
4
+ "commitShort": "6c598d1",
5
5
  "branch": "main",
6
- "commitDate": "2026-10-01T13:04:44-07:00",
6
+ "commitDate": "2026-10-02T18:32:54-07:00",
7
7
  "dirty": false,
8
- "builtAt": "2026-10-01T20:08:53.706Z"
8
+ "builtAt": "2026-10-03T01:37:16.649Z"
9
9
  }
package/dist/client.js CHANGED
@@ -44,6 +44,7 @@ const agent_auth_js_1 = require("./agent-auth.js");
44
44
  const context_js_1 = require("./context.js");
45
45
  const config_js_1 = require("./config.js");
46
46
  const auth_store_js_1 = require("./auth-store.js");
47
+ const subject_js_1 = require("./subject.js");
47
48
  const runtime_credential_js_1 = require("./runtime-credential.js");
48
49
  const BROKER_PERMISSION_BATCH_SIZE = 10;
49
50
  function permissionCheckFromRequest(request) {
@@ -261,7 +262,13 @@ class OryAgentClient {
261
262
  || this.config.userSubjectNamespace
262
263
  || "User";
263
264
  if (subject.namespace === ownerNamespace) {
264
- if (subject.object === this.userPrincipal.subject) {
265
+ const userSubject = this.userPrincipal.subject;
266
+ if (subject.object === userSubject) {
267
+ return { kind: "owner", namespace: subject.namespace };
268
+ }
269
+ // A preprovisioned runtime with no local user: the broker derives the
270
+ // owner from the authenticated runtime, and no owner ID is sent.
271
+ if (userSubject === undefined && subject.object === subject_js_1.RUNTIME_OWNER_SUBJECT && this._runtimeContext) {
265
272
  return { kind: "owner", namespace: subject.namespace };
266
273
  }
267
274
  throw httpError(403, "Permission owner does not match the authenticated user");
@@ -43,7 +43,12 @@ export declare class AgentSecurityEventReporter {
43
43
  private scheduled;
44
44
  private activeRequest;
45
45
  private flushDeadline;
46
- private suppressRetry;
46
+ /**
47
+ * Set when a flush deadline passes. The current delivery stops starting new
48
+ * requests so a short-lived hook process can exit on time; undelivered events
49
+ * stay queued (and in the durable outbox) for a later delivery.
50
+ */
51
+ private flushExpired;
47
52
  private retryTimer;
48
53
  private retryAttempt;
49
54
  private readonly retryBaseMs;
@@ -56,6 +61,11 @@ export declare class AgentSecurityEventReporter {
56
61
  enqueue(entry: ActivityEntry, principalKey?: string): void;
57
62
  /** Attempt all currently deliverable batches, bounded by the caller's deadline. */
58
63
  flush(timeoutMs?: number): Promise<void>;
64
+ /**
65
+ * The request timeout shares the flush deadline and can abort a request before
66
+ * the flush timer fires, so check the deadline itself as well as the flag.
67
+ */
68
+ private flushDeadlinePassed;
59
69
  private schedule;
60
70
  private startDelivery;
61
71
  private scheduleRetry;
@@ -99,7 +99,12 @@ class AgentSecurityEventReporter {
99
99
  scheduled = false;
100
100
  activeRequest;
101
101
  flushDeadline;
102
- suppressRetry = false;
102
+ /**
103
+ * Set when a flush deadline passes. The current delivery stops starting new
104
+ * requests so a short-lived hook process can exit on time; undelivered events
105
+ * stay queued (and in the durable outbox) for a later delivery.
106
+ */
107
+ flushExpired = false;
103
108
  retryTimer;
104
109
  retryAttempt = 0;
105
110
  retryBaseMs;
@@ -173,7 +178,7 @@ class AgentSecurityEventReporter {
173
178
  work,
174
179
  new Promise((resolve) => {
175
180
  timer = setTimeout(() => {
176
- this.suppressRetry = true;
181
+ this.flushExpired = true;
177
182
  this.activeRequest?.abort();
178
183
  resolve();
179
184
  }, timeoutMs);
@@ -185,6 +190,13 @@ class AgentSecurityEventReporter {
185
190
  if (this.flushDeadline === deadline)
186
191
  this.flushDeadline = undefined;
187
192
  }
193
+ /**
194
+ * The request timeout shares the flush deadline and can abort a request before
195
+ * the flush timer fires, so check the deadline itself as well as the flag.
196
+ */
197
+ flushDeadlinePassed() {
198
+ return this.flushExpired || (this.flushDeadline !== undefined && Date.now() >= this.flushDeadline);
199
+ }
188
200
  schedule() {
189
201
  if (this.scheduled || this.delivery)
190
202
  return;
@@ -207,7 +219,7 @@ class AgentSecurityEventReporter {
207
219
  void work.finally(() => {
208
220
  if (this.delivery === work)
209
221
  this.delivery = undefined;
210
- this.suppressRetry = false;
222
+ this.flushExpired = false;
211
223
  if (this.queue.length === 0)
212
224
  this.retryAttempt = 0;
213
225
  else if (shouldRetry)
@@ -236,6 +248,8 @@ class AgentSecurityEventReporter {
236
248
  this.prune();
237
249
  const retainedScopes = new Set();
238
250
  while (this.queue.length > 0) {
251
+ if (this.flushDeadlinePassed())
252
+ return true;
239
253
  const first = this.queue.find((pending) => !retainedScopes.has(scopeKey(pendingScope(pending))));
240
254
  if (!first)
241
255
  return shouldRetry;
@@ -280,6 +294,10 @@ class AgentSecurityEventReporter {
280
294
  const remove = new Set();
281
295
  let retry = false;
282
296
  for (const event of batch) {
297
+ if (this.flushDeadlinePassed()) {
298
+ retry = true;
299
+ break;
300
+ }
283
301
  const isolated = await this.deliver([event], scope);
284
302
  for (const eventId of isolated.remove)
285
303
  remove.add(eventId);
package/dist/subject.d.ts CHANGED
@@ -23,6 +23,14 @@ import type { OryAgentClient } from "./client.js";
23
23
  * provisioning all use `User`, so it is the out-of-the-box default.
24
24
  */
25
25
  export declare const DEFAULT_USER_SUBJECT_NAMESPACE = "User";
26
+ /**
27
+ * Owner object for a preprovisioned runtime (a Talos, SPIFFE or mTLS agent
28
+ * deployment) that has no local user principal. The client never knows that
29
+ * owner's identity; the broker derives it from the authenticated runtime, so
30
+ * this object is only ever sent as `{ kind: "owner" }`. It is not a UUID, so it
31
+ * cannot collide with a real identity ID.
32
+ */
33
+ export declare const RUNTIME_OWNER_SUBJECT = "runtime-owner";
26
34
  /** Namespace the agent principal is addressed under (delegation subjects). */
27
35
  export declare const AGENT_NAMESPACE = "Agent";
28
36
  /** Namespace a sub-agent principal is addressed under. */
@@ -53,7 +61,8 @@ export declare function runWithUserSubject<T>(subject: string | undefined, fn: (
53
61
  * Resolve the user subject for permission checks. Prefers a per-call
54
62
  * override installed via {@link runWithUserSubject}, then the user
55
63
  * login's `userPrincipal.subject`, falls back to `ORY_USER_SUBJECT_ID`, then the
56
- * legacy `ORY_AGENT_SUBJECT_ID`, then the caller-supplied `fallback`
64
+ * legacy `ORY_AGENT_SUBJECT_ID`, then the deployment owner of a preprovisioned
65
+ * runtime ({@link RUNTIME_OWNER_SUBJECT}), then the caller-supplied `fallback`
57
66
  * (typically `session:<id>`).
58
67
  *
59
68
  * Always returns a SubjectSet. A resolved principal is addressed under the
package/dist/subject.js CHANGED
@@ -18,7 +18,7 @@
18
18
  * toggles between SubjectSet and a legacy direct SubjectID.
19
19
  */
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.AGENT_SUBJECT_ID_ENV_VAR = exports.SUBJECT_AXIS_SEPARATOR = exports.SESSION_NAMESPACE = exports.SUBAGENT_NAMESPACE = exports.AGENT_NAMESPACE = exports.DEFAULT_USER_SUBJECT_NAMESPACE = void 0;
21
+ exports.AGENT_SUBJECT_ID_ENV_VAR = exports.SUBJECT_AXIS_SEPARATOR = exports.SESSION_NAMESPACE = exports.SUBAGENT_NAMESPACE = exports.AGENT_NAMESPACE = exports.RUNTIME_OWNER_SUBJECT = exports.DEFAULT_USER_SUBJECT_NAMESPACE = void 0;
22
22
  exports.runWithUserSubject = runWithUserSubject;
23
23
  exports.resolveUserSubject = resolveUserSubject;
24
24
  exports.subjectLabel = subjectLabel;
@@ -35,6 +35,14 @@ const node_async_hooks_1 = require("node:async_hooks");
35
35
  * provisioning all use `User`, so it is the out-of-the-box default.
36
36
  */
37
37
  exports.DEFAULT_USER_SUBJECT_NAMESPACE = "User";
38
+ /**
39
+ * Owner object for a preprovisioned runtime (a Talos, SPIFFE or mTLS agent
40
+ * deployment) that has no local user principal. The client never knows that
41
+ * owner's identity; the broker derives it from the authenticated runtime, so
42
+ * this object is only ever sent as `{ kind: "owner" }`. It is not a UUID, so it
43
+ * cannot collide with a real identity ID.
44
+ */
45
+ exports.RUNTIME_OWNER_SUBJECT = "runtime-owner";
38
46
  /** Namespace the agent principal is addressed under (delegation subjects). */
39
47
  exports.AGENT_NAMESPACE = "Agent";
40
48
  /** Namespace a sub-agent principal is addressed under. */
@@ -72,7 +80,8 @@ function subjectSet(namespace, object) {
72
80
  * Resolve the user subject for permission checks. Prefers a per-call
73
81
  * override installed via {@link runWithUserSubject}, then the user
74
82
  * login's `userPrincipal.subject`, falls back to `ORY_USER_SUBJECT_ID`, then the
75
- * legacy `ORY_AGENT_SUBJECT_ID`, then the caller-supplied `fallback`
83
+ * legacy `ORY_AGENT_SUBJECT_ID`, then the deployment owner of a preprovisioned
84
+ * runtime ({@link RUNTIME_OWNER_SUBJECT}), then the caller-supplied `fallback`
76
85
  * (typically `session:<id>`).
77
86
  *
78
87
  * Always returns a SubjectSet. A resolved principal is addressed under the
@@ -94,6 +103,11 @@ function resolveUserSubject(client, fallback) {
94
103
  || exports.DEFAULT_USER_SUBJECT_NAMESPACE;
95
104
  if (principal)
96
105
  return subjectSet(ns, principal);
106
+ // A preprovisioned runtime acts for its deployment's owner, whom only the
107
+ // broker knows. Without this, a headless deployment resolves no subject and
108
+ // every native tool is denied in enforce mode (#338).
109
+ if (client.runtimeContext)
110
+ return subjectSet(ns, exports.RUNTIME_OWNER_SUBJECT);
97
111
  // No principal — use the caller's fallback. A `session:<id>` fallback is
98
112
  // addressed under the Session namespace so it is a well-formed SubjectSet that
99
113
  // could match a Session-scoped grant, rather than being wrapped under `User`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/argus",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
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",