@ory/argus 0.2.1 → 0.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/dist/client.js +5 -0
- package/dist/index.d.ts +2 -2
- package/dist/permissions.d.ts +23 -0
- package/dist/permissions.js +28 -8
- package/dist/tracer.d.ts +18 -0
- package/dist/tracer.js +31 -2
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -38,6 +38,11 @@ class OryAgentClient {
|
|
|
38
38
|
traceFile: config.traceFile,
|
|
39
39
|
...(config.exporter ? { exporter: config.exporter } : {}),
|
|
40
40
|
});
|
|
41
|
+
// Every span — including direct `tracer.record(...)` calls from
|
|
42
|
+
// plugin handlers — gets userSubject / agentSubject merged in, so
|
|
43
|
+
// tool.invoke / tool.block / permission.observe_deny spans carry
|
|
44
|
+
// the same principal attribution as the API-call spans.
|
|
45
|
+
this.tracer.setAttributeEnricher(() => this.principalSpanAttributes());
|
|
41
46
|
// Seed agent principal from the constructor `apiKey` so existing
|
|
42
47
|
// call sites keep working until they migrate to ensureAgentIdentity.
|
|
43
48
|
if (config.apiKey)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { OryAgentClient, type OryAgentConfig, type PrincipalIdentity, } from "./client.js";
|
|
2
2
|
export { DebugLogger, redactLogData, type LogEntry, type LogLevel, } from "./logger.js";
|
|
3
|
-
export { Tracer, ActiveSpan, deriveTraceId, formatSpan, watchTraceFile, type TraceEvent, type SpanStatus, type TraceSpan, type SpanOptions, type TracerOptions, type TracerContext, } from "./tracer.js";
|
|
3
|
+
export { Tracer, ActiveSpan, deriveTraceId, formatSpan, watchTraceFile, type TraceEvent, type SpanStatus, type TraceSpan, type SpanOptions, type TracerOptions, type TracerContext, type SpanAttributeEnricher, } from "./tracer.js";
|
|
4
4
|
export { loadConfig, saveConfig, resolveConfig, mutateConfig, getConfigPath, getDataDir, getHarnessDataDir, type OryPluginConfig, type OryOAuth2Tokens, type OryUserCredentials, type OryAgentCredentialsBlock, type OryAgentDynamicCredentials, type PermissionMode, } from "./config.js";
|
|
5
5
|
export { pkceLogin, refreshAccessToken, detectHeadless, generateCodeVerifier, sha256Base64Url, buildAuthorizeUrl, LOOPBACK_PORTS, DEFAULT_LOGIN_TIMEOUT_MS, type PkceLoginOptions, type PkceLoginOutcome, type PkceDeclineReason, } from "./auth.js";
|
|
6
6
|
export { loadTokens, saveTokens, clearTokens, isExpired, refreshAndSave, tryAcquirePkceFlightLock, clearPkceFlightLock, waitForPeerTokens, waitForPeerTokensSync, TOKEN_EXPIRY_SKEW_SEC, type PkceFlightLock, } from "./auth-store.js";
|
|
@@ -14,7 +14,7 @@ export { runDevLauncher, type DevLauncherConfig, type InstallContext, } from "./
|
|
|
14
14
|
export { renderOrySkills, renderOryCommands, commandToSkill, commandToToml, commandToFrontmatterMarkdown, commandToPlainMarkdown, toSkillMarkdown, writeSkillTree, removeSkillDirs, ORY_SKILL_NAMES, ORY_COMMAND_SKILL_NAMES, ORY_COMMAND_SLUGS, type RenderedSkill, type RenderedCommand, type RenderProfileOptions, } from "./skills.js";
|
|
15
15
|
export { runLocalCommand, ensureDevJaeger, stopDevJaeger, DEV_JAEGER_CONTAINER, type EnsureDevJaegerResult, type StopDevJaegerResult, } from "./local/index.js";
|
|
16
16
|
export { runRegistryCommand } from "./registry/index.js";
|
|
17
|
-
export { checkAndDecide, applyPermissionMode, type PermissionDecision, type ModeDecision, type CheckAndDecideOptions, type ApplyPermissionModeContext, } from "./permissions.js";
|
|
17
|
+
export { checkAndDecide, applyPermissionMode, type PermissionDecision, type ModeDecision, type DecisionSpanAttributes, type CheckAndDecideOptions, type ApplyPermissionModeContext, } from "./permissions.js";
|
|
18
18
|
export { HARNESS_TOOL_CATALOG, KNOWN_HARNESSES, ALL_TOOLS, getToolCatalog, type KnownHarness, } from "./tool-catalog.js";
|
|
19
19
|
export { parseClaudeCodeMcpTool, parseGeminiMcpTool, parseMcpToolGeneric, checkMcpPermission, type McpToolIdentifier, type McpPermissionCheckOptions, type McpPermissionResult, } from "./mcp.js";
|
|
20
20
|
export { resolveUserSubject, subjectLabel, type UserSubjectRef, } from "./subject.js";
|
package/dist/permissions.d.ts
CHANGED
|
@@ -23,22 +23,40 @@
|
|
|
23
23
|
import type { OryAgentClient } from "./client.js";
|
|
24
24
|
import { type PermissionMode } from "./config.js";
|
|
25
25
|
import type { OryError, PermissionCheck, PermissionResult } from "./types.js";
|
|
26
|
+
/**
|
|
27
|
+
* Attributes describing *what was checked* and *under which posture*.
|
|
28
|
+
* Plugins spread this onto their `tool.invoke` / `tool.block` spans so
|
|
29
|
+
* the audit trail makes the observe-vs-enforce posture and the checked
|
|
30
|
+
* subject visible at a glance — without that, a span with `allowed=false`
|
|
31
|
+
* status=ok is ambiguous (observe pass-through? fail-open? bug?).
|
|
32
|
+
*/
|
|
33
|
+
export interface DecisionSpanAttributes {
|
|
34
|
+
permissionMode: PermissionMode;
|
|
35
|
+
/** Direct subject ID when the check used `subjectId`. */
|
|
36
|
+
subjectId?: string;
|
|
37
|
+
/** SubjectSet rendered as `<namespace>:<object>#<relation>` when used. */
|
|
38
|
+
subjectSet?: string;
|
|
39
|
+
}
|
|
26
40
|
export type PermissionDecision = {
|
|
27
41
|
kind: "allow";
|
|
28
42
|
result: PermissionResult;
|
|
29
43
|
mode: PermissionMode;
|
|
44
|
+
spanAttributes: DecisionSpanAttributes;
|
|
30
45
|
} | {
|
|
31
46
|
kind: "deny";
|
|
32
47
|
result: PermissionResult;
|
|
33
48
|
mode: "enforce";
|
|
49
|
+
spanAttributes: DecisionSpanAttributes;
|
|
34
50
|
} | {
|
|
35
51
|
kind: "observe";
|
|
36
52
|
result: PermissionResult;
|
|
37
53
|
mode: "observe";
|
|
54
|
+
spanAttributes: DecisionSpanAttributes;
|
|
38
55
|
} | {
|
|
39
56
|
kind: "fail_open";
|
|
40
57
|
error: OryError;
|
|
41
58
|
mode: PermissionMode;
|
|
59
|
+
spanAttributes: DecisionSpanAttributes;
|
|
42
60
|
};
|
|
43
61
|
/**
|
|
44
62
|
* The "what should the caller do?" half of a decision, independent of
|
|
@@ -49,12 +67,15 @@ export type PermissionDecision = {
|
|
|
49
67
|
export type ModeDecision = {
|
|
50
68
|
kind: "allow";
|
|
51
69
|
mode: PermissionMode;
|
|
70
|
+
spanAttributes: DecisionSpanAttributes;
|
|
52
71
|
} | {
|
|
53
72
|
kind: "deny";
|
|
54
73
|
mode: "enforce";
|
|
74
|
+
spanAttributes: DecisionSpanAttributes;
|
|
55
75
|
} | {
|
|
56
76
|
kind: "observe";
|
|
57
77
|
mode: "observe";
|
|
78
|
+
spanAttributes: DecisionSpanAttributes;
|
|
58
79
|
};
|
|
59
80
|
export interface CheckAndDecideOptions {
|
|
60
81
|
/**
|
|
@@ -81,6 +102,8 @@ export interface ApplyPermissionModeContext {
|
|
|
81
102
|
relation?: string;
|
|
82
103
|
/** Subject ID for the observe-deny log line, if available. */
|
|
83
104
|
subjectId?: string;
|
|
105
|
+
/** SubjectSet for the observe-deny log line, if available. */
|
|
106
|
+
subjectSet?: PermissionCheck["subjectSet"];
|
|
84
107
|
/** Attributes merged into the `permission.observe_deny` span. */
|
|
85
108
|
spanAttributes?: Record<string, unknown>;
|
|
86
109
|
/** Override the resolved mode (tests). */
|
package/dist/permissions.js
CHANGED
|
@@ -25,6 +25,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
25
25
|
exports.applyPermissionMode = applyPermissionMode;
|
|
26
26
|
exports.checkAndDecide = checkAndDecide;
|
|
27
27
|
const config_js_1 = require("./config.js");
|
|
28
|
+
function formatSubjectSet(set) {
|
|
29
|
+
if (!set)
|
|
30
|
+
return undefined;
|
|
31
|
+
return `${set.namespace}:${set.object}#${set.relation}`;
|
|
32
|
+
}
|
|
33
|
+
function buildDecisionAttributes(mode, subjectId, subjectSet) {
|
|
34
|
+
const set = formatSubjectSet(subjectSet);
|
|
35
|
+
// SubjectSet supersedes subjectId in the span (the printable label is
|
|
36
|
+
// derivable from it); avoid emitting both so consumers don't need to
|
|
37
|
+
// dedupe.
|
|
38
|
+
if (set)
|
|
39
|
+
return { permissionMode: mode, subjectSet: set };
|
|
40
|
+
if (subjectId)
|
|
41
|
+
return { permissionMode: mode, subjectId };
|
|
42
|
+
return { permissionMode: mode };
|
|
43
|
+
}
|
|
28
44
|
/**
|
|
29
45
|
* Map an `allowed` boolean from any permission check (plain or MCP)
|
|
30
46
|
* onto a {@link ModeDecision}. When `allowed === false` and the mode
|
|
@@ -34,14 +50,16 @@ const config_js_1 = require("./config.js");
|
|
|
34
50
|
*/
|
|
35
51
|
function applyPermissionMode(client, allowed, context = {}) {
|
|
36
52
|
const mode = context.modeOverride ?? (0, config_js_1.resolveConfig)().permissionMode;
|
|
53
|
+
const spanAttributes = buildDecisionAttributes(mode, context.subjectId, context.subjectSet);
|
|
37
54
|
if (allowed)
|
|
38
|
-
return { kind: "allow", mode };
|
|
55
|
+
return { kind: "allow", mode, spanAttributes };
|
|
39
56
|
if (mode === "observe") {
|
|
40
57
|
client.logger.warn("permission.observe_deny", {
|
|
41
58
|
namespace: context.namespace,
|
|
42
59
|
object: context.object,
|
|
43
60
|
relation: context.relation,
|
|
44
61
|
subjectId: context.subjectId,
|
|
62
|
+
subjectSet: spanAttributes.subjectSet,
|
|
45
63
|
note: "denied by Ory; allowed by plugin in observe mode",
|
|
46
64
|
});
|
|
47
65
|
client.tracer.record("permission.observe_deny", "denied", {
|
|
@@ -49,13 +67,13 @@ function applyPermissionMode(client, allowed, context = {}) {
|
|
|
49
67
|
namespace: context.namespace,
|
|
50
68
|
object: context.object,
|
|
51
69
|
relation: context.relation,
|
|
52
|
-
|
|
70
|
+
...spanAttributes,
|
|
53
71
|
...context.spanAttributes,
|
|
54
72
|
},
|
|
55
73
|
});
|
|
56
|
-
return { kind: "observe", mode: "observe" };
|
|
74
|
+
return { kind: "observe", mode: "observe", spanAttributes };
|
|
57
75
|
}
|
|
58
|
-
return { kind: "deny", mode: "enforce" };
|
|
76
|
+
return { kind: "deny", mode: "enforce", spanAttributes };
|
|
59
77
|
}
|
|
60
78
|
/**
|
|
61
79
|
* Run a permission check and resolve the configured mode against the
|
|
@@ -64,6 +82,7 @@ function applyPermissionMode(client, allowed, context = {}) {
|
|
|
64
82
|
*/
|
|
65
83
|
async function checkAndDecide(client, check, opts = {}) {
|
|
66
84
|
const mode = opts.modeOverride ?? (0, config_js_1.resolveConfig)().permissionMode;
|
|
85
|
+
const spanAttributes = buildDecisionAttributes(mode, check.subjectId, check.subjectSet);
|
|
67
86
|
let result;
|
|
68
87
|
try {
|
|
69
88
|
result = await client.checkPermission(check, {
|
|
@@ -71,19 +90,20 @@ async function checkAndDecide(client, check, opts = {}) {
|
|
|
71
90
|
});
|
|
72
91
|
}
|
|
73
92
|
catch (err) {
|
|
74
|
-
return { kind: "fail_open", error: err, mode };
|
|
93
|
+
return { kind: "fail_open", error: err, mode, spanAttributes };
|
|
75
94
|
}
|
|
76
95
|
const inner = applyPermissionMode(client, result.allowed, {
|
|
77
96
|
namespace: check.namespace,
|
|
78
97
|
object: check.object,
|
|
79
98
|
relation: check.relation,
|
|
80
99
|
subjectId: check.subjectId,
|
|
100
|
+
subjectSet: check.subjectSet,
|
|
81
101
|
spanAttributes: opts.spanAttributes,
|
|
82
102
|
modeOverride: opts.modeOverride,
|
|
83
103
|
});
|
|
84
104
|
if (inner.kind === "allow")
|
|
85
|
-
return { kind: "allow", result, mode: inner.mode };
|
|
105
|
+
return { kind: "allow", result, mode: inner.mode, spanAttributes: inner.spanAttributes };
|
|
86
106
|
if (inner.kind === "observe")
|
|
87
|
-
return { kind: "observe", result, mode: "observe" };
|
|
88
|
-
return { kind: "deny", result, mode: "enforce" };
|
|
107
|
+
return { kind: "observe", result, mode: "observe", spanAttributes: inner.spanAttributes };
|
|
108
|
+
return { kind: "deny", result, mode: "enforce", spanAttributes: inner.spanAttributes };
|
|
89
109
|
}
|
package/dist/tracer.d.ts
CHANGED
|
@@ -99,6 +99,14 @@ export interface ProcessMetadata {
|
|
|
99
99
|
gitBranch?: string;
|
|
100
100
|
gitCommit?: string;
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Returns a record of attributes to merge into every span's attribute
|
|
104
|
+
* bag at span-end. Used by `OryAgentClient` to inject the current
|
|
105
|
+
* `userSubject` / `agentSubject` onto every span without each handler
|
|
106
|
+
* having to remember to attach them. Must not throw and should be cheap;
|
|
107
|
+
* it is invoked on every span.
|
|
108
|
+
*/
|
|
109
|
+
export type SpanAttributeEnricher = () => Record<string, unknown> | undefined;
|
|
102
110
|
export declare class Tracer extends EventEmitter {
|
|
103
111
|
readonly harness: string;
|
|
104
112
|
readonly processMetadata: ProcessMetadata;
|
|
@@ -106,6 +114,7 @@ export declare class Tracer extends EventEmitter {
|
|
|
106
114
|
private readonly _spans;
|
|
107
115
|
private _context;
|
|
108
116
|
private _exporter;
|
|
117
|
+
private _enricher;
|
|
109
118
|
constructor(opts: TracerOptions);
|
|
110
119
|
/** Backwards-compatible accessors for the most common metadata fields. */
|
|
111
120
|
get hostname(): string;
|
|
@@ -135,6 +144,15 @@ export declare class Tracer extends EventEmitter {
|
|
|
135
144
|
*/
|
|
136
145
|
setExporter(exporter: SpanExporter | null): void;
|
|
137
146
|
get exporter(): SpanExporter | null;
|
|
147
|
+
/**
|
|
148
|
+
* Install (or replace) an attribute enricher. The enricher is invoked
|
|
149
|
+
* on every span at end-time and its return value is merged into the
|
|
150
|
+
* span's attributes (with caller-supplied attributes taking precedence).
|
|
151
|
+
* Pass `null` to clear.
|
|
152
|
+
*/
|
|
153
|
+
setAttributeEnricher(enricher: SpanAttributeEnricher | null): void;
|
|
154
|
+
/** @internal — invoked by `ActiveSpan.end()`. Never throws. */
|
|
155
|
+
_callEnricher(): Record<string, unknown> | undefined;
|
|
138
156
|
/**
|
|
139
157
|
* Flush any pending exports. Call before the host process exits so
|
|
140
158
|
* the OTLP HTTP request has a chance to complete.
|
package/dist/tracer.js
CHANGED
|
@@ -98,8 +98,12 @@ class ActiveSpan {
|
|
|
98
98
|
}
|
|
99
99
|
this._ended = true;
|
|
100
100
|
const durationMs = Math.round(performance.now() - this.startMs);
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
// Pull enricher attributes (e.g. userSubject / agentSubject from the
|
|
102
|
+
// client's principals) at span-end so every span gets them, including
|
|
103
|
+
// direct `tracer.record(...)` calls from plugin handlers.
|
|
104
|
+
const enriched = this.tracer._callEnricher();
|
|
105
|
+
const merged = enriched || this.baseAttributes || attributes
|
|
106
|
+
? { ...enriched, ...this.baseAttributes, ...attributes }
|
|
103
107
|
: undefined;
|
|
104
108
|
const meta = this.tracer.processMetadata;
|
|
105
109
|
const span = {
|
|
@@ -137,6 +141,7 @@ class Tracer extends node_events_1.EventEmitter {
|
|
|
137
141
|
_spans = [];
|
|
138
142
|
_context = {};
|
|
139
143
|
_exporter;
|
|
144
|
+
_enricher = null;
|
|
140
145
|
constructor(opts) {
|
|
141
146
|
super();
|
|
142
147
|
this.harness = opts.harness;
|
|
@@ -212,6 +217,30 @@ class Tracer extends node_events_1.EventEmitter {
|
|
|
212
217
|
get exporter() {
|
|
213
218
|
return this._exporter;
|
|
214
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Install (or replace) an attribute enricher. The enricher is invoked
|
|
222
|
+
* on every span at end-time and its return value is merged into the
|
|
223
|
+
* span's attributes (with caller-supplied attributes taking precedence).
|
|
224
|
+
* Pass `null` to clear.
|
|
225
|
+
*/
|
|
226
|
+
setAttributeEnricher(enricher) {
|
|
227
|
+
this._enricher = enricher;
|
|
228
|
+
}
|
|
229
|
+
/** @internal — invoked by `ActiveSpan.end()`. Never throws. */
|
|
230
|
+
_callEnricher() {
|
|
231
|
+
if (!this._enricher)
|
|
232
|
+
return undefined;
|
|
233
|
+
try {
|
|
234
|
+
const out = this._enricher();
|
|
235
|
+
if (!out || Object.keys(out).length === 0)
|
|
236
|
+
return undefined;
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Enricher contract: never throw. Swallow defensively.
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
215
244
|
/**
|
|
216
245
|
* Flush any pending exports. Call before the host process exits so
|
|
217
246
|
* the OTLP HTTP request has a chance to complete.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/argus",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|