@ory/argus 0.14.0 → 1.0.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 +31 -46
- package/assets/commands/temporal-up.md +1 -1
- package/assets/skills/auth-setup/SKILL.md +1 -1
- package/assets/skills/local-dev/SKILL.md +17 -7
- package/assets/skills/ory-build-agent/SKILL.md +39 -91
- package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -17
- package/assets/skills/ory-temporal-worker/SKILL.md +31 -33
- package/assets/skills/permissions-onboarding/SKILL.md +131 -104
- package/dist/adapters.d.ts +88 -32
- package/dist/adapters.js +443 -164
- package/dist/agent-auth.d.ts +226 -67
- package/dist/agent-auth.js +951 -205
- package/dist/auth-store.d.ts +37 -2
- package/dist/auth-store.js +37 -3
- package/dist/auth.d.ts +33 -4
- package/dist/auth.js +163 -20
- package/dist/bash-parser.d.ts +98 -0
- package/dist/bash-parser.js +396 -0
- package/dist/branding.d.ts +77 -16
- package/dist/branding.js +93 -23
- package/dist/build-info.json +4 -4
- package/dist/cli-invocation.d.ts +1 -1
- package/dist/cli-invocation.js +2 -1
- package/dist/cli.d.ts +20 -29
- package/dist/cli.js +271 -239
- package/dist/client.d.ts +175 -138
- package/dist/client.js +672 -391
- package/dist/config.d.ts +217 -54
- package/dist/config.js +461 -42
- package/dist/context.d.ts +10 -0
- package/dist/context.js +21 -0
- package/dist/contract-suite.d.ts +3 -5
- package/dist/contract-suite.js +75 -47
- package/dist/denial.d.ts +36 -3
- package/dist/denial.js +79 -10
- package/dist/event-reporter.d.ts +77 -0
- package/dist/event-reporter.js +776 -0
- package/dist/external-registrations-main.d.ts +10 -0
- package/dist/external-registrations-main.js +38 -0
- package/dist/external-registrations.d.ts +79 -0
- package/dist/external-registrations.js +188 -0
- package/dist/help-cli.d.ts +39 -0
- package/dist/help-cli.js +55 -0
- package/dist/hook-timeout.d.ts +64 -0
- package/dist/hook-timeout.js +88 -0
- package/dist/index.d.ts +28 -23
- package/dist/index.js +173 -58
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +38 -6
- package/dist/local/cli.js +11 -6
- package/dist/local/configs.d.ts +74 -18
- package/dist/local/configs.js +291 -84
- package/dist/local/health.js +7 -7
- package/dist/local/index.d.ts +2 -2
- package/dist/local/index.js +24 -10
- package/dist/local/manager.d.ts +20 -1
- package/dist/local/manager.js +159 -36
- package/dist/local/ports.d.ts +158 -0
- package/dist/local/ports.js +443 -0
- package/dist/local/seed.d.ts +13 -5
- package/dist/local/seed.js +62 -38
- package/dist/logger.d.ts +54 -25
- package/dist/logger.js +329 -63
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +10 -5
- package/dist/mirror-bootstrap.d.ts +48 -0
- package/dist/mirror-bootstrap.js +254 -0
- package/dist/opl.d.ts +289 -0
- package/dist/opl.js +446 -0
- package/dist/permission-mode.d.ts +87 -0
- package/dist/permission-mode.js +307 -0
- package/dist/permissions-cli.d.ts +13 -49
- package/dist/permissions-cli.js +154 -348
- package/dist/permissions.d.ts +148 -38
- package/dist/permissions.js +591 -45
- package/dist/post-install.d.ts +33 -0
- package/dist/post-install.js +127 -0
- package/dist/read-credential.d.ts +65 -0
- package/dist/read-credential.js +86 -0
- package/dist/registry/cli.js +5 -2
- package/dist/registry/config.d.ts +0 -17
- package/dist/registry/config.js +0 -23
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.js +2 -2
- package/dist/registry/manager.d.ts +4 -21
- package/dist/registry/manager.js +21 -37
- package/dist/runtime-credential.d.ts +140 -0
- package/dist/runtime-credential.js +572 -0
- package/dist/runtime.d.ts +408 -0
- package/dist/runtime.js +748 -0
- package/dist/setup.d.ts +23 -47
- package/dist/setup.js +59 -116
- package/dist/skills.js +0 -7
- package/dist/status-cli.d.ts +27 -11
- package/dist/status-cli.js +121 -115
- package/dist/status-data.d.ts +116 -17
- package/dist/status-data.js +121 -38
- package/dist/subject.d.ts +126 -20
- package/dist/subject.js +215 -30
- package/dist/testing.d.ts +74 -38
- package/dist/testing.js +185 -68
- package/dist/tool-catalog.d.ts +53 -11
- package/dist/tool-catalog.js +164 -13
- package/dist/tool-metadata.d.ts +7 -6
- package/dist/tool-metadata.js +6 -5
- package/dist/types.d.ts +11 -1
- package/dist/uninstall.d.ts +71 -34
- package/dist/uninstall.js +217 -85
- package/dist/user-login.d.ts +9 -10
- package/dist/user-login.js +56 -75
- package/dist/watch-cli.d.ts +6 -0
- package/dist/watch-cli.js +217 -0
- package/package.json +4 -27
- package/assets/commands/dashboard.md +0 -34
- package/dist/dashboard-cli.d.ts +0 -8
- package/dist/dashboard-cli.js +0 -70
- package/dist/dev.d.ts +0 -103
- package/dist/dev.js +0 -583
- package/dist/interactive-setup.d.ts +0 -286
- package/dist/interactive-setup.js +0 -1734
- package/dist/local/jaeger-main.d.ts +0 -13
- package/dist/local/jaeger-main.js +0 -85
- package/dist/local/jaeger.d.ts +0 -50
- package/dist/local/jaeger.js +0 -162
- package/dist/otel/exporter.d.ts +0 -17
- package/dist/otel/exporter.js +0 -12
- package/dist/otel/index.d.ts +0 -2
- package/dist/otel/index.js +0 -8
- package/dist/otel/otlp.d.ts +0 -103
- package/dist/otel/otlp.js +0 -385
- package/dist/project-api-key.d.ts +0 -69
- package/dist/project-api-key.js +0 -147
- package/dist/setup-actions.d.ts +0 -232
- package/dist/setup-actions.js +0 -507
- package/dist/tracer.d.ts +0 -190
- package/dist/tracer.js +0 -481
- package/dist/watch-sandbox.d.ts +0 -9
- package/dist/watch-sandbox.js +0 -81
- package/dist/web/api.d.ts +0 -33
- package/dist/web/api.js +0 -294
- package/dist/web/launch.d.ts +0 -11
- package/dist/web/launch.js +0 -96
- package/dist/web/server.d.ts +0 -20
- package/dist/web/server.js +0 -233
- package/dist/web/types.d.ts +0 -65
- package/dist/web/types.js +0 -2
- package/dist/webapp/assets/index-Wucl4SZs.css +0 -1
- package/dist/webapp/assets/index-m-GtEdq0.js +0 -49
- package/dist/webapp/favicon.ico +0 -0
- package/dist/webapp/index.html +0 -15
package/dist/logger.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
+
export type ActivityStatus = "ok" | "error" | "denied" | "skipped";
|
|
2
3
|
export interface LogEntry {
|
|
3
4
|
timestamp: string;
|
|
4
5
|
level: LogLevel;
|
|
@@ -6,45 +7,73 @@ export interface LogEntry {
|
|
|
6
7
|
harness: string;
|
|
7
8
|
data?: Record<string, unknown>;
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
export interface ActivityEntry {
|
|
11
|
+
timestamp: string;
|
|
12
|
+
level: "activity";
|
|
13
|
+
event: string;
|
|
14
|
+
harness: string;
|
|
15
|
+
status: ActivityStatus;
|
|
16
|
+
durationMs: number;
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
attributes?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
export type UnifiedLogEntry = LogEntry | ActivityEntry;
|
|
21
|
+
export type ActivityEnricher = () => Record<string, unknown> | undefined;
|
|
22
|
+
export type ActivitySink = (entry: ActivityEntry) => void;
|
|
13
23
|
export declare function redactLogData(value: unknown): unknown;
|
|
24
|
+
export declare function activityValueHash(value: string): string;
|
|
25
|
+
/** Convert arbitrary strings to correlation-safe summaries for always-on activity. */
|
|
26
|
+
export declare function sanitizeActivityAttributes(attributes: Record<string, unknown>): Record<string, unknown>;
|
|
27
|
+
export declare class ActiveActivity {
|
|
28
|
+
private readonly logger;
|
|
29
|
+
private readonly event;
|
|
30
|
+
private readonly baseAttributes?;
|
|
31
|
+
private readonly sessionId?;
|
|
32
|
+
private readonly startedAt;
|
|
33
|
+
private readonly timestamp;
|
|
34
|
+
private endedEntry;
|
|
35
|
+
constructor(logger: DebugLogger, event: string, baseAttributes?: Record<string, unknown> | undefined, sessionId?: string | undefined);
|
|
36
|
+
get ended(): boolean;
|
|
37
|
+
end(status: ActivityStatus, attributes?: Record<string, unknown>): ActivityEntry;
|
|
38
|
+
}
|
|
14
39
|
export declare class DebugLogger {
|
|
15
|
-
/**
|
|
16
|
-
* Process-global switch to suppress the stderr firehose while still writing
|
|
17
|
-
* to the log file. The interactive installer sets this so structured debug
|
|
18
|
-
* JSON (`[ory-agent] {...}`) doesn't interleave with the wizard's
|
|
19
|
-
* human-facing UI when a session runs with `ORY_AGENT_DEBUG=true` (the dev
|
|
20
|
-
* launcher, for one, always sets it). File logging — the real observability
|
|
21
|
-
* channel — is unaffected.
|
|
22
|
-
*/
|
|
23
40
|
static stderrSuppressed: boolean;
|
|
24
41
|
enabled: boolean;
|
|
25
|
-
harness: string;
|
|
26
|
-
logFile: string | null;
|
|
42
|
+
readonly harness: string;
|
|
43
|
+
readonly logFile: string | null;
|
|
44
|
+
private readonly activityEntries;
|
|
45
|
+
private activityEnricher;
|
|
46
|
+
private activitySink;
|
|
27
47
|
constructor(harness: string, opts?: {
|
|
28
48
|
logFile?: string;
|
|
29
49
|
});
|
|
30
50
|
get isEnabled(): boolean;
|
|
31
51
|
enable(): void;
|
|
32
52
|
disable(): void;
|
|
53
|
+
setActivityEnricher(enricher: ActivityEnricher | null): void;
|
|
54
|
+
setActivitySink(sink: ActivitySink | null): void;
|
|
55
|
+
activities(): ReadonlyArray<ActivityEntry>;
|
|
56
|
+
startActivity(event: string, attributes?: Record<string, unknown>, sessionId?: string): ActiveActivity;
|
|
57
|
+
activity(event: string, status: ActivityStatus, options?: {
|
|
58
|
+
attributes?: Record<string, unknown>;
|
|
59
|
+
durationMs?: number;
|
|
60
|
+
sessionId?: string;
|
|
61
|
+
timestamp?: string;
|
|
62
|
+
}): ActivityEntry;
|
|
33
63
|
log(level: LogLevel, event: string, data?: Record<string, unknown>): void;
|
|
34
64
|
debug(event: string, data?: Record<string, unknown>): void;
|
|
35
65
|
info(event: string, data?: Record<string, unknown>): void;
|
|
36
66
|
warn(event: string, data?: Record<string, unknown>): void;
|
|
37
67
|
error(event: string, data?: Record<string, unknown>): void;
|
|
68
|
+
private writeToStderr;
|
|
69
|
+
private writeToFile;
|
|
70
|
+
}
|
|
71
|
+
export interface FormattedLogEntryParts {
|
|
72
|
+
time: string;
|
|
73
|
+
event: string;
|
|
74
|
+
marker: string;
|
|
75
|
+
details: string[];
|
|
38
76
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* previous state afterward (even on throw). File logging is unaffected.
|
|
42
|
-
*
|
|
43
|
-
* Human-facing CLI commands (the interactive installer, `status`,
|
|
44
|
-
* `permissions status`/`bootstrap`) spin up a client and emit debug/info
|
|
45
|
-
* events; when the process runs with `ORY_AGENT_DEBUG=true` (the dev launcher
|
|
46
|
-
* always sets it) those `[ory-agent] {...}` lines would interleave with the
|
|
47
|
-
* command's polished output. Wrapping the command body in this keeps the
|
|
48
|
-
* terminal clean while the log file still captures everything.
|
|
49
|
-
*/
|
|
77
|
+
export declare function formatLogEntryParts(entry: UnifiedLogEntry): FormattedLogEntryParts;
|
|
78
|
+
export declare function formatLogEntry(entry: UnifiedLogEntry): string;
|
|
50
79
|
export declare function withQuietStderr<T>(fn: () => Promise<T> | T): Promise<T>;
|
package/dist/logger.js
CHANGED
|
@@ -33,15 +33,67 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.DebugLogger = void 0;
|
|
36
|
+
exports.DebugLogger = exports.ActiveActivity = void 0;
|
|
37
37
|
exports.redactLogData = redactLogData;
|
|
38
|
+
exports.activityValueHash = activityValueHash;
|
|
39
|
+
exports.sanitizeActivityAttributes = sanitizeActivityAttributes;
|
|
40
|
+
exports.formatLogEntryParts = formatLogEntryParts;
|
|
41
|
+
exports.formatLogEntry = formatLogEntry;
|
|
38
42
|
exports.withQuietStderr = withQuietStderr;
|
|
39
43
|
const fs = __importStar(require("node:fs"));
|
|
40
44
|
const path = __importStar(require("node:path"));
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
const node_crypto_1 = require("node:crypto");
|
|
46
|
+
const context_js_1 = require("./context.js");
|
|
47
|
+
const MAX_BUFFERED_ACTIVITIES = 1_000;
|
|
48
|
+
const ACTIVITY_HASH_LENGTH = 16;
|
|
49
|
+
// These values are bounded vocabularies or names needed to interpret an event.
|
|
50
|
+
// Every other string is summarized before it reaches the always-on activity sink.
|
|
51
|
+
const SAFE_ACTIVITY_STRING_KEYS = new Set([
|
|
52
|
+
"alertKind",
|
|
53
|
+
"blockReason",
|
|
54
|
+
"blockedPrincipalLevel",
|
|
55
|
+
"checkRejected",
|
|
56
|
+
"composerMode",
|
|
57
|
+
"delegation",
|
|
58
|
+
"delegationKind",
|
|
59
|
+
"decision",
|
|
60
|
+
"event",
|
|
61
|
+
"failureType",
|
|
62
|
+
"harness",
|
|
63
|
+
"kind",
|
|
64
|
+
"mcpServer",
|
|
65
|
+
"mcpTool",
|
|
66
|
+
"mode",
|
|
67
|
+
"model",
|
|
68
|
+
"namespace",
|
|
69
|
+
"notificationType",
|
|
70
|
+
"object",
|
|
71
|
+
"permissionMode",
|
|
72
|
+
"postureSource",
|
|
73
|
+
"relation",
|
|
74
|
+
"reasonCode",
|
|
75
|
+
"revoke",
|
|
76
|
+
"scope",
|
|
77
|
+
"severity",
|
|
78
|
+
"source",
|
|
79
|
+
"stage",
|
|
80
|
+
"status",
|
|
81
|
+
"subAgentType",
|
|
82
|
+
"toolName",
|
|
83
|
+
"trigger",
|
|
84
|
+
]);
|
|
85
|
+
const SAFE_ACTIVITY_STRING_ARRAY_KEYS = new Set(["missing", "shellDeniedWords", "shellWords"]);
|
|
86
|
+
const SAFE_ACTIVITY_ID_KEYS = new Set(["oryRequestId"]);
|
|
87
|
+
const SAFE_ACTIVITY_ERROR_CODES = new Set([
|
|
88
|
+
"forbidden",
|
|
89
|
+
"network_error",
|
|
90
|
+
"not_found",
|
|
91
|
+
"rate_limited",
|
|
92
|
+
"session_aal2_required",
|
|
93
|
+
"session_inactive",
|
|
94
|
+
"tls_error",
|
|
95
|
+
"unknown",
|
|
96
|
+
]);
|
|
45
97
|
const REDACTED_KEYS = new Set([
|
|
46
98
|
"accesstoken",
|
|
47
99
|
"access_token",
|
|
@@ -51,50 +103,119 @@ const REDACTED_KEYS = new Set([
|
|
|
51
103
|
"id_token",
|
|
52
104
|
"sessiontoken",
|
|
53
105
|
"session_token",
|
|
106
|
+
"token",
|
|
54
107
|
"code_verifier",
|
|
55
108
|
"codeverifier",
|
|
56
109
|
"client_secret",
|
|
57
110
|
"clientsecret",
|
|
58
111
|
"authorization",
|
|
112
|
+
"cookie",
|
|
59
113
|
"password",
|
|
60
114
|
"apikey",
|
|
61
115
|
"api_key",
|
|
62
116
|
]);
|
|
63
|
-
/**
|
|
64
|
-
* Recursively redact sensitive values from a structured log payload.
|
|
65
|
-
* The original object is not modified.
|
|
66
|
-
*/
|
|
67
117
|
function redactLogData(value) {
|
|
68
|
-
if (Array.isArray(value))
|
|
118
|
+
if (Array.isArray(value))
|
|
69
119
|
return value.map(redactLogData);
|
|
70
|
-
}
|
|
71
120
|
if (value && typeof value === "object") {
|
|
72
121
|
const out = {};
|
|
73
|
-
for (const [
|
|
74
|
-
|
|
75
|
-
|
|
122
|
+
for (const [key, item] of Object.entries(value)) {
|
|
123
|
+
out[key] = REDACTED_KEYS.has(key.toLowerCase()) && item !== undefined && item !== null
|
|
124
|
+
? "[redacted]"
|
|
125
|
+
: redactLogData(item);
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
function activityValueHash(value) {
|
|
132
|
+
return (0, node_crypto_1.createHash)("sha256").update(value).digest("hex").slice(0, ACTIVITY_HASH_LENGTH);
|
|
133
|
+
}
|
|
134
|
+
/** Convert arbitrary strings to correlation-safe summaries for always-on activity. */
|
|
135
|
+
function sanitizeActivityAttributes(attributes) {
|
|
136
|
+
const out = {};
|
|
137
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
138
|
+
if (typeof value === "string") {
|
|
139
|
+
if (value === "[redacted]"
|
|
140
|
+
|| SAFE_ACTIVITY_STRING_KEYS.has(key)
|
|
141
|
+
|| SAFE_ACTIVITY_ID_KEYS.has(key)
|
|
142
|
+
|| (key === "error" && SAFE_ACTIVITY_ERROR_CODES.has(value))
|
|
143
|
+
|| (key.endsWith("Hash") && /^[a-f0-9]{16,64}$/i.test(value))) {
|
|
144
|
+
out[key] = value;
|
|
76
145
|
}
|
|
77
146
|
else {
|
|
78
|
-
out[
|
|
147
|
+
out[`${key}Bytes`] = Buffer.byteLength(value, "utf8");
|
|
148
|
+
out[`${key}Hash`] = activityValueHash(value);
|
|
79
149
|
}
|
|
150
|
+
continue;
|
|
80
151
|
}
|
|
81
|
-
|
|
152
|
+
if (Array.isArray(value)) {
|
|
153
|
+
if (value.every((item) => typeof item === "string")) {
|
|
154
|
+
if (SAFE_ACTIVITY_STRING_ARRAY_KEYS.has(key)) {
|
|
155
|
+
out[key] = value;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
out[`${key}Count`] = value.length;
|
|
159
|
+
out[`${key}Hashes`] = value.map(activityValueHash);
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
out[key] = value.map((item) => {
|
|
164
|
+
if (typeof item === "string")
|
|
165
|
+
return activityValueHash(item);
|
|
166
|
+
if (item && typeof item === "object") {
|
|
167
|
+
return sanitizeActivityAttributes(item);
|
|
168
|
+
}
|
|
169
|
+
return item;
|
|
170
|
+
});
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
174
|
+
out[key] = sanitizeActivityAttributes(value);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
out[key] = value;
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
class ActiveActivity {
|
|
182
|
+
logger;
|
|
183
|
+
event;
|
|
184
|
+
baseAttributes;
|
|
185
|
+
sessionId;
|
|
186
|
+
startedAt = performance.now();
|
|
187
|
+
timestamp = new Date().toISOString();
|
|
188
|
+
endedEntry;
|
|
189
|
+
constructor(logger, event, baseAttributes, sessionId) {
|
|
190
|
+
this.logger = logger;
|
|
191
|
+
this.event = event;
|
|
192
|
+
this.baseAttributes = baseAttributes;
|
|
193
|
+
this.sessionId = sessionId;
|
|
194
|
+
}
|
|
195
|
+
get ended() {
|
|
196
|
+
return this.endedEntry !== undefined;
|
|
197
|
+
}
|
|
198
|
+
end(status, attributes) {
|
|
199
|
+
if (this.endedEntry)
|
|
200
|
+
return this.endedEntry;
|
|
201
|
+
this.endedEntry = this.logger.activity(this.event, status, {
|
|
202
|
+
durationMs: Math.round(performance.now() - this.startedAt),
|
|
203
|
+
timestamp: this.timestamp,
|
|
204
|
+
sessionId: this.sessionId,
|
|
205
|
+
attributes: { ...this.baseAttributes, ...attributes },
|
|
206
|
+
});
|
|
207
|
+
return this.endedEntry;
|
|
82
208
|
}
|
|
83
|
-
return value;
|
|
84
209
|
}
|
|
210
|
+
exports.ActiveActivity = ActiveActivity;
|
|
85
211
|
class DebugLogger {
|
|
86
|
-
/**
|
|
87
|
-
* Process-global switch to suppress the stderr firehose while still writing
|
|
88
|
-
* to the log file. The interactive installer sets this so structured debug
|
|
89
|
-
* JSON (`[ory-agent] {...}`) doesn't interleave with the wizard's
|
|
90
|
-
* human-facing UI when a session runs with `ORY_AGENT_DEBUG=true` (the dev
|
|
91
|
-
* launcher, for one, always sets it). File logging — the real observability
|
|
92
|
-
* channel — is unaffected.
|
|
93
|
-
*/
|
|
94
212
|
static stderrSuppressed = false;
|
|
95
213
|
enabled;
|
|
96
214
|
harness;
|
|
97
215
|
logFile;
|
|
216
|
+
activityEntries = [];
|
|
217
|
+
activityEnricher = null;
|
|
218
|
+
activitySink = null;
|
|
98
219
|
constructor(harness, opts) {
|
|
99
220
|
this.enabled = process.env.ORY_AGENT_DEBUG === "true";
|
|
100
221
|
this.harness = harness;
|
|
@@ -109,12 +230,60 @@ class DebugLogger {
|
|
|
109
230
|
disable() {
|
|
110
231
|
this.enabled = false;
|
|
111
232
|
}
|
|
233
|
+
setActivityEnricher(enricher) {
|
|
234
|
+
this.activityEnricher = enricher;
|
|
235
|
+
}
|
|
236
|
+
setActivitySink(sink) {
|
|
237
|
+
this.activitySink = sink;
|
|
238
|
+
}
|
|
239
|
+
activities() {
|
|
240
|
+
return this.activityEntries;
|
|
241
|
+
}
|
|
242
|
+
startActivity(event, attributes, sessionId) {
|
|
243
|
+
return new ActiveActivity(this, event, attributes, sessionId);
|
|
244
|
+
}
|
|
245
|
+
activity(event, status, options = {}) {
|
|
246
|
+
let enriched;
|
|
247
|
+
try {
|
|
248
|
+
enriched = this.activityEnricher?.();
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
enriched = undefined;
|
|
252
|
+
}
|
|
253
|
+
const merged = { ...enriched, ...options.attributes };
|
|
254
|
+
const safeAttributes = Object.keys(merged).length > 0
|
|
255
|
+
? sanitizeActivityAttributes(redactLogData(merged))
|
|
256
|
+
: undefined;
|
|
257
|
+
const sessionId = options.sessionId ?? (0, context_js_1.currentSessionContext)().sessionId;
|
|
258
|
+
const entry = {
|
|
259
|
+
timestamp: options.timestamp ?? new Date().toISOString(),
|
|
260
|
+
level: "activity",
|
|
261
|
+
event,
|
|
262
|
+
harness: this.harness,
|
|
263
|
+
status,
|
|
264
|
+
durationMs: options.durationMs ?? 0,
|
|
265
|
+
...(sessionId ? { sessionId } : {}),
|
|
266
|
+
...(safeAttributes ? { attributes: safeAttributes } : {}),
|
|
267
|
+
};
|
|
268
|
+
this.activityEntries.push(entry);
|
|
269
|
+
if (this.activityEntries.length > MAX_BUFFERED_ACTIVITIES) {
|
|
270
|
+
this.activityEntries.splice(0, this.activityEntries.length - MAX_BUFFERED_ACTIVITIES);
|
|
271
|
+
}
|
|
272
|
+
if (this.enabled)
|
|
273
|
+
this.writeToStderr(entry);
|
|
274
|
+
this.writeToFile(entry);
|
|
275
|
+
try {
|
|
276
|
+
this.activitySink?.(entry);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// Remote reporting must never affect local activity or a hook decision.
|
|
280
|
+
}
|
|
281
|
+
return entry;
|
|
282
|
+
}
|
|
112
283
|
log(level, event, data) {
|
|
113
284
|
if (!this.enabled)
|
|
114
285
|
return;
|
|
115
|
-
const safeData = data
|
|
116
|
-
? redactLogData(data)
|
|
117
|
-
: undefined;
|
|
286
|
+
const safeData = data ? redactLogData(data) : undefined;
|
|
118
287
|
const entry = {
|
|
119
288
|
timestamp: new Date().toISOString(),
|
|
120
289
|
level,
|
|
@@ -122,54 +291,151 @@ class DebugLogger {
|
|
|
122
291
|
harness: this.harness,
|
|
123
292
|
...(safeData ? { data: safeData } : {}),
|
|
124
293
|
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
294
|
+
this.writeToStderr(entry);
|
|
295
|
+
this.writeToFile(entry);
|
|
296
|
+
}
|
|
297
|
+
debug(event, data) { this.log("debug", event, data); }
|
|
298
|
+
info(event, data) { this.log("info", event, data); }
|
|
299
|
+
warn(event, data) { this.log("warn", event, data); }
|
|
300
|
+
error(event, data) { this.log("error", event, data); }
|
|
301
|
+
writeToStderr(entry) {
|
|
302
|
+
if (DebugLogger.stderrSuppressed)
|
|
303
|
+
return;
|
|
304
|
+
try {
|
|
305
|
+
process.stderr.write(`[ory-agent] ${JSON.stringify(entry)}\n`);
|
|
132
306
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (!fs.existsSync(dir)) {
|
|
136
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
137
|
-
}
|
|
138
|
-
fs.appendFileSync(this.logFile, line + "\n");
|
|
307
|
+
catch {
|
|
308
|
+
// Logging must never affect a hook decision.
|
|
139
309
|
}
|
|
140
310
|
}
|
|
141
|
-
|
|
142
|
-
this.
|
|
311
|
+
writeToFile(entry) {
|
|
312
|
+
if (!this.logFile)
|
|
313
|
+
return;
|
|
314
|
+
try {
|
|
315
|
+
fs.mkdirSync(path.dirname(this.logFile), { recursive: true });
|
|
316
|
+
fs.appendFileSync(this.logFile, JSON.stringify(entry) + "\n");
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
// Activity/audit persistence is best-effort and must never block a hook.
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
exports.DebugLogger = DebugLogger;
|
|
324
|
+
const STATUS_MARKERS = {
|
|
325
|
+
ok: "+",
|
|
326
|
+
error: "!",
|
|
327
|
+
denied: "x",
|
|
328
|
+
skipped: "-",
|
|
329
|
+
};
|
|
330
|
+
function displayValue(value) {
|
|
331
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
332
|
+
const text = String(value).replace(/\s+/g, " ");
|
|
333
|
+
return text.length > 240 ? `${text.slice(0, 237)}...` : text;
|
|
143
334
|
}
|
|
144
|
-
|
|
145
|
-
|
|
335
|
+
if (Array.isArray(value) && value.every((item) => typeof item === "string" || typeof item === "number" || typeof item === "boolean")) {
|
|
336
|
+
return displayValue(value.join(","));
|
|
146
337
|
}
|
|
147
|
-
|
|
148
|
-
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
function decisionFor(entry, values) {
|
|
341
|
+
const explicit = displayValue(values.decision);
|
|
342
|
+
if (explicit)
|
|
343
|
+
return explicit;
|
|
344
|
+
if (values.observed === true && values.allowed === false)
|
|
345
|
+
return "observe";
|
|
346
|
+
if (values.allowed === true)
|
|
347
|
+
return "allow";
|
|
348
|
+
if (values.allowed === false)
|
|
349
|
+
return "deny";
|
|
350
|
+
if (values.securityConnected === false)
|
|
351
|
+
return "not_connected";
|
|
352
|
+
if (entry.level === "activity" && entry.status === "error")
|
|
353
|
+
return "error";
|
|
354
|
+
if (entry.event.includes("fail_open"))
|
|
355
|
+
return "fail_open";
|
|
356
|
+
if (entry.event.endsWith(".failed") || entry.event.endsWith(".failure"))
|
|
357
|
+
return "error";
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
function formatEntryDetails(entry) {
|
|
361
|
+
const values = entry.level === "activity" ? entry.attributes ?? {} : entry.data ?? {};
|
|
362
|
+
const details = [];
|
|
363
|
+
const decision = decisionFor(entry, values);
|
|
364
|
+
const tool = displayValue(values.toolName ?? values.tool);
|
|
365
|
+
const mode = displayValue(values.permissionMode ?? values.mode);
|
|
366
|
+
const error = displayValue(values.error ?? values.code);
|
|
367
|
+
const message = displayValue(values.message);
|
|
368
|
+
if (entry.level === "activity")
|
|
369
|
+
details.push(`status=${entry.status}`);
|
|
370
|
+
if (decision)
|
|
371
|
+
details.push(`decision=${decision}`);
|
|
372
|
+
if (tool)
|
|
373
|
+
details.push(`tool=${tool}`);
|
|
374
|
+
for (const key of [
|
|
375
|
+
"namespace",
|
|
376
|
+
"object",
|
|
377
|
+
"relation",
|
|
378
|
+
"blockReason",
|
|
379
|
+
"reasonCode",
|
|
380
|
+
"reason",
|
|
381
|
+
"stage",
|
|
382
|
+
"kind",
|
|
383
|
+
"trigger",
|
|
384
|
+
]) {
|
|
385
|
+
const value = displayValue(values[key]);
|
|
386
|
+
if (value)
|
|
387
|
+
details.push(`${key}=${value}`);
|
|
149
388
|
}
|
|
150
|
-
|
|
151
|
-
|
|
389
|
+
if (mode)
|
|
390
|
+
details.push(`mode=${mode}`);
|
|
391
|
+
if (error)
|
|
392
|
+
details.push(`error=${error}`);
|
|
393
|
+
const status = entry.level !== "activity" ? displayValue(values.status) : undefined;
|
|
394
|
+
if (status)
|
|
395
|
+
details.push(`http=${status}`);
|
|
396
|
+
if (message)
|
|
397
|
+
details.push(`message=${message}`);
|
|
398
|
+
for (const key of ["count", "allowed", "denied", "tooComplex"]) {
|
|
399
|
+
if ((key === "allowed" || key === "denied") && typeof values[key] === "boolean")
|
|
400
|
+
continue;
|
|
401
|
+
const value = displayValue(values[key]);
|
|
402
|
+
if (value !== undefined)
|
|
403
|
+
details.push(`${key}=${value}`);
|
|
152
404
|
}
|
|
405
|
+
const shellWords = displayValue(values.shellWords ?? values.words);
|
|
406
|
+
if (shellWords)
|
|
407
|
+
details.push(`commands=${shellWords}`);
|
|
408
|
+
const deniedWords = displayValue(values.shellDeniedWords);
|
|
409
|
+
if (deniedWords)
|
|
410
|
+
details.push(`deniedCommands=${deniedWords}`);
|
|
411
|
+
const command = displayValue(values.command);
|
|
412
|
+
if (command)
|
|
413
|
+
details.push(`command=${command}`);
|
|
414
|
+
if (entry.level === "activity" && entry.durationMs > 0)
|
|
415
|
+
details.push(`duration=${entry.durationMs}ms`);
|
|
416
|
+
if (entry.level === "activity" && entry.sessionId)
|
|
417
|
+
details.push(`session=${entry.sessionId}`);
|
|
418
|
+
return details;
|
|
419
|
+
}
|
|
420
|
+
function formatLogEntryParts(entry) {
|
|
421
|
+
return {
|
|
422
|
+
time: entry.timestamp.slice(11, 23),
|
|
423
|
+
event: entry.event,
|
|
424
|
+
marker: entry.level === "activity" ? STATUS_MARKERS[entry.status] : `[${entry.level}]`,
|
|
425
|
+
details: formatEntryDetails(entry),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function formatLogEntry(entry) {
|
|
429
|
+
const { time, event, marker, details } = formatLogEntryParts(entry);
|
|
430
|
+
return `${time} ${event.padEnd(28)} ${marker}${details.length ? ` ${details.join(" ")}` : ""}`;
|
|
153
431
|
}
|
|
154
|
-
exports.DebugLogger = DebugLogger;
|
|
155
|
-
/**
|
|
156
|
-
* Run `fn` with the structured stderr firehose suppressed, restoring the
|
|
157
|
-
* previous state afterward (even on throw). File logging is unaffected.
|
|
158
|
-
*
|
|
159
|
-
* Human-facing CLI commands (the interactive installer, `status`,
|
|
160
|
-
* `permissions status`/`bootstrap`) spin up a client and emit debug/info
|
|
161
|
-
* events; when the process runs with `ORY_AGENT_DEBUG=true` (the dev launcher
|
|
162
|
-
* always sets it) those `[ory-agent] {...}` lines would interleave with the
|
|
163
|
-
* command's polished output. Wrapping the command body in this keeps the
|
|
164
|
-
* terminal clean while the log file still captures everything.
|
|
165
|
-
*/
|
|
166
432
|
async function withQuietStderr(fn) {
|
|
167
|
-
const
|
|
433
|
+
const previous = DebugLogger.stderrSuppressed;
|
|
168
434
|
DebugLogger.stderrSuppressed = true;
|
|
169
435
|
try {
|
|
170
436
|
return await fn();
|
|
171
437
|
}
|
|
172
438
|
finally {
|
|
173
|
-
DebugLogger.stderrSuppressed =
|
|
439
|
+
DebugLogger.stderrSuppressed = previous;
|
|
174
440
|
}
|
|
175
441
|
}
|
package/dist/mcp.d.ts
CHANGED
|
@@ -30,10 +30,10 @@ export interface McpPermissionCheckOptions {
|
|
|
30
30
|
checkToolLevel?: boolean;
|
|
31
31
|
/**
|
|
32
32
|
* Attributes merged into the permission.check / permission.batch_check
|
|
33
|
-
*
|
|
33
|
+
* activity events. Use this to carry the harness-side tool name and any
|
|
34
34
|
* other context the Zanzibar tuple shape doesn't capture.
|
|
35
35
|
*/
|
|
36
|
-
|
|
36
|
+
activityAttributes?: Record<string, unknown>;
|
|
37
37
|
}
|
|
38
38
|
export interface McpPermissionResult {
|
|
39
39
|
/** Whether the server-level check passed. */
|
package/dist/mcp.js
CHANGED
|
@@ -13,6 +13,7 @@ exports.parseClaudeCodeMcpTool = parseClaudeCodeMcpTool;
|
|
|
13
13
|
exports.parseGeminiMcpTool = parseGeminiMcpTool;
|
|
14
14
|
exports.parseMcpToolGeneric = parseMcpToolGeneric;
|
|
15
15
|
exports.checkMcpPermission = checkMcpPermission;
|
|
16
|
+
const read_credential_js_1 = require("./read-credential.js");
|
|
16
17
|
// ─── Parsers ───────────────────────────────────────────────────────
|
|
17
18
|
/**
|
|
18
19
|
* Parse a Claude Code MCP tool name of the form `mcp__<server>__<tool>`.
|
|
@@ -80,20 +81,24 @@ async function checkMcpPermission(client, mcpTool, options) {
|
|
|
80
81
|
(process.env.ORY_MCP_TOOL_NAMESPACE ?? "mcp_tools");
|
|
81
82
|
const checkToolLevel = options.checkToolLevel ??
|
|
82
83
|
process.env.ORY_MCP_TOOL_LEVEL_CHECKS === "true";
|
|
84
|
+
// MCP tool calls arrive on the same subprocess lifecycle events as any other
|
|
85
|
+
// tool, so this read needs a credential resolved for exactly the reasons
|
|
86
|
+
// `gateToolCall` does (#242).
|
|
87
|
+
await (0, read_credential_js_1.ensureReadCredential)(client);
|
|
83
88
|
const serverCheck = {
|
|
84
89
|
namespace: serverNamespace,
|
|
85
90
|
object: mcpTool.serverName,
|
|
86
|
-
relation: "
|
|
91
|
+
relation: "users",
|
|
87
92
|
...options.subject,
|
|
88
93
|
};
|
|
89
|
-
const
|
|
94
|
+
const activityAttributes = {
|
|
90
95
|
mcpServer: mcpTool.serverName,
|
|
91
96
|
...(mcpTool.toolName ? { mcpTool: mcpTool.toolName } : {}),
|
|
92
|
-
...options.
|
|
97
|
+
...options.activityAttributes,
|
|
93
98
|
};
|
|
94
99
|
if (!checkToolLevel || !mcpTool.toolName) {
|
|
95
100
|
// Server-only check
|
|
96
|
-
const result = await client.checkPermission(serverCheck, {
|
|
101
|
+
const result = await client.checkPermission(serverCheck, { activityAttributes });
|
|
97
102
|
return {
|
|
98
103
|
serverAllowed: result.allowed,
|
|
99
104
|
toolAllowed: true,
|
|
@@ -109,7 +114,7 @@ async function checkMcpPermission(client, mcpTool, options) {
|
|
|
109
114
|
relation: "invoke",
|
|
110
115
|
...options.subject,
|
|
111
116
|
};
|
|
112
|
-
const batchResult = await client.batchCheckPermissions([serverCheck, toolCheck], {
|
|
117
|
+
const batchResult = await client.batchCheckPermissions([serverCheck, toolCheck], { activityAttributes });
|
|
113
118
|
const serverAllowed = batchResult.results[0]?.allowed ?? false;
|
|
114
119
|
const toolAllowed = batchResult.results[1]?.allowed ?? false;
|
|
115
120
|
return {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bootstrap committed to the public install-surface mirrors.
|
|
3
|
+
*
|
|
4
|
+
* Three harnesses install from a git repo rather than from npm — Claude Code
|
|
5
|
+
* (plugin marketplace), Gemini CLI (`gemini extensions install <url>`) and
|
|
6
|
+
* Codex (`codex plugin marketplace add <url>`) — so those repos carry a
|
|
7
|
+
* checked-in `hooks.json` / manifest. A machine-specific shim path cannot go
|
|
8
|
+
* into a file a teammate will clone, and an `npx` command must not go back onto
|
|
9
|
+
* the tool-call path, so those surfaces instead reference a **bootstrap** that
|
|
10
|
+
* ships inside the plugin tree, addressed through the harness's own path
|
|
11
|
+
* variable (verified per harness — see `docs/distribution.md`).
|
|
12
|
+
*
|
|
13
|
+
* The bootstrap is deliberately **dependency-free**: the mirror repos have no
|
|
14
|
+
* `node_modules`, so it uses `node:` builtins only. It does three things:
|
|
15
|
+
*
|
|
16
|
+
* 1. Read the runtime manifest this machine's `install` wrote and `require`
|
|
17
|
+
* the wired entry. That is the steady state, and it costs one file read.
|
|
18
|
+
* 2. On a **session-start** event with nothing wired, complete the install
|
|
19
|
+
* once by shelling out to `npx <package> install`. This is what keeps
|
|
20
|
+
* `claude plugin install …` a working one-liner: the marketplace install
|
|
21
|
+
* is the install, and the runtime it needs is materialized on first use —
|
|
22
|
+
* never per hook invocation.
|
|
23
|
+
* 3. Fail **open** in every other unresolved case: print what to run, emit
|
|
24
|
+
* the harness's pass-through response, exit 0. A tool-call event never
|
|
25
|
+
* triggers a materialize, because a `PreToolUse` hook has seconds to
|
|
26
|
+
* answer and a harness that treats an unanswered gate as a block would
|
|
27
|
+
* otherwise block the tool while npm ran.
|
|
28
|
+
*/
|
|
29
|
+
/** Which entry point the generated bootstrap loads. */
|
|
30
|
+
export type MirrorBootstrapKind = "hook" | "mcp";
|
|
31
|
+
export interface MirrorBootstrapOptions {
|
|
32
|
+
/** Harness slug, e.g. `claude-code` — the manifest key to look up. */
|
|
33
|
+
harness: string;
|
|
34
|
+
/** npm package the install command names, e.g. `@ory/claude-code`. */
|
|
35
|
+
packageName: string;
|
|
36
|
+
/** CLI bin the install command runs, e.g. `ory-claude`. */
|
|
37
|
+
binName: string;
|
|
38
|
+
/** Harness pass-through payload for the runtime-missing case. */
|
|
39
|
+
fallbackStdout?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Event names (as they appear in the hook payload) that may trigger the
|
|
42
|
+
* one-time materialize. Defaults to the session-start spellings the
|
|
43
|
+
* harnesses use.
|
|
44
|
+
*/
|
|
45
|
+
sessionStartEvents?: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
/** Render the bootstrap script for one entry kind. */
|
|
48
|
+
export declare function renderMirrorBootstrap(kind: MirrorBootstrapKind, opts: MirrorBootstrapOptions): string;
|