@ory/argus 1.2.4 → 1.3.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 +25 -0
- package/assets/skills/ory-build-agent/SKILL.md +1 -1
- package/assets/skills/ory-deploy-agent/SKILL.md +315 -0
- package/assets/skills/ory-deploy-openai-e2b/SKILL.md +289 -0
- package/assets/skills/ory-e2b-sandbox/SKILL.md +10 -6
- package/assets/skills/ory-temporal-worker/SKILL.md +4 -4
- package/dist/adapters.d.ts +11 -3
- package/dist/adapters.js +55 -13
- package/dist/agent-auth.d.ts +18 -7
- package/dist/agent-auth.js +120 -10
- package/dist/auth-store.d.ts +3 -3
- package/dist/auth-store.js +56 -12
- package/dist/auth.d.ts +1 -1
- package/dist/auth.js +1 -1
- package/dist/build-info.json +4 -4
- package/dist/cli.js +5 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.js +24 -7
- package/dist/config.d.ts +23 -3
- package/dist/config.js +202 -18
- package/dist/contract-suite.d.ts +0 -8
- package/dist/contract-suite.js +0 -1
- package/dist/event-reporter.d.ts +1 -0
- package/dist/event-reporter.js +19 -2
- package/dist/hook-timeout.d.ts +2 -2
- package/dist/hook-timeout.js +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +13 -6
- package/dist/lifecycle.js +2 -1
- package/dist/local/configs.d.ts +2 -4
- package/dist/local/configs.js +2 -4
- package/dist/local/manager.js +4 -4
- package/dist/local/seed.d.ts +4 -4
- package/dist/local/seed.js +8 -10
- package/dist/runtime-credential.d.ts +35 -0
- package/dist/runtime-credential.js +252 -1
- package/dist/runtime.d.ts +19 -12
- package/dist/runtime.js +69 -21
- package/dist/setup.js +3 -3
- package/dist/skills.d.ts +3 -5
- package/dist/skills.js +14 -5
- package/dist/subject.d.ts +2 -3
- package/dist/subject.js +10 -11
- package/dist/testing.d.ts +17 -0
- package/dist/testing.js +53 -0
- package/dist/tool-catalog.d.ts +12 -12
- package/dist/tool-catalog.js +206 -50
- package/dist/user-login.js +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -81,6 +81,7 @@ class OryAgentClient {
|
|
|
81
81
|
logger;
|
|
82
82
|
eventReporter;
|
|
83
83
|
_runtimeCredential;
|
|
84
|
+
_runtimeContext;
|
|
84
85
|
_runtimeCredentialsBySession = new Map();
|
|
85
86
|
_runtimeCredentialsByPrincipalKey = new Map();
|
|
86
87
|
sessionCache = new Map();
|
|
@@ -341,6 +342,17 @@ class OryAgentClient {
|
|
|
341
342
|
get hasRuntimeCredential() {
|
|
342
343
|
return this.runtimeCredential !== undefined;
|
|
343
344
|
}
|
|
345
|
+
/** Process-local deployment context. It is never persisted or copied to the environment. */
|
|
346
|
+
get runtimeContext() {
|
|
347
|
+
return this._runtimeContext ? { ...this._runtimeContext } : undefined;
|
|
348
|
+
}
|
|
349
|
+
get runtimeRootDelegation() {
|
|
350
|
+
return this._runtimeContext?.rootDelegation;
|
|
351
|
+
}
|
|
352
|
+
setRuntimeContext(context) {
|
|
353
|
+
this._runtimeContext = { ...context };
|
|
354
|
+
this.setAgentPrincipal({ subject: context.subjectId });
|
|
355
|
+
}
|
|
344
356
|
setRuntimeCredential(credential) {
|
|
345
357
|
const sessionId = this.sessionId?.trim();
|
|
346
358
|
if (sessionId) {
|
|
@@ -441,7 +453,10 @@ class OryAgentClient {
|
|
|
441
453
|
emitActivity: false,
|
|
442
454
|
signal: controller.signal,
|
|
443
455
|
}));
|
|
444
|
-
if (identity.
|
|
456
|
+
if (identity.runtimeCredential) {
|
|
457
|
+
this.rememberRuntimeCredential(identity.runtimeCredential);
|
|
458
|
+
}
|
|
459
|
+
else if (identity.subject && identity.token) {
|
|
445
460
|
this._eventPrincipalsByKey.set((0, event_reporter_js_1.eventPrincipalKey)(identity.subject), {
|
|
446
461
|
subject: identity.subject,
|
|
447
462
|
token: identity.token,
|
|
@@ -814,28 +829,30 @@ class OryAgentClient {
|
|
|
814
829
|
};
|
|
815
830
|
const activity = this.logger.startActivity("delegation.record", { delegationKind: input.kind, ...options?.activityAttributes }, sessionId);
|
|
816
831
|
const url = `${this.agentSecurityUrl.replace(/\/+$/, "")}/agent-security/v1/delegations:record`;
|
|
817
|
-
//
|
|
818
|
-
//
|
|
832
|
+
// Interactive lineage uses separate user and runtime proofs. A validated
|
|
833
|
+
// preprovisioned root has no user bearer; its acting runtime authenticates
|
|
834
|
+
// directly in Authorization.
|
|
819
835
|
const userToken = this.userPrincipalForSession(sessionId).token;
|
|
820
836
|
const runtimeCredential = options?.runtimeCredential ?? (options?.agentToken
|
|
821
837
|
? (0, runtime_credential_js_1.talosRuntimeCredential)(options.agentToken)
|
|
822
838
|
: this.runtimeCredential);
|
|
823
839
|
try {
|
|
824
|
-
if (!
|
|
840
|
+
if (!runtimeCredential || (!userToken && !options?.preprovisionedRoot)) {
|
|
825
841
|
throw httpError(403, "User token and runtime credential are required");
|
|
826
842
|
}
|
|
843
|
+
const runtimeOnly = options?.preprovisionedRoot === true;
|
|
827
844
|
const res = await (0, runtime_credential_js_1.authenticatedRuntimeRequest)(runtimeCredential, {
|
|
828
845
|
url,
|
|
829
846
|
init: {
|
|
830
847
|
method: "POST",
|
|
831
848
|
headers: {
|
|
832
849
|
"content-type": "application/json",
|
|
833
|
-
authorization: `Bearer ${userToken}
|
|
850
|
+
...(!runtimeOnly && userToken ? { authorization: `Bearer ${userToken}` } : {}),
|
|
834
851
|
},
|
|
835
852
|
body: JSON.stringify(body),
|
|
836
853
|
signal: options?.signal ?? AbortSignal.timeout(30_000),
|
|
837
854
|
},
|
|
838
|
-
authorizationHeader: "x-ory-agent-authorization",
|
|
855
|
+
authorizationHeader: runtimeOnly ? "authorization" : "x-ory-agent-authorization",
|
|
839
856
|
fetch: options?.fetchImpl,
|
|
840
857
|
});
|
|
841
858
|
if (!res.ok) {
|
|
@@ -1104,7 +1121,7 @@ function rehydrateUserPrincipal(client) {
|
|
|
1104
1121
|
// without persisting it — so without this branch `ORY_USER_OAUTH2_TOKEN`
|
|
1105
1122
|
// authenticated the session and then silently stopped
|
|
1106
1123
|
// applying on every subsequent event. On a subprocess harness that left the
|
|
1107
|
-
// tool-call client with no user credential for delegation or
|
|
1124
|
+
// tool-call client with no user credential for delegation or Talos enrollment.
|
|
1108
1125
|
const env = (0, auth_store_js_1.readEnvUserCredential)();
|
|
1109
1126
|
const tokens = (0, auth_store_js_1.loadTokens)();
|
|
1110
1127
|
if (env.token) {
|
package/dist/config.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface OryOAuth2Tokens {
|
|
|
12
12
|
expiresAt: number;
|
|
13
13
|
/** Subject (sub claim) the token was issued for. */
|
|
14
14
|
subject?: string;
|
|
15
|
-
/** Client ID the token was issued under.
|
|
15
|
+
/** Client ID the token was issued under. Persisted to bind refreshes to that client. */
|
|
16
16
|
clientId?: string;
|
|
17
17
|
/** @deprecated Accepted in memory for older callers; never persisted. */
|
|
18
18
|
idToken?: string;
|
|
@@ -104,6 +104,14 @@ 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
|
+
/** Opaque, administrator-provisioned root delegation node for headless agents. */
|
|
108
|
+
export declare const AGENT_ROOT_DELEGATION_ENV_VAR = "ORY_AGENT_ROOT_DELEGATION";
|
|
109
|
+
/** Validate an opaque broker node without interpreting its server-owned format. */
|
|
110
|
+
export declare function validateAgentRootDelegation(raw: string | undefined): {
|
|
111
|
+
value?: string;
|
|
112
|
+
warning?: string;
|
|
113
|
+
};
|
|
114
|
+
export declare function readAgentRootDelegation(env?: NodeJS.ProcessEnv, warnings?: string[]): string | undefined;
|
|
107
115
|
/**
|
|
108
116
|
* Credentials for the AI agent principal (machine identity). Every harness
|
|
109
117
|
* session owns a distinct DCR client, and every typed sub-agent in that session
|
|
@@ -183,7 +191,7 @@ export interface OryPluginConfig {
|
|
|
183
191
|
projectUrl?: string;
|
|
184
192
|
/**
|
|
185
193
|
* Canonical Ory Agent Security broker URL. Broker calls resolve this value
|
|
186
|
-
* independently from {@link projectUrl}, which remains the
|
|
194
|
+
* independently from {@link projectUrl}, which remains the project and user-login origin.
|
|
187
195
|
* Older configs fall back to `projectUrl` until this key is persisted.
|
|
188
196
|
*/
|
|
189
197
|
agentSecurityUrl?: string;
|
|
@@ -250,7 +258,7 @@ export interface OryPluginConfig {
|
|
|
250
258
|
}
|
|
251
259
|
/**
|
|
252
260
|
* Single OS-agnostic data directory for *all* Ory agent plugin state:
|
|
253
|
-
* the shared config file, persisted DCR credentials, and any harness
|
|
261
|
+
* the shared config file, persisted legacy DCR credentials, and any harness
|
|
254
262
|
* asset directories. One place per platform — never split across
|
|
255
263
|
* `~/.ory/` and `~/.config/`.
|
|
256
264
|
*
|
|
@@ -317,6 +325,18 @@ export declare function saveConfig(update: Partial<{
|
|
|
317
325
|
* atomic via write-temp + rename.
|
|
318
326
|
*/
|
|
319
327
|
export declare function mutateConfig(mutator: (current: OryPluginConfig) => OryPluginConfig | undefined): void;
|
|
328
|
+
export interface AsyncConfigMutation<T> {
|
|
329
|
+
/** New config to persist while the lock is still held. Omit for a read-only result. */
|
|
330
|
+
config?: OryPluginConfig;
|
|
331
|
+
value: T;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Async read-modify-write transaction using the same config.json.lock as
|
|
335
|
+
* mutateConfig. The lock remains held while the callback awaits, which lets
|
|
336
|
+
* token refresh serialize its read/check/network/write sequence across
|
|
337
|
+
* JavaScript and Go processes without blocking the JavaScript event loop.
|
|
338
|
+
*/
|
|
339
|
+
export declare function mutateConfigAsync<T>(mutator: (current: OryPluginConfig) => Promise<AsyncConfigMutation<T>>): Promise<T>;
|
|
320
340
|
/**
|
|
321
341
|
* True when `value` looks like an Ory project **id** (a UUID) rather than a
|
|
322
342
|
* project **slug**. Project slugs are human-readable tokens (e.g.
|
package/dist/config.js
CHANGED
|
@@ -33,7 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.LOCAL_AGENT_SECURITY_URL = exports.DEFAULT_AGENT_SECURITY_URL = exports.DEFAULT_OAUTH2_CLIENT_ID = exports.SESSIONLESS_KEY = exports.UNKNOWN_HARNESS_KEY = exports.DELEGATION_NODE_HISTORY = void 0;
|
|
36
|
+
exports.LOCAL_AGENT_SECURITY_URL = exports.DEFAULT_AGENT_SECURITY_URL = exports.DEFAULT_OAUTH2_CLIENT_ID = exports.SESSIONLESS_KEY = exports.UNKNOWN_HARNESS_KEY = exports.AGENT_ROOT_DELEGATION_ENV_VAR = exports.DELEGATION_NODE_HISTORY = void 0;
|
|
37
|
+
exports.validateAgentRootDelegation = validateAgentRootDelegation;
|
|
38
|
+
exports.readAgentRootDelegation = readAgentRootDelegation;
|
|
37
39
|
exports.getDataDir = getDataDir;
|
|
38
40
|
exports.getHarnessDataDir = getHarnessDataDir;
|
|
39
41
|
exports.getConfigPath = getConfigPath;
|
|
@@ -43,6 +45,7 @@ exports.registerInstalledHarness = registerInstalledHarness;
|
|
|
43
45
|
exports.unregisterInstalledHarness = unregisterInstalledHarness;
|
|
44
46
|
exports.saveConfig = saveConfig;
|
|
45
47
|
exports.mutateConfig = mutateConfig;
|
|
48
|
+
exports.mutateConfigAsync = mutateConfigAsync;
|
|
46
49
|
exports.looksLikeProjectId = looksLikeProjectId;
|
|
47
50
|
exports.isSecurityConnected = isSecurityConnected;
|
|
48
51
|
exports.describeSecurityGap = describeSecurityGap;
|
|
@@ -51,6 +54,7 @@ exports.configPromptMessage = configPromptMessage;
|
|
|
51
54
|
const fs = __importStar(require("node:fs"));
|
|
52
55
|
const os = __importStar(require("node:os"));
|
|
53
56
|
const path = __importStar(require("node:path"));
|
|
57
|
+
const node_crypto_1 = require("node:crypto");
|
|
54
58
|
const cli_invocation_js_1 = require("./cli-invocation.js");
|
|
55
59
|
/**
|
|
56
60
|
* How many sessions' delegation node ids to keep per credential. Only the
|
|
@@ -58,6 +62,29 @@ const cli_invocation_js_1 = require("./cli-invocation.js");
|
|
|
58
62
|
* bounded so a long-lived install's config doesn't grow without limit.
|
|
59
63
|
*/
|
|
60
64
|
exports.DELEGATION_NODE_HISTORY = 10;
|
|
65
|
+
/** Opaque, administrator-provisioned root delegation node for headless agents. */
|
|
66
|
+
exports.AGENT_ROOT_DELEGATION_ENV_VAR = "ORY_AGENT_ROOT_DELEGATION";
|
|
67
|
+
/** Validate an opaque broker node without interpreting its server-owned format. */
|
|
68
|
+
function validateAgentRootDelegation(raw) {
|
|
69
|
+
if (raw === undefined)
|
|
70
|
+
return {};
|
|
71
|
+
const value = raw.trim();
|
|
72
|
+
if (!value) {
|
|
73
|
+
return { warning: `${exports.AGENT_ROOT_DELEGATION_ENV_VAR} is set but empty; ignoring it.` };
|
|
74
|
+
}
|
|
75
|
+
if (Buffer.byteLength(value, "utf8") > 255 || /[\0\r\n]/.test(raw)) {
|
|
76
|
+
return {
|
|
77
|
+
warning: `${exports.AGENT_ROOT_DELEGATION_ENV_VAR} must be at most 255 UTF-8 bytes and contain no NUL or newline characters; ignoring it.`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { value };
|
|
81
|
+
}
|
|
82
|
+
function readAgentRootDelegation(env = process.env, warnings) {
|
|
83
|
+
const { value, warning } = validateAgentRootDelegation(env[exports.AGENT_ROOT_DELEGATION_ENV_VAR]);
|
|
84
|
+
if (warning)
|
|
85
|
+
warnings?.push(warning);
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
61
88
|
/**
|
|
62
89
|
* Key used when a credential record carries no harness of its own — only
|
|
63
90
|
* reachable by migrating a config written before credentials were keyed by
|
|
@@ -78,7 +105,7 @@ exports.UNKNOWN_HARNESS_KEY = "unknown";
|
|
|
78
105
|
exports.SESSIONLESS_KEY = "sessionless";
|
|
79
106
|
/**
|
|
80
107
|
* Single OS-agnostic data directory for *all* Ory agent plugin state:
|
|
81
|
-
* the shared config file, persisted DCR credentials, and any harness
|
|
108
|
+
* the shared config file, persisted legacy DCR credentials, and any harness
|
|
82
109
|
* asset directories. One place per platform — never split across
|
|
83
110
|
* `~/.ory/` and `~/.config/`.
|
|
84
111
|
*
|
|
@@ -118,8 +145,11 @@ function configFile() {
|
|
|
118
145
|
function configLockFile() {
|
|
119
146
|
return configFile() + ".lock";
|
|
120
147
|
}
|
|
121
|
-
/**
|
|
122
|
-
const
|
|
148
|
+
/** Age fallback for malformed or definitely cross-host lock owners. */
|
|
149
|
+
const LOCK_STALE_FALLBACK_MS = 5 * 60_000;
|
|
150
|
+
/** Lease used by nonce-bearing locks; async transactions refresh it. */
|
|
151
|
+
const LOCK_LEASE_MS = 5_000;
|
|
152
|
+
const LOCK_HEARTBEAT_MS = 1_000;
|
|
123
153
|
/** Polling interval while waiting to acquire the lock. */
|
|
124
154
|
const LOCK_POLL_MS = 25;
|
|
125
155
|
/** Maximum total time to wait for the lock before giving up. */
|
|
@@ -507,6 +537,7 @@ function parseOAuth2(value) {
|
|
|
507
537
|
refreshToken: typeof v.refreshToken === "string" ? v.refreshToken : undefined,
|
|
508
538
|
expiresAt: v.expiresAt,
|
|
509
539
|
subject: typeof v.subject === "string" ? v.subject : undefined,
|
|
540
|
+
clientId: typeof v.clientId === "string" ? v.clientId : undefined,
|
|
510
541
|
};
|
|
511
542
|
}
|
|
512
543
|
/**
|
|
@@ -540,6 +571,25 @@ function mutateConfig(mutator) {
|
|
|
540
571
|
releaseLock(lock);
|
|
541
572
|
}
|
|
542
573
|
}
|
|
574
|
+
/**
|
|
575
|
+
* Async read-modify-write transaction using the same config.json.lock as
|
|
576
|
+
* mutateConfig. The lock remains held while the callback awaits, which lets
|
|
577
|
+
* token refresh serialize its read/check/network/write sequence across
|
|
578
|
+
* JavaScript and Go processes without blocking the JavaScript event loop.
|
|
579
|
+
*/
|
|
580
|
+
async function mutateConfigAsync(mutator) {
|
|
581
|
+
ensureConfigDir();
|
|
582
|
+
const lock = await acquireLockAsync();
|
|
583
|
+
try {
|
|
584
|
+
const mutation = await mutator(loadConfig());
|
|
585
|
+
if (mutation.config)
|
|
586
|
+
writeConfigAtomic(mutation.config);
|
|
587
|
+
return mutation.value;
|
|
588
|
+
}
|
|
589
|
+
finally {
|
|
590
|
+
releaseLock(lock);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
543
593
|
function mergeConfig(existing, update) {
|
|
544
594
|
const result = { ...existing };
|
|
545
595
|
for (const key of Object.keys(update)) {
|
|
@@ -568,23 +618,16 @@ function acquireLock() {
|
|
|
568
618
|
for (;;) {
|
|
569
619
|
try {
|
|
570
620
|
const fd = fs.openSync(lockPath, "wx", 0o600);
|
|
571
|
-
|
|
572
|
-
|
|
621
|
+
const nonce = (0, node_crypto_1.randomBytes)(16).toString("hex");
|
|
622
|
+
fs.writeSync(fd, configLockOwner(nonce));
|
|
623
|
+
return { fd, nonce };
|
|
573
624
|
}
|
|
574
625
|
catch (err) {
|
|
575
626
|
if (err.code !== "EEXIST")
|
|
576
627
|
throw err;
|
|
577
|
-
// Lock exists — check if it's stale.
|
|
578
628
|
try {
|
|
579
|
-
const
|
|
580
|
-
if (
|
|
581
|
-
// Best-effort break; if another process beats us, we'll retry.
|
|
582
|
-
try {
|
|
583
|
-
fs.unlinkSync(lockPath);
|
|
584
|
-
}
|
|
585
|
-
catch {
|
|
586
|
-
/* ignore */
|
|
587
|
-
}
|
|
629
|
+
const observed = inspectConfigLock(lockPath);
|
|
630
|
+
if (observed.stale && removeObservedLock(lockPath, observed)) {
|
|
588
631
|
continue;
|
|
589
632
|
}
|
|
590
633
|
}
|
|
@@ -606,6 +649,142 @@ function acquireLock() {
|
|
|
606
649
|
}
|
|
607
650
|
}
|
|
608
651
|
}
|
|
652
|
+
async function acquireLockAsync() {
|
|
653
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
654
|
+
const lockPath = configLockFile();
|
|
655
|
+
for (;;) {
|
|
656
|
+
try {
|
|
657
|
+
const fd = fs.openSync(lockPath, "wx", 0o600);
|
|
658
|
+
const nonce = (0, node_crypto_1.randomBytes)(16).toString("hex");
|
|
659
|
+
fs.writeSync(fd, configLockOwner(nonce));
|
|
660
|
+
const lock = { fd, nonce };
|
|
661
|
+
lock.heartbeat = setInterval(() => heartbeatLock(lock), LOCK_HEARTBEAT_MS);
|
|
662
|
+
lock.heartbeat.unref?.();
|
|
663
|
+
return lock;
|
|
664
|
+
}
|
|
665
|
+
catch (err) {
|
|
666
|
+
if (err.code !== "EEXIST")
|
|
667
|
+
throw err;
|
|
668
|
+
try {
|
|
669
|
+
const observed = inspectConfigLock(lockPath);
|
|
670
|
+
if (observed.stale && removeObservedLock(lockPath, observed)) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (Date.now() > deadline) {
|
|
678
|
+
throw new Error(`Timed out waiting for config lock at ${lockPath}. ` +
|
|
679
|
+
"Another process may be holding it; remove the file if you are sure no other process is running.");
|
|
680
|
+
}
|
|
681
|
+
await new Promise((resolve) => setTimeout(resolve, LOCK_POLL_MS));
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function configLockOwner(nonce, now = Date.now()) {
|
|
686
|
+
return `${process.pid} ${os.hostname()} ${nonce} ${now + LOCK_LEASE_MS}`;
|
|
687
|
+
}
|
|
688
|
+
function inspectConfigLock(lockPath) {
|
|
689
|
+
const stat = fs.statSync(lockPath);
|
|
690
|
+
let raw = "";
|
|
691
|
+
try {
|
|
692
|
+
raw = fs.readFileSync(lockPath, "utf8").trim();
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
return {
|
|
696
|
+
stale: Date.now() - stat.mtimeMs > LOCK_STALE_FALLBACK_MS,
|
|
697
|
+
raw,
|
|
698
|
+
dev: stat.dev,
|
|
699
|
+
ino: stat.ino,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const [pidText, hostname, nonce, leaseText] = raw.split(/\s+/, 4);
|
|
703
|
+
const pid = Number.parseInt(pidText ?? "", 10);
|
|
704
|
+
const leaseExpiresAt = Number.parseInt(leaseText ?? "", 10);
|
|
705
|
+
const hasLease = Boolean(nonce) && Number.isFinite(leaseExpiresAt);
|
|
706
|
+
if (hasLease && Date.now() > leaseExpiresAt) {
|
|
707
|
+
return { stale: true, raw, dev: stat.dev, ino: stat.ino };
|
|
708
|
+
}
|
|
709
|
+
if (Number.isInteger(pid) && pid > 0 && (!hostname || hostname === os.hostname())) {
|
|
710
|
+
try {
|
|
711
|
+
process.kill(pid, 0);
|
|
712
|
+
return {
|
|
713
|
+
stale: !hasLease && Date.now() - stat.mtimeMs > LOCK_STALE_FALLBACK_MS,
|
|
714
|
+
raw,
|
|
715
|
+
dev: stat.dev,
|
|
716
|
+
ino: stat.ino,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
catch (err) {
|
|
720
|
+
if (err.code === "EPERM") {
|
|
721
|
+
return {
|
|
722
|
+
stale: !hasLease && Date.now() - stat.mtimeMs > LOCK_STALE_FALLBACK_MS,
|
|
723
|
+
raw,
|
|
724
|
+
dev: stat.dev,
|
|
725
|
+
ino: stat.ino,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
if (err.code === "ESRCH") {
|
|
729
|
+
return { stale: true, raw, dev: stat.dev, ino: stat.ino };
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return {
|
|
734
|
+
stale: Date.now() - stat.mtimeMs > LOCK_STALE_FALLBACK_MS,
|
|
735
|
+
raw,
|
|
736
|
+
dev: stat.dev,
|
|
737
|
+
ino: stat.ino,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
function removeObservedLock(lockPath, observed) {
|
|
741
|
+
try {
|
|
742
|
+
const stat = fs.statSync(lockPath);
|
|
743
|
+
if (stat.dev !== observed.dev || stat.ino !== observed.ino)
|
|
744
|
+
return false;
|
|
745
|
+
if (fs.readFileSync(lockPath, "utf8").trim() !== observed.raw)
|
|
746
|
+
return false;
|
|
747
|
+
fs.unlinkSync(lockPath);
|
|
748
|
+
return true;
|
|
749
|
+
}
|
|
750
|
+
catch {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
function ownsLock(lock) {
|
|
755
|
+
if (lock.fd === null || !lock.nonce)
|
|
756
|
+
return false;
|
|
757
|
+
try {
|
|
758
|
+
const held = fs.fstatSync(lock.fd);
|
|
759
|
+
const current = fs.statSync(configLockFile());
|
|
760
|
+
if (held.dev !== current.dev || held.ino !== current.ino)
|
|
761
|
+
return false;
|
|
762
|
+
const [, , nonce] = fs.readFileSync(configLockFile(), "utf8").trim().split(/\s+/, 4);
|
|
763
|
+
return nonce === lock.nonce;
|
|
764
|
+
}
|
|
765
|
+
catch {
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function heartbeatLock(lock) {
|
|
770
|
+
if (lock.fd === null || !lock.nonce || !ownsLock(lock)) {
|
|
771
|
+
if (lock.heartbeat)
|
|
772
|
+
clearInterval(lock.heartbeat);
|
|
773
|
+
lock.heartbeat = undefined;
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const record = configLockOwner(lock.nonce);
|
|
777
|
+
try {
|
|
778
|
+
fs.ftruncateSync(lock.fd, 0);
|
|
779
|
+
fs.writeSync(lock.fd, record, 0, "utf8");
|
|
780
|
+
fs.fsyncSync(lock.fd);
|
|
781
|
+
}
|
|
782
|
+
catch {
|
|
783
|
+
if (lock.heartbeat)
|
|
784
|
+
clearInterval(lock.heartbeat);
|
|
785
|
+
lock.heartbeat = undefined;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
609
788
|
/**
|
|
610
789
|
* Lazily initialized buffer for `Atomics.wait`-based synchronous sleep.
|
|
611
790
|
* `undefined` = not yet probed; `null` = unavailable on this runtime.
|
|
@@ -637,14 +816,18 @@ function sleepSync(ms) {
|
|
|
637
816
|
function releaseLock(lock) {
|
|
638
817
|
if (lock.fd === null)
|
|
639
818
|
return; // lock-less fallback: nothing to release
|
|
819
|
+
if (lock.heartbeat)
|
|
820
|
+
clearInterval(lock.heartbeat);
|
|
821
|
+
lock.heartbeat = undefined;
|
|
640
822
|
try {
|
|
641
|
-
|
|
823
|
+
if (ownsLock(lock))
|
|
824
|
+
fs.unlinkSync(configLockFile());
|
|
642
825
|
}
|
|
643
826
|
catch {
|
|
644
827
|
/* ignore */
|
|
645
828
|
}
|
|
646
829
|
try {
|
|
647
|
-
fs.
|
|
830
|
+
fs.closeSync(lock.fd);
|
|
648
831
|
}
|
|
649
832
|
catch {
|
|
650
833
|
/* ignore */
|
|
@@ -672,6 +855,7 @@ function compactPersistedConfig(config) {
|
|
|
672
855
|
...(userTokens.refreshToken ? { refreshToken: userTokens.refreshToken } : {}),
|
|
673
856
|
expiresAt: userTokens.expiresAt,
|
|
674
857
|
...(userTokens.subject ? { subject: userTokens.subject } : {}),
|
|
858
|
+
...(userTokens.clientId ? { clientId: userTokens.clientId } : {}),
|
|
675
859
|
},
|
|
676
860
|
}
|
|
677
861
|
: undefined;
|
package/dist/contract-suite.d.ts
CHANGED
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
* runHarnessContractSuite({
|
|
18
18
|
* harness: "claude-code",
|
|
19
19
|
* tool: "Bash",
|
|
20
|
-
* sessionCanBlock: true,
|
|
21
20
|
* sessionStart: async ({ client, gates }) => {
|
|
22
21
|
* const out = await handleHookEvent(sessionInput(), client, gates);
|
|
23
22
|
* return { blocked: out.decision === "block", reason: out.reason };
|
|
@@ -60,13 +59,6 @@ export interface HarnessContractAdapter {
|
|
|
60
59
|
harness: string;
|
|
61
60
|
/** A real, non-interactive tool from this harness's catalog. */
|
|
62
61
|
tool: string;
|
|
63
|
-
/**
|
|
64
|
-
* @deprecated Ignored. Session start is now uniformly non-blocking on every
|
|
65
|
-
* harness (authentication never blocks; enforcement is governed by
|
|
66
|
-
* `permissionMode` at tool-call time). Kept optional so existing adapters
|
|
67
|
-
* that still pass it type-check.
|
|
68
|
-
*/
|
|
69
|
-
sessionCanBlock?: boolean;
|
|
70
62
|
/** Whether the tool gate can hard-block. Defaults to true. */
|
|
71
63
|
toolCanBlock?: boolean;
|
|
72
64
|
/** Drive the session-start phase through the plugin's real entry point. */
|
package/dist/contract-suite.js
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
* runHarnessContractSuite({
|
|
19
19
|
* harness: "claude-code",
|
|
20
20
|
* tool: "Bash",
|
|
21
|
-
* sessionCanBlock: true,
|
|
22
21
|
* sessionStart: async ({ client, gates }) => {
|
|
23
22
|
* const out = await handleHookEvent(sessionInput(), client, gates);
|
|
24
23
|
* return { blocked: out.decision === "block", reason: out.reason };
|
package/dist/event-reporter.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export declare class AgentSecurityEventReporter {
|
|
|
49
49
|
private readonly retryBaseMs;
|
|
50
50
|
private readonly retryMaxMs;
|
|
51
51
|
private readonly random;
|
|
52
|
+
private readonly reportedAuthFailures;
|
|
52
53
|
constructor(options: EventReporterOptions);
|
|
53
54
|
get pending(): number;
|
|
54
55
|
get pendingScopes(): ReadonlyArray<PendingEventScope>;
|
package/dist/event-reporter.js
CHANGED
|
@@ -105,6 +105,7 @@ class AgentSecurityEventReporter {
|
|
|
105
105
|
retryBaseMs;
|
|
106
106
|
retryMaxMs;
|
|
107
107
|
random;
|
|
108
|
+
reportedAuthFailures = new Set();
|
|
108
109
|
constructor(options) {
|
|
109
110
|
this.options = options;
|
|
110
111
|
this.fetcher = options.fetch ?? globalThis.fetch;
|
|
@@ -328,14 +329,27 @@ class AgentSecurityEventReporter {
|
|
|
328
329
|
this.diagnostic("events.batch_rejected", new Error(`HTTP ${response.status}`));
|
|
329
330
|
return "reject";
|
|
330
331
|
}
|
|
331
|
-
if (response.status === 401) {
|
|
332
|
+
if (response.status === 401 || response.status === 403) {
|
|
332
333
|
try {
|
|
333
|
-
if (token)
|
|
334
|
+
if (response.status === 401 && token) {
|
|
334
335
|
this.options.onUnauthorized?.(scope.sessionId, scope.principalKey, token);
|
|
336
|
+
}
|
|
335
337
|
}
|
|
336
338
|
catch (error) {
|
|
337
339
|
this.diagnostic("events.credential_invalidation_failed", error);
|
|
338
340
|
}
|
|
341
|
+
const failureKey = `${scopeKey(scope)}:${response.status}`;
|
|
342
|
+
if (!this.reportedAuthFailures.has(failureKey)) {
|
|
343
|
+
this.reportedAuthFailures.add(failureKey);
|
|
344
|
+
this.options.logger?.activity("events.record", "error", {
|
|
345
|
+
sessionId: scope.sessionId,
|
|
346
|
+
attributes: {
|
|
347
|
+
failureType: "authentication",
|
|
348
|
+
reasonCode: response.status === 401 ? "session_inactive" : "forbidden",
|
|
349
|
+
count: batch.length,
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
}
|
|
339
353
|
return "retain";
|
|
340
354
|
}
|
|
341
355
|
if (response.status === 429 || (response.status >= 500 && response.status <= 599)) {
|
|
@@ -359,6 +373,9 @@ class AgentSecurityEventReporter {
|
|
|
359
373
|
this.diagnostic("events.invalid_response", new Error("response event IDs did not match request order"));
|
|
360
374
|
return "retain";
|
|
361
375
|
}
|
|
376
|
+
const acceptedScope = scopeKey(scope);
|
|
377
|
+
this.reportedAuthFailures.delete(`${acceptedScope}:401`);
|
|
378
|
+
this.reportedAuthFailures.delete(`${acceptedScope}:403`);
|
|
362
379
|
return "accepted";
|
|
363
380
|
}
|
|
364
381
|
finally {
|
package/dist/hook-timeout.d.ts
CHANGED
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
* can be given — which is why the login window has to fit inside 120s.
|
|
30
30
|
*/
|
|
31
31
|
/**
|
|
32
|
-
* Wall-clock budget for a session-start hook: the interactive login, plus
|
|
33
|
-
*
|
|
32
|
+
* Wall-clock budget for a session-start hook: the interactive login, plus Talos
|
|
33
|
+
* credential loading or enrollment and the delegation record that follow it.
|
|
34
34
|
*
|
|
35
35
|
* Generous on purpose. It is an upper bound on a hook that is *waiting*, not a
|
|
36
36
|
* latency target — nothing is spinning, and the login gives up on its own well
|
package/dist/hook-timeout.js
CHANGED
|
@@ -35,8 +35,8 @@ exports.sessionStartHookTimeout = sessionStartHookTimeout;
|
|
|
35
35
|
exports.toolHookTimeout = toolHookTimeout;
|
|
36
36
|
const auth_js_1 = require("./auth.js");
|
|
37
37
|
/**
|
|
38
|
-
* Wall-clock budget for a session-start hook: the interactive login, plus
|
|
39
|
-
*
|
|
38
|
+
* Wall-clock budget for a session-start hook: the interactive login, plus Talos
|
|
39
|
+
* credential loading or enrollment and the delegation record that follow it.
|
|
40
40
|
*
|
|
41
41
|
* Generous on purpose. It is an upper bound on a hook that is *waiting*, not a
|
|
42
42
|
* latency target — nothing is spinning, and the login gives up on its own well
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { OryAgentClient, type OryAgentConfig, type PrincipalIdentity, type RecordDelegationInput, type RecordDelegationResult, } from "./client.js";
|
|
2
|
-
export { authenticatedRuntimeRequest, canonicalCredentialIdentity, canonicalProjectUrl, credentialAccount, configureSystemCaTrustForDefaultFetch, createOsTalosCredentialStore, enrollTalosChildRuntimeCredential, enrollTalosRuntimeCredential, resolveRuntimeCredential, runCredentialStoreCommand, talosRuntimeCredential, type CredentialStoreCommandOptions, type CredentialStoreCommandRunner, type ResolveRuntimeCredentialOptions, type EnrollTalosChildRuntimeCredentialOptions, type EnrollTalosRuntimeCredentialOptions, type EnrolledRuntimeCredential, type OsTalosCredentialStoreOptions, type NodeTlsCaProvider, type StoredTalosCredential, type TalosCredentialStore, type TalosCredentialIdentity, type RuntimeAuthenticatedRequest, type RuntimeAuthorizationHeader, type RuntimeCredential, type RuntimeCredentialKind, type RuntimeRequestAuthenticator, } from "./runtime-credential.js";
|
|
1
|
+
export { OryAgentClient, type OryAgentConfig, type PrincipalIdentity, type RecordDelegationInput, type RecordDelegationResult, type RuntimeContext, } from "./client.js";
|
|
2
|
+
export { authenticatedRuntimeRequest, canonicalCredentialIdentity, canonicalProjectUrl, credentialAccount, configureSystemCaTrustForDefaultFetch, createSpiffeFileRequestAuthenticator, createOsTalosCredentialStore, enrollTalosChildRuntimeCredential, enrollTalosRuntimeCredential, resolveRuntimeCredential, runCredentialStoreCommand, talosRuntimeCredential, type CredentialStoreCommandOptions, type CredentialStoreCommandRunner, type ResolveRuntimeCredentialOptions, type SpiffeFileRequestAuthenticatorOptions, type EnrollTalosChildRuntimeCredentialOptions, type EnrollTalosRuntimeCredentialOptions, type EnrolledRuntimeCredential, type OsTalosCredentialStoreOptions, type NodeTlsCaProvider, type StoredTalosCredential, type TalosCredentialStore, type TalosCredentialIdentity, type RuntimeAuthenticatedRequest, type RuntimeAuthorizationHeader, type RuntimeCredential, type RuntimeCredentialKind, type RuntimeRequestAuthenticator, } from "./runtime-credential.js";
|
|
3
3
|
export { DebugLogger, ActiveActivity, redactLogData, formatLogEntry, withQuietStderr, type LogEntry, type LogLevel, type ActivityEntry, type ActivityStatus, type UnifiedLogEntry, type ActivityEnricher, type ActivitySink, } from "./logger.js";
|
|
4
4
|
export { AgentSecurityEventReporter, SERVER_ACTIVITY_EVENTS, projectActivityEntry, type EventReporterOptions, type ServerActivityEvent, } from "./event-reporter.js";
|
|
5
5
|
export { currentSessionContext, withSessionContext, setSessionContext, clearSessionContext, type SessionContext, } from "./context.js";
|
|
6
|
-
export { loadConfig, saveConfig, resolveConfig, isSecurityConnected, describeSecurityGap, type SecurityConnection, mutateConfig, getConfigPath, getDataDir, getHarnessDataDir, listInstalledHarnesses, registerInstalledHarness, unregisterInstalledHarness, UNKNOWN_HARNESS_KEY, SESSIONLESS_KEY, type OryPluginConfig, type OryOAuth2Tokens, type OryUserCredentials, type OryAgentCredentialsBlock, type OryAgentDynamicCredentials, type OryDelegationRecord, type PermissionMode, type PermissionModeCache, } from "./config.js";
|
|
6
|
+
export { loadConfig, saveConfig, resolveConfig, isSecurityConnected, describeSecurityGap, readAgentRootDelegation, validateAgentRootDelegation, AGENT_ROOT_DELEGATION_ENV_VAR, type SecurityConnection, mutateConfig, mutateConfigAsync, getConfigPath, getDataDir, getHarnessDataDir, listInstalledHarnesses, registerInstalledHarness, unregisterInstalledHarness, UNKNOWN_HARNESS_KEY, SESSIONLESS_KEY, type OryPluginConfig, type AsyncConfigMutation, type OryOAuth2Tokens, type OryUserCredentials, type OryAgentCredentialsBlock, type OryAgentDynamicCredentials, type OryDelegationRecord, type PermissionMode, type PermissionModeCache, } from "./config.js";
|
|
7
7
|
export { resolvePermissionMode, warmPermissionModeCache, resetPermissionModeCache, type PermissionModeSource, type ResolvedPermissionMode, type ModeSubject, } from "./permission-mode.js";
|
|
8
8
|
export { pkceLogin, refreshAccessToken, detectHeadless, generateCodeVerifier, sha256Base64Url, buildAuthorizeUrl, LOOPBACK_PORTS, DEFAULT_LOGIN_TIMEOUT_MS, type PkceLoginOptions, type PkceLoginOutcome, type PkceDeclineReason, } from "./auth.js";
|
|
9
9
|
export { wantsHelp, shouldPrintHelp } from "./help-cli.js";
|
|
@@ -11,7 +11,7 @@ export { loadExternalRegistrations, recordExternalRegistration, clearExternalReg
|
|
|
11
11
|
export { SESSION_START_HOOK_WINDOW_MS, TOOL_HOOK_WINDOW_MS, HOOK_TIMEOUT_MAX_MS, sessionStartHookTimeout, toolHookTimeout, type HookTimeoutUnit, } from "./hook-timeout.js";
|
|
12
12
|
export { loadTokens, saveTokens, clearTokens, isExpired, refreshAndSave, tryAcquirePkceFlightLock, clearPkceFlightLock, waitForPeerTokens, waitForPeerTokensSync, TOKEN_EXPIRY_SKEW_SEC, type PkceFlightLock, } from "./auth-store.js";
|
|
13
13
|
export { ensureUserAuthenticated, ensureAuthenticated, type UserLoginDecision, type UserLoginMode, type UserLoginOptions, } from "./user-login.js";
|
|
14
|
-
export { resolveAgentCredentials, resolveRuntimeAgentCredentials, ensureAgentIdentity, ensureSubAgentIdentity, fetchClientCredentialsToken, registerAgentClient, loadAgentDynamicCredentials, loadAgentDynamicCredentialsByHarness, saveAgentDynamicCredentials, clearAgentDynamicCredentials, latestAgentRegistration, latestSubAgentSession, countAgentSessions, claimAgentDynamicCredentials, loadSubAgentDynamicCredentials, loadAllSubAgentDynamicCredentials, loadSubAgentDynamicCredentialsByHarness, saveSubAgentDynamicCredentials, clearSubAgentDynamicCredentials, clearSubAgentDynamicCredentialsForHarness, claimSubAgentDynamicCredentials, loadDelegationNodeId, saveDelegationNodeId, loadRetiredCredentials, clearRetiredCredentials, retireAgentSessions, sessionRetention, revokeAgentDynamicClient, AGENT_TOKEN_EXPIRY_SKEW_SEC, DEFAULT_SESSION_RETENTION, type AgentRegistrationClaim, type AgentCredentials, type AgentCredentialKind, type ResolveAgentCredentialsOptions, type ResolveRuntimeAgentCredentialsOptions, type EnsureAgentIdentityOptions, type RegisterAgentClientArgs, type DelegationInput, type RevokeAgentClientResult, type SubAgentIdentity, type EnsureSubAgentIdentityOptions, } from "./agent-auth.js";
|
|
14
|
+
export { resolveAgentCredentials, resolvePreprovisionedRuntimeContext, resolveRuntimeAgentCredentials, ensureAgentIdentity, ensureSubAgentIdentity, fetchClientCredentialsToken, registerAgentClient, loadAgentDynamicCredentials, loadAgentDynamicCredentialsByHarness, saveAgentDynamicCredentials, clearAgentDynamicCredentials, latestAgentRegistration, latestSubAgentSession, countAgentSessions, claimAgentDynamicCredentials, loadSubAgentDynamicCredentials, loadAllSubAgentDynamicCredentials, loadSubAgentDynamicCredentialsByHarness, saveSubAgentDynamicCredentials, clearSubAgentDynamicCredentials, clearSubAgentDynamicCredentialsForHarness, claimSubAgentDynamicCredentials, loadDelegationNodeId, resolvePreprovisionedRootDelegation, saveDelegationNodeId, loadRetiredCredentials, clearRetiredCredentials, retireAgentSessions, sessionRetention, revokeAgentDynamicClient, AGENT_TOKEN_EXPIRY_SKEW_SEC, DEFAULT_SESSION_RETENTION, type AgentRegistrationClaim, type AgentCredentials, type AgentCredentialKind, type ResolveAgentCredentialsOptions, type ResolveRuntimeAgentCredentialsOptions, type EnsureAgentIdentityOptions, type RegisterAgentClientArgs, type DelegationInput, type RevokeAgentClientResult, type SubAgentIdentity, type EnsureSubAgentIdentityOptions, } from "./agent-auth.js";
|
|
15
15
|
export { ensureReadCredential, resetReadCredentialBackoff, READ_CREDENTIAL_RETRY_MS, type EnsureReadCredentialOptions, type ReadCredentialOutcome, } from "./read-credential.js";
|
|
16
16
|
export { clearCredentialsForUninstall, resolveInstalledHarnesses, type ClearCredentialsOptions, type ClearCredentialsResult, } from "./uninstall.js";
|
|
17
17
|
export { type SessionInfo, type OAuth2TokenInfo, type PermissionCheck, type PermissionResult, type BatchPermissionResult, type OryError, type OryErrorCode, } from "./types.js";
|