@ory/argus 1.4.1 → 1.4.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.
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "repo": "ory-agent-plugins",
3
- "commit": "5901fea77c25766073ad2f41fab14875d20f4652",
4
- "commitShort": "5901fea",
3
+ "commit": "42429b6ed5f9b4201bb43bedb7977e19b53783ac",
4
+ "commitShort": "42429b6",
5
5
  "branch": "main",
6
- "commitDate": "2026-10-01T13:04:44-07:00",
6
+ "commitDate": "2026-10-01T17:08:44-07:00",
7
7
  "dirty": false,
8
- "builtAt": "2026-10-01T20:08:53.706Z"
8
+ "builtAt": "2026-10-02T00:12:26.601Z"
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.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",