@ory/argus 0.10.0 → 0.11.1
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 +1 -1
- package/dist/adapters.js +16 -1
- package/dist/agent-auth.js +23 -3
- package/dist/config.js +35 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/lifecycle.js +1 -0
- package/dist/permissions.d.ts +21 -5
- package/dist/permissions.js +45 -5
- package/dist/subject.d.ts +13 -1
- package/dist/subject.js +29 -2
- package/dist/tool-catalog.d.ts +1 -0
- package/dist/tool-catalog.js +19 -0
- package/dist/user-login.js +7 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Ory Argus: Agent and Developer Experience
|
|
2
2
|
|
|
3
|
-
The core API behind every Ory Agent Plugin and Extension. Argus wraps [Ory Identities](https://www.ory.com/ory-ecosystem), [Ory Permissions](https://ory.com/permissions), MCP authorization, and distributed tracing into a single client. Each
|
|
3
|
+
The core API behind every Ory Agent Plugin and Extension. Argus wraps [Ory Identities](https://www.ory.com/ory-ecosystem), [Ory Permissions](https://ory.com/permissions), MCP authorization, and distributed tracing into a single client. Each harness package (`@ory/claude-code`, `@ory/codex`, `@ory/gemini-cli`, and the rest — see the [root README](../../README.md) for the full list) is a thin adapter that maps one harness's hook contract onto Argus.
|
|
4
4
|
|
|
5
5
|
Argus is also published on its own so you can build new harness plugins or extensions, embed Ory into a custom agent runtime, or instrument any SDK that exposes event lifecycle hooks for session start, tool execution, and tool completion.
|
|
6
6
|
|
package/dist/adapters.js
CHANGED
|
@@ -128,10 +128,25 @@ async function gate(client, opts) {
|
|
|
128
128
|
const label = (0, subject_js_1.subjectLabel)(subjectRef);
|
|
129
129
|
const canBlock = opts.canBlock ?? true;
|
|
130
130
|
const spanAttrs = { toolName: opts.toolName, ...(opts.extraSpanAttributes ?? {}) };
|
|
131
|
+
const check = { namespace, object: opts.toolName, relation: "use", ...subjectRef };
|
|
132
|
+
// Audit-only kill switch: Ory is disabled entirely — no permission check.
|
|
133
|
+
// Record only the audit `tool.invoke` span (same span semantics as the
|
|
134
|
+
// harness plugins' audit-only short-circuit) and pass the tool through.
|
|
135
|
+
const resolved = (0, config_js_1.resolveConfig)();
|
|
136
|
+
if (resolved.auditOnly) {
|
|
137
|
+
client.tracer.record("tool.invoke", "ok", { attributes: spanAttrs });
|
|
138
|
+
const decision = {
|
|
139
|
+
kind: "allow",
|
|
140
|
+
result: { allowed: true, checkedAt: new Date().toISOString(), check },
|
|
141
|
+
mode: resolved.permissionMode,
|
|
142
|
+
spanAttributes: { permissionMode: resolved.permissionMode },
|
|
143
|
+
};
|
|
144
|
+
return { proceed: true, blocked: false, kind: "allow", decision, subject: label, namespace };
|
|
145
|
+
}
|
|
131
146
|
const outcome = await (0, permissions_js_1.gateToolCall)(client, {
|
|
132
147
|
harness: opts.harness,
|
|
133
148
|
toolName: opts.toolName,
|
|
134
|
-
check
|
|
149
|
+
check,
|
|
135
150
|
spanAttributes: spanAttrs,
|
|
136
151
|
});
|
|
137
152
|
if (outcome.kind === "audit_only") {
|
package/dist/agent-auth.js
CHANGED
|
@@ -384,12 +384,13 @@ async function fetchClientCredentialsToken(args) {
|
|
|
384
384
|
* the agent identity was available for the session.
|
|
385
385
|
*/
|
|
386
386
|
async function ensureAgentIdentity(client, options = {}) {
|
|
387
|
-
//
|
|
388
|
-
//
|
|
387
|
+
// Audit-only kill switch: Ory is disabled entirely — skip credential
|
|
388
|
+
// resolution (no DCR, no token grant) and record the no-op, shaped like
|
|
389
|
+
// the user gate's audit_only result.
|
|
389
390
|
if ((0, config_js_1.resolveConfig)().auditOnly) {
|
|
390
391
|
const creds = {
|
|
391
392
|
kind: "none",
|
|
392
|
-
reason: "Configured for audit-only mode; agent identity is a no-op",
|
|
393
|
+
reason: "Configured for audit-only mode; agent identity resolution is a no-op",
|
|
393
394
|
warnings: [],
|
|
394
395
|
};
|
|
395
396
|
client.tracer.record("agent.auth", "skipped", {
|
|
@@ -450,6 +451,25 @@ async function ensureAgentIdentity(client, options = {}) {
|
|
|
450
451
|
* resolution.
|
|
451
452
|
*/
|
|
452
453
|
async function ensureSubAgentIdentity(client, options) {
|
|
454
|
+
// Audit-only kill switch: same no-op as ensureAgentIdentity — no
|
|
455
|
+
// sub-agent DCR while Ory is disabled entirely.
|
|
456
|
+
if ((0, config_js_1.resolveConfig)().auditOnly) {
|
|
457
|
+
const identity = {
|
|
458
|
+
kind: "none",
|
|
459
|
+
subAgentType: options.subAgentType,
|
|
460
|
+
reason: "Configured for audit-only mode; sub-agent identity resolution is a no-op",
|
|
461
|
+
warnings: [],
|
|
462
|
+
};
|
|
463
|
+
client.tracer.record("agent.auth", "skipped", {
|
|
464
|
+
attributes: {
|
|
465
|
+
kind: "subagent_none",
|
|
466
|
+
subAgentType: identity.subAgentType,
|
|
467
|
+
reason: identity.reason,
|
|
468
|
+
auditOnly: true,
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
return identity;
|
|
472
|
+
}
|
|
453
473
|
const env = options.env ?? process.env;
|
|
454
474
|
const registerFn = options.registerAgentClientFn ?? registerAgentClient;
|
|
455
475
|
const loadFn = options.loadFn ?? loadSubAgentDynamicCredentials;
|
package/dist/config.js
CHANGED
|
@@ -290,15 +290,47 @@ function acquireLock() {
|
|
|
290
290
|
throw new Error(`Timed out waiting for config lock at ${lockPath}. ` +
|
|
291
291
|
"Another process may be holding it; remove the file if you are sure no other process is running.");
|
|
292
292
|
}
|
|
293
|
-
sleepSync(LOCK_POLL_MS)
|
|
293
|
+
if (!sleepSync(LOCK_POLL_MS)) {
|
|
294
|
+
// The runtime can't block synchronously (no SharedArrayBuffer /
|
|
295
|
+
// Atomics.wait — e.g. Cloudflare Workers). Rather than hot-spin
|
|
296
|
+
// until the deadline, proceed without the lock: the write itself
|
|
297
|
+
// stays atomic via write-temp + rename.
|
|
298
|
+
return { fd: null };
|
|
299
|
+
}
|
|
294
300
|
}
|
|
295
301
|
}
|
|
296
302
|
}
|
|
297
|
-
|
|
303
|
+
/**
|
|
304
|
+
* Lazily initialized buffer for `Atomics.wait`-based synchronous sleep.
|
|
305
|
+
* `undefined` = not yet probed; `null` = unavailable on this runtime.
|
|
306
|
+
* Never touched at module scope: constructing a SharedArrayBuffer eagerly
|
|
307
|
+
* would make merely importing this module throw on runtimes without it
|
|
308
|
+
* (e.g. Cloudflare Workers).
|
|
309
|
+
*/
|
|
310
|
+
let sleepBuf;
|
|
311
|
+
/** Sleep synchronously. Returns false when the runtime can't sleep. */
|
|
298
312
|
function sleepSync(ms) {
|
|
299
|
-
|
|
313
|
+
if (sleepBuf === undefined) {
|
|
314
|
+
try {
|
|
315
|
+
sleepBuf =
|
|
316
|
+
typeof SharedArrayBuffer === "function" &&
|
|
317
|
+
typeof Atomics !== "undefined" &&
|
|
318
|
+
typeof Atomics.wait === "function"
|
|
319
|
+
? new Int32Array(new SharedArrayBuffer(4))
|
|
320
|
+
: null;
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
sleepBuf = null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (sleepBuf === null)
|
|
327
|
+
return false;
|
|
328
|
+
Atomics.wait(sleepBuf, 0, 0, ms);
|
|
329
|
+
return true;
|
|
300
330
|
}
|
|
301
331
|
function releaseLock(lock) {
|
|
332
|
+
if (lock.fd === null)
|
|
333
|
+
return; // lock-less fallback: nothing to release
|
|
302
334
|
try {
|
|
303
335
|
fs.closeSync(lock.fd);
|
|
304
336
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export { checkAndDecide, applyPermissionMode, gateToolCall, type PermissionDecis
|
|
|
19
19
|
export { HARNESS_TOOL_CATALOG, KNOWN_HARNESSES, ALL_TOOLS, getToolCatalog, INTERACTIVE_TOOL_CATALOG, getInteractiveToolCatalog, isInteractiveTool, type KnownHarness, } from "./tool-catalog.js";
|
|
20
20
|
export { classifyLifecycle, isUserFacingPhase, isToolExecutionPhase, HARNESS_LIFECYCLE_MAP, USER_FACING_PHASES, TOOL_EXECUTION_PHASES, type LifecyclePhase, } from "./lifecycle.js";
|
|
21
21
|
export { parseClaudeCodeMcpTool, parseGeminiMcpTool, parseMcpToolGeneric, checkMcpPermission, type McpToolIdentifier, type McpPermissionCheckOptions, type McpPermissionResult, } from "./mcp.js";
|
|
22
|
-
export { resolveUserSubject, subjectLabel, type UserSubjectRef, } from "./subject.js";
|
|
22
|
+
export { resolveUserSubject, runWithUserSubject, subjectLabel, type UserSubjectRef, } from "./subject.js";
|
|
23
23
|
export { formatDenialMessage, formatDenialSummary, formatAlertMessage, formatAlertSummary, alertAttributes, OryDenialError, type DenialContext, type AlertAttributes, } from "./denial.js";
|
|
24
24
|
export { summarizeToolInput, summarizeToolOutput, type ToolInputSummary, type ToolOutputSummary, } from "./tool-metadata.js";
|
|
25
25
|
export { OtlpExporter, otlpExporterFromEnv, parseKeyValueList, type SpanExporter, type OtlpExporterOptions, type OtlpProtocol, } from "./otel/index.js";
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.printOryConfig = exports.runAgentCommand = exports.runConfigureCommand = exports.AGENT_TOKEN_EXPIRY_SKEW_SEC = exports.clearSubAgentDynamicCredentials = exports.saveSubAgentDynamicCredentials = exports.loadSubAgentDynamicCredentials = exports.clearAgentDynamicCredentials = exports.saveAgentDynamicCredentials = exports.loadAgentDynamicCredentials = exports.registerAgentClient = exports.fetchClientCredentialsToken = exports.ensureSubAgentIdentity = exports.ensureAgentIdentity = exports.resolveAgentCredentials = exports.ensureAuthenticated = exports.ensureUserAuthenticated = exports.TOKEN_EXPIRY_SKEW_SEC = exports.waitForPeerTokensSync = exports.waitForPeerTokens = exports.clearPkceFlightLock = exports.tryAcquirePkceFlightLock = exports.refreshAndSave = exports.isExpired = exports.clearTokens = exports.saveTokens = exports.loadTokens = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.LOOPBACK_PORTS = exports.buildAuthorizeUrl = exports.sha256Base64Url = exports.generateCodeVerifier = exports.detectHeadless = exports.refreshAccessToken = exports.pkceLogin = exports.getHarnessDataDir = exports.getDataDir = exports.getConfigPath = exports.mutateConfig = exports.resolveConfig = exports.saveConfig = exports.loadConfig = exports.watchTraceFile = exports.formatSpan = exports.deriveTraceId = exports.ActiveSpan = exports.Tracer = exports.redactLogData = exports.DebugLogger = exports.OryAgentClient = void 0;
|
|
4
4
|
exports.runLocalCommand = exports.ORY_COMMAND_SLUGS = exports.ORY_COMMAND_SKILL_NAMES = exports.ORY_SKILL_NAMES = exports.removeSkillDirs = exports.writeSkillTree = exports.toSkillMarkdown = exports.commandToPlainMarkdown = exports.commandToFrontmatterMarkdown = exports.commandToToml = exports.commandToSkill = exports.renderOryCommands = exports.renderOrySkills = exports.runDevLauncher = exports.unregisterPlugin = exports.registerPlugin = exports.removeMcpServer = exports.mergeMcpServer = exports.mcpServerEntry = exports.resolveMcpServerCommand = exports.printNextSteps = exports.printSetupHelp = exports.removeFlatHooks = exports.mergeFlatHooks = exports.flatHookEntry = exports.removeMatcherHooks = exports.mergeMatcherHooks = exports.matcherHookEntry = exports.resolveHookCommand = exports.isOryHookCommand = exports.writeJsonFile = exports.readJsonFile = exports.parseSetupArgs = exports.printPermissionsSection = exports.printAgentIdentitySection = exports.printUserIdentitySection = exports.runStatusCommand = exports.printPermissionsOnboardingHelp = exports.maybeAutoBootstrap = exports.isUserIdentityCached = exports.runPermissionsCommand = exports.interactiveConfigPrompt = exports.promptForProjectUrl = exports.promptOnTty = exports.isTtyAvailable = exports.runWatchCommand = exports.printTraceTail = exports.printEnvHelp = exports.printLogTail = exports.printEnvironment = void 0;
|
|
5
|
-
exports.wrapTool = exports.registerSubagent = exports.complete = exports.gate = exports.sessionStart = exports.resolveNamespace = exports.parseKeyValueList = exports.otlpExporterFromEnv = exports.OtlpExporter = exports.summarizeToolOutput = exports.summarizeToolInput = exports.OryDenialError = exports.alertAttributes = exports.formatAlertSummary = exports.formatAlertMessage = exports.formatDenialSummary = exports.formatDenialMessage = exports.subjectLabel = exports.resolveUserSubject = exports.checkMcpPermission = exports.parseMcpToolGeneric = exports.parseGeminiMcpTool = exports.parseClaudeCodeMcpTool = exports.TOOL_EXECUTION_PHASES = exports.USER_FACING_PHASES = exports.HARNESS_LIFECYCLE_MAP = exports.isToolExecutionPhase = exports.isUserFacingPhase = exports.classifyLifecycle = exports.isInteractiveTool = exports.getInteractiveToolCatalog = exports.INTERACTIVE_TOOL_CATALOG = exports.getToolCatalog = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = exports.gateToolCall = exports.applyPermissionMode = exports.checkAndDecide = exports.runRegistryCommand = exports.DEV_JAEGER_CONTAINER = exports.stopDevJaeger = exports.ensureDevJaeger = void 0;
|
|
5
|
+
exports.wrapTool = exports.registerSubagent = exports.complete = exports.gate = exports.sessionStart = exports.resolveNamespace = exports.parseKeyValueList = exports.otlpExporterFromEnv = exports.OtlpExporter = exports.summarizeToolOutput = exports.summarizeToolInput = exports.OryDenialError = exports.alertAttributes = exports.formatAlertSummary = exports.formatAlertMessage = exports.formatDenialSummary = exports.formatDenialMessage = exports.subjectLabel = exports.runWithUserSubject = exports.resolveUserSubject = exports.checkMcpPermission = exports.parseMcpToolGeneric = exports.parseGeminiMcpTool = exports.parseClaudeCodeMcpTool = exports.TOOL_EXECUTION_PHASES = exports.USER_FACING_PHASES = exports.HARNESS_LIFECYCLE_MAP = exports.isToolExecutionPhase = exports.isUserFacingPhase = exports.classifyLifecycle = exports.isInteractiveTool = exports.getInteractiveToolCatalog = exports.INTERACTIVE_TOOL_CATALOG = exports.getToolCatalog = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = exports.gateToolCall = exports.applyPermissionMode = exports.checkAndDecide = exports.runRegistryCommand = exports.DEV_JAEGER_CONTAINER = exports.stopDevJaeger = exports.ensureDevJaeger = void 0;
|
|
6
6
|
var client_js_1 = require("./client.js");
|
|
7
7
|
Object.defineProperty(exports, "OryAgentClient", { enumerable: true, get: function () { return client_js_1.OryAgentClient; } });
|
|
8
8
|
var logger_js_1 = require("./logger.js");
|
|
@@ -149,6 +149,7 @@ Object.defineProperty(exports, "parseMcpToolGeneric", { enumerable: true, get: f
|
|
|
149
149
|
Object.defineProperty(exports, "checkMcpPermission", { enumerable: true, get: function () { return mcp_js_1.checkMcpPermission; } });
|
|
150
150
|
var subject_js_1 = require("./subject.js");
|
|
151
151
|
Object.defineProperty(exports, "resolveUserSubject", { enumerable: true, get: function () { return subject_js_1.resolveUserSubject; } });
|
|
152
|
+
Object.defineProperty(exports, "runWithUserSubject", { enumerable: true, get: function () { return subject_js_1.runWithUserSubject; } });
|
|
152
153
|
Object.defineProperty(exports, "subjectLabel", { enumerable: true, get: function () { return subject_js_1.subjectLabel; } });
|
|
153
154
|
var denial_js_1 = require("./denial.js");
|
|
154
155
|
Object.defineProperty(exports, "formatDenialMessage", { enumerable: true, get: function () { return denial_js_1.formatDenialMessage; } });
|
package/dist/lifecycle.js
CHANGED
package/dist/permissions.d.ts
CHANGED
|
@@ -12,8 +12,15 @@
|
|
|
12
12
|
* return `{ kind: "observe", … }` so the caller allows the
|
|
13
13
|
* tool through.
|
|
14
14
|
* - `enforce` — return `{ kind: "deny", … }` so the caller blocks.
|
|
15
|
-
* 3. On a thrown {@link OryError}
|
|
16
|
-
*
|
|
15
|
+
* 3. On a thrown {@link OryError}, inspects the classified code:
|
|
16
|
+
* - Infrastructure errors (`network_error`, `rate_limited`,
|
|
17
|
+
* `not_found`, `unknown`) — returns `{ kind: "fail_open", … }`.
|
|
18
|
+
* Callers should log and allow.
|
|
19
|
+
* - Auth-rejected check calls (`forbidden`, `session_inactive`,
|
|
20
|
+
* `session_aal2_required` — the check itself was rejected, so
|
|
21
|
+
* its result cannot be trusted) — in `enforce` mode returns
|
|
22
|
+
* `{ kind: "deny", … }` so the caller blocks; in `observe`
|
|
23
|
+
* mode falls back to `fail_open` (observe never blocks).
|
|
17
24
|
*
|
|
18
25
|
* The discriminated `kind` lets plugins map cleanly to their native
|
|
19
26
|
* decision shape (claude-code exit codes, openclaw `{ block: true }`,
|
|
@@ -22,7 +29,7 @@
|
|
|
22
29
|
*/
|
|
23
30
|
import type { OryAgentClient } from "./client.js";
|
|
24
31
|
import { type PermissionMode } from "./config.js";
|
|
25
|
-
import type { OryError, PermissionCheck, PermissionResult } from "./types.js";
|
|
32
|
+
import type { OryError, OryErrorCode, PermissionCheck, PermissionResult } from "./types.js";
|
|
26
33
|
/**
|
|
27
34
|
* Attributes describing *what was checked* and *under which posture*.
|
|
28
35
|
* Plugins spread this onto their `tool.invoke` / `tool.block` spans so
|
|
@@ -36,6 +43,13 @@ export interface DecisionSpanAttributes {
|
|
|
36
43
|
subjectId?: string;
|
|
37
44
|
/** SubjectSet rendered as `<namespace>:<object>#<relation>` when used. */
|
|
38
45
|
subjectSet?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Set when the check call itself was auth-rejected and enforce mode
|
|
48
|
+
* turned that into a deny — the classified {@link OryErrorCode} that
|
|
49
|
+
* caused it. Makes the "denied because the check failed" case
|
|
50
|
+
* distinguishable from a normal Keto deny in the audit trail.
|
|
51
|
+
*/
|
|
52
|
+
checkRejected?: OryErrorCode;
|
|
39
53
|
}
|
|
40
54
|
export type PermissionDecision = {
|
|
41
55
|
kind: "allow";
|
|
@@ -182,7 +196,9 @@ export interface GateToolCallArgs {
|
|
|
182
196
|
export declare function gateToolCall(client: OryAgentClient, args: GateToolCallArgs): Promise<ToolGateOutcome>;
|
|
183
197
|
/**
|
|
184
198
|
* Run a permission check and resolve the configured mode against the
|
|
185
|
-
* result. Never throws —
|
|
186
|
-
* `{ kind: "fail_open" }` decision
|
|
199
|
+
* result. Never throws — infrastructure errors surface as a typed
|
|
200
|
+
* `{ kind: "fail_open" }` decision, and auth-rejected check calls in
|
|
201
|
+
* enforce mode as `{ kind: "deny" }` (see
|
|
202
|
+
* {@link CHECK_AUTH_REJECTED_CODES}).
|
|
187
203
|
*/
|
|
188
204
|
export declare function checkAndDecide(client: OryAgentClient, check: PermissionCheck, opts?: CheckAndDecideOptions): Promise<PermissionDecision>;
|
package/dist/permissions.js
CHANGED
|
@@ -13,8 +13,15 @@
|
|
|
13
13
|
* return `{ kind: "observe", … }` so the caller allows the
|
|
14
14
|
* tool through.
|
|
15
15
|
* - `enforce` — return `{ kind: "deny", … }` so the caller blocks.
|
|
16
|
-
* 3. On a thrown {@link OryError}
|
|
17
|
-
*
|
|
16
|
+
* 3. On a thrown {@link OryError}, inspects the classified code:
|
|
17
|
+
* - Infrastructure errors (`network_error`, `rate_limited`,
|
|
18
|
+
* `not_found`, `unknown`) — returns `{ kind: "fail_open", … }`.
|
|
19
|
+
* Callers should log and allow.
|
|
20
|
+
* - Auth-rejected check calls (`forbidden`, `session_inactive`,
|
|
21
|
+
* `session_aal2_required` — the check itself was rejected, so
|
|
22
|
+
* its result cannot be trusted) — in `enforce` mode returns
|
|
23
|
+
* `{ kind: "deny", … }` so the caller blocks; in `observe`
|
|
24
|
+
* mode falls back to `fail_open` (observe never blocks).
|
|
18
25
|
*
|
|
19
26
|
* The discriminated `kind` lets plugins map cleanly to their native
|
|
20
27
|
* decision shape (claude-code exit codes, openclaw `{ block: true }`,
|
|
@@ -27,6 +34,19 @@ exports.gateToolCall = gateToolCall;
|
|
|
27
34
|
exports.checkAndDecide = checkAndDecide;
|
|
28
35
|
const config_js_1 = require("./config.js");
|
|
29
36
|
const tool_catalog_js_1 = require("./tool-catalog.js");
|
|
37
|
+
/**
|
|
38
|
+
* Error codes meaning the check call itself was rejected for
|
|
39
|
+
* authentication/authorization reasons — an expired or misconfigured
|
|
40
|
+
* agent credential, an inactive session, or a missing MFA step. The
|
|
41
|
+
* check result cannot be trusted, so `enforce` mode denies instead of
|
|
42
|
+
* failing open. Infrastructure errors (`network_error`, `rate_limited`,
|
|
43
|
+
* `not_found`, `unknown`) keep the fail-open posture.
|
|
44
|
+
*/
|
|
45
|
+
const CHECK_AUTH_REJECTED_CODES = new Set([
|
|
46
|
+
"forbidden",
|
|
47
|
+
"session_inactive",
|
|
48
|
+
"session_aal2_required",
|
|
49
|
+
]);
|
|
30
50
|
function formatSubjectSet(set) {
|
|
31
51
|
if (!set)
|
|
32
52
|
return undefined;
|
|
@@ -140,8 +160,10 @@ async function gateToolCall(client, args) {
|
|
|
140
160
|
}
|
|
141
161
|
/**
|
|
142
162
|
* Run a permission check and resolve the configured mode against the
|
|
143
|
-
* result. Never throws —
|
|
144
|
-
* `{ kind: "fail_open" }` decision
|
|
163
|
+
* result. Never throws — infrastructure errors surface as a typed
|
|
164
|
+
* `{ kind: "fail_open" }` decision, and auth-rejected check calls in
|
|
165
|
+
* enforce mode as `{ kind: "deny" }` (see
|
|
166
|
+
* {@link CHECK_AUTH_REJECTED_CODES}).
|
|
145
167
|
*/
|
|
146
168
|
async function checkAndDecide(client, check, opts = {}) {
|
|
147
169
|
const mode = opts.modeOverride ?? (0, config_js_1.resolveConfig)().permissionMode;
|
|
@@ -153,7 +175,25 @@ async function checkAndDecide(client, check, opts = {}) {
|
|
|
153
175
|
});
|
|
154
176
|
}
|
|
155
177
|
catch (err) {
|
|
156
|
-
|
|
178
|
+
const oryErr = err;
|
|
179
|
+
if (mode === "enforce" && oryErr && CHECK_AUTH_REJECTED_CODES.has(oryErr.code)) {
|
|
180
|
+
// The check call itself was rejected for auth reasons — the
|
|
181
|
+
// result cannot be trusted, so enforce mode must not fail open.
|
|
182
|
+
client.logger.warn("permission.check_rejected", {
|
|
183
|
+
namespace: check.namespace,
|
|
184
|
+
object: check.object,
|
|
185
|
+
relation: check.relation,
|
|
186
|
+
code: oryErr.code,
|
|
187
|
+
note: `permission check rejected (${oryErr.code}); enforce mode denies when checks cannot be completed`,
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
kind: "deny",
|
|
191
|
+
result: { allowed: false, checkedAt: new Date().toISOString(), check },
|
|
192
|
+
mode: "enforce",
|
|
193
|
+
spanAttributes: { ...spanAttributes, checkRejected: oryErr.code },
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return { kind: "fail_open", error: oryErr, mode, spanAttributes };
|
|
157
197
|
}
|
|
158
198
|
const inner = applyPermissionMode(client, result.allowed, {
|
|
159
199
|
namespace: check.namespace,
|
package/dist/subject.d.ts
CHANGED
|
@@ -27,7 +27,19 @@ export type UserSubjectRef = {
|
|
|
27
27
|
};
|
|
28
28
|
};
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* Run `fn` with a per-call user subject that takes precedence over the
|
|
31
|
+
* client's user principal and the env overrides in
|
|
32
|
+
* {@link resolveUserSubject}. The override only applies within `fn`'s
|
|
33
|
+
* async context, so concurrent calls cannot observe each other's subject.
|
|
34
|
+
* `ORY_USER_SUBJECT_NAMESPACE` SubjectSet shaping still applies.
|
|
35
|
+
*
|
|
36
|
+
* A missing/empty `subject` is a no-op: `fn` runs with the normal
|
|
37
|
+
* resolution chain.
|
|
38
|
+
*/
|
|
39
|
+
export declare function runWithUserSubject<T>(subject: string | undefined, fn: () => T): T;
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the user subject for permission checks. Prefers a per-call
|
|
42
|
+
* override installed via {@link runWithUserSubject}, then the user
|
|
31
43
|
* login's `userPrincipal.subject`, falls back to `ORY_USER_SUBJECT_ID`, then the
|
|
32
44
|
* legacy `ORY_AGENT_SUBJECT_ID`, then the caller-supplied `fallback`
|
|
33
45
|
* (typically `session:<id>`).
|
package/dist/subject.js
CHANGED
|
@@ -18,10 +18,36 @@
|
|
|
18
18
|
* falls back to the direct SubjectID chain.
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.runWithUserSubject = runWithUserSubject;
|
|
21
22
|
exports.resolveUserSubject = resolveUserSubject;
|
|
22
23
|
exports.subjectLabel = subjectLabel;
|
|
24
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
23
25
|
/**
|
|
24
|
-
*
|
|
26
|
+
* Per-async-context user-subject override. Populated by
|
|
27
|
+
* {@link runWithUserSubject} so multi-user server integrations (Vercel AI
|
|
28
|
+
* SDK, Cloudflare Agents) can attribute each request to its own acting
|
|
29
|
+
* user without mutating the shared client or process env — both of which
|
|
30
|
+
* would race under concurrent requests.
|
|
31
|
+
*/
|
|
32
|
+
const perCallUserSubject = new node_async_hooks_1.AsyncLocalStorage();
|
|
33
|
+
/**
|
|
34
|
+
* Run `fn` with a per-call user subject that takes precedence over the
|
|
35
|
+
* client's user principal and the env overrides in
|
|
36
|
+
* {@link resolveUserSubject}. The override only applies within `fn`'s
|
|
37
|
+
* async context, so concurrent calls cannot observe each other's subject.
|
|
38
|
+
* `ORY_USER_SUBJECT_NAMESPACE` SubjectSet shaping still applies.
|
|
39
|
+
*
|
|
40
|
+
* A missing/empty `subject` is a no-op: `fn` runs with the normal
|
|
41
|
+
* resolution chain.
|
|
42
|
+
*/
|
|
43
|
+
function runWithUserSubject(subject, fn) {
|
|
44
|
+
if (!subject)
|
|
45
|
+
return fn();
|
|
46
|
+
return perCallUserSubject.run(subject, fn);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the user subject for permission checks. Prefers a per-call
|
|
50
|
+
* override installed via {@link runWithUserSubject}, then the user
|
|
25
51
|
* login's `userPrincipal.subject`, falls back to `ORY_USER_SUBJECT_ID`, then the
|
|
26
52
|
* legacy `ORY_AGENT_SUBJECT_ID`, then the caller-supplied `fallback`
|
|
27
53
|
* (typically `session:<id>`).
|
|
@@ -30,7 +56,8 @@ exports.subjectLabel = subjectLabel;
|
|
|
30
56
|
* concrete subject is available; otherwise a direct SubjectID.
|
|
31
57
|
*/
|
|
32
58
|
function resolveUserSubject(client, fallback) {
|
|
33
|
-
const subject =
|
|
59
|
+
const subject = perCallUserSubject.getStore()
|
|
60
|
+
?? client.userPrincipal.subject
|
|
34
61
|
?? process.env.ORY_USER_SUBJECT_ID
|
|
35
62
|
?? process.env.ORY_AGENT_SUBJECT_ID
|
|
36
63
|
?? fallback;
|
package/dist/tool-catalog.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export declare const HARNESS_TOOL_CATALOG: {
|
|
18
18
|
readonly "claude-code": readonly ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebFetch", "WebSearch", "NotebookEdit", "TodoWrite", "Task"];
|
|
19
|
+
readonly "claude-agent-sdk": readonly ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebFetch", "WebSearch", "NotebookEdit", "TodoWrite", "Task"];
|
|
19
20
|
readonly codex: readonly ["shell", "apply_patch", "read_file"];
|
|
20
21
|
readonly "gemini-cli": readonly ["read_file", "write_file", "edit_file", "list_files", "search_files", "shell", "web_search"];
|
|
21
22
|
readonly openclaw: readonly ["execute_command", "read_file", "write_file", "list_directory", "search_files", "browser"];
|
package/dist/tool-catalog.js
CHANGED
|
@@ -34,6 +34,22 @@ exports.HARNESS_TOOL_CATALOG = {
|
|
|
34
34
|
"TodoWrite",
|
|
35
35
|
"Task",
|
|
36
36
|
],
|
|
37
|
+
// The Claude Agent SDK exposes the same built-in tool surface as Claude
|
|
38
|
+
// Code (it is the embeddable form of the same runtime), so the catalog
|
|
39
|
+
// mirrors claude-code's and `permissions bootstrap` can seed it.
|
|
40
|
+
"claude-agent-sdk": [
|
|
41
|
+
"Read",
|
|
42
|
+
"Write",
|
|
43
|
+
"Edit",
|
|
44
|
+
"Bash",
|
|
45
|
+
"Glob",
|
|
46
|
+
"Grep",
|
|
47
|
+
"WebFetch",
|
|
48
|
+
"WebSearch",
|
|
49
|
+
"NotebookEdit",
|
|
50
|
+
"TodoWrite",
|
|
51
|
+
"Task",
|
|
52
|
+
],
|
|
37
53
|
codex: ["shell", "apply_patch", "read_file"],
|
|
38
54
|
"gemini-cli": [
|
|
39
55
|
"read_file",
|
|
@@ -136,6 +152,9 @@ exports.ALL_TOOLS = Array.from(new Set(Object.values(exports.HARNESS_TOOL_CATALO
|
|
|
136
152
|
*/
|
|
137
153
|
exports.INTERACTIVE_TOOL_CATALOG = {
|
|
138
154
|
"claude-code": ["AskUserQuestion", "ExitPlanMode", "TodoWrite"],
|
|
155
|
+
// Same runtime as Claude Code — the SDK delivers the same
|
|
156
|
+
// user-interaction primitives through PreToolUse.
|
|
157
|
+
"claude-agent-sdk": ["AskUserQuestion", "ExitPlanMode", "TodoWrite"],
|
|
139
158
|
codex: [],
|
|
140
159
|
"gemini-cli": [],
|
|
141
160
|
openclaw: [],
|
package/dist/user-login.js
CHANGED
|
@@ -165,8 +165,13 @@ async function runUserLogin(client, options) {
|
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
-
// 2. Pre-supplied env tokens short-circuit the browser flow (CI, scripted
|
|
169
|
-
|
|
168
|
+
// 2. Pre-supplied env tokens short-circuit the browser flow (CI, scripted
|
|
169
|
+
// runs). ORY_USER_* are the documented names; the legacy single-identity
|
|
170
|
+
// ORY_SESSION_TOKEN / ORY_OAUTH2_TOKEN are still honored.
|
|
171
|
+
if (process.env.ORY_USER_SESSION_TOKEN ||
|
|
172
|
+
process.env.ORY_USER_OAUTH2_TOKEN ||
|
|
173
|
+
process.env.ORY_SESSION_TOKEN ||
|
|
174
|
+
process.env.ORY_OAUTH2_TOKEN) {
|
|
170
175
|
return {
|
|
171
176
|
proceed: true,
|
|
172
177
|
mode: "env_token",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/argus",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
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",
|