@ory/argus 0.13.9 → 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 -45
- 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 +43 -97
- package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -18
- package/assets/skills/ory-temporal-worker/SKILL.md +38 -42
- package/assets/skills/permissions-onboarding/SKILL.md +131 -104
- package/dist/adapters.d.ts +93 -30
- package/dist/adapters.js +464 -136
- package/dist/agent-auth.d.ts +258 -68
- package/dist/agent-auth.js +998 -202
- package/dist/auth-store.d.ts +37 -2
- package/dist/auth-store.js +37 -3
- package/dist/auth.d.ts +40 -4
- package/dist/auth.js +247 -19
- package/dist/bash-parser.d.ts +98 -0
- package/dist/bash-parser.js +396 -0
- package/dist/branding.d.ts +128 -0
- package/dist/branding.js +151 -0
- 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 -278
- package/dist/client.d.ts +175 -138
- package/dist/client.js +672 -391
- package/dist/config.d.ts +249 -57
- package/dist/config.js +486 -62
- package/dist/context.d.ts +10 -0
- package/dist/context.js +21 -0
- package/dist/contract-suite.d.ts +8 -8
- package/dist/contract-suite.js +88 -69
- 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 +31 -19
- package/dist/index.js +182 -31
- 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.d.ts +14 -0
- package/dist/local/health.js +50 -4
- 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 +160 -39
- package/dist/local/ports.d.ts +158 -0
- package/dist/local/ports.js +443 -0
- package/dist/local/seed.d.ts +22 -25
- package/dist/local/seed.js +88 -56
- 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 +83 -55
- 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 -28
- package/dist/setup.js +57 -84
- package/dist/status-cli.d.ts +29 -13
- package/dist/status-cli.js +124 -144
- package/dist/status-data.d.ts +195 -0
- package/dist/status-data.js +333 -0
- package/dist/status-system.d.ts +24 -0
- package/dist/status-system.js +56 -0
- 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 +74 -19
- package/dist/uninstall.js +224 -49
- package/dist/user-login.d.ts +22 -16
- package/dist/user-login.js +67 -96
- package/dist/watch-cli.d.ts +6 -0
- package/dist/watch-cli.js +217 -0
- package/package.json +3 -11
- package/dist/dev.d.ts +0 -103
- package/dist/dev.js +0 -584
- package/dist/interactive-setup.d.ts +0 -165
- package/dist/interactive-setup.js +0 -1546
- 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/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
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AgentSecurityEventReporter = exports.SERVER_ACTIVITY_EVENTS = void 0;
|
|
37
|
+
exports.projectActivityEntry = projectActivityEntry;
|
|
38
|
+
exports.eventOutboxProjectScope = eventOutboxProjectScope;
|
|
39
|
+
exports.eventPrincipalKey = eventPrincipalKey;
|
|
40
|
+
const node_crypto_1 = require("node:crypto");
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
exports.SERVER_ACTIVITY_EVENTS = new Set([
|
|
44
|
+
"session.start", "session.end", "session.verify",
|
|
45
|
+
"tool.invoke", "tool.complete", "tool.block", "tool.fail",
|
|
46
|
+
"subagent.start", "subagent.stop",
|
|
47
|
+
"user.auth", "agent.auth", "agent.session.retire", "agent.retired.drain",
|
|
48
|
+
"oauth2.login", "oauth2.refresh", "oauth2.introspect",
|
|
49
|
+
"permission.mode_resolve", "permission.observe_deny", "permission.block_observed",
|
|
50
|
+
"bash.parser_unavailable", "user.interaction",
|
|
51
|
+
]);
|
|
52
|
+
const STRING_ATTRIBUTES = new Set([
|
|
53
|
+
"blockReason", "composerMode", "harness", "kind", "mcpServer", "mcpTool",
|
|
54
|
+
"mode", "model", "permissionMode", "source", "stage", "status",
|
|
55
|
+
"subAgentType", "toolName", "trigger", "alertKind", "severity",
|
|
56
|
+
"blockedPrincipalLevel", "failureType", "namespace", "relation", "reasonCode",
|
|
57
|
+
"scope", "postureSource", "object", "revoke", "delegation", "delegationKind",
|
|
58
|
+
]);
|
|
59
|
+
const HASH_ATTRIBUTES = new Set([
|
|
60
|
+
"bashCommandHash", "inputHash", "outputHash", "filePathHash", "urlHostHash",
|
|
61
|
+
"errorHash", "reasonHash", "blockedPrincipalHash", "subjectHash",
|
|
62
|
+
]);
|
|
63
|
+
const NUMBER_ATTRIBUTES = new Set([
|
|
64
|
+
"bashCommandBytes", "bashExitCode", "bashStderrBytes", "bashStdoutBytes", "count",
|
|
65
|
+
"durationMs", "inputBytes", "outputBytes", "responseLen", "shellCommandCount", "summaryLen",
|
|
66
|
+
"filePathBytes", "errorBytes", "reasonBytes", "executionTimeMs",
|
|
67
|
+
"blockedPrincipalBytes", "subjectBytes", "retired", "keep", "queued", "revoked",
|
|
68
|
+
"expiresAt", "warningsCount",
|
|
69
|
+
]);
|
|
70
|
+
const BOOL_ATTRIBUTES = new Set([
|
|
71
|
+
"active", "allowed", "hasOutput", "observed", "securityConnected", "shellTooComplex",
|
|
72
|
+
"alert", "blocked", "checkRejected", "hasError", "success",
|
|
73
|
+
"enforceGranted", "proceed", "interactive",
|
|
74
|
+
]);
|
|
75
|
+
const ARRAY_ATTRIBUTES = new Set(["shellWords", "shellDeniedWords", "warningsHashes"]);
|
|
76
|
+
const SAFE_STRING = /^[A-Za-z0-9][A-Za-z0-9._:/@-]{0,254}$/;
|
|
77
|
+
const HASH = /^[a-f0-9]{16}$/;
|
|
78
|
+
const MAX_EVENT_AGE_MS = 24 * 60 * 60 * 1_000;
|
|
79
|
+
const MAX_FUTURE_SKEW_MS = 5 * 60 * 1_000;
|
|
80
|
+
const MAX_NUMBER = 1e12;
|
|
81
|
+
const DEFAULT_QUEUE_SIZE = 1_000;
|
|
82
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
|
|
83
|
+
const DEFAULT_RETRY_BASE_MS = 250;
|
|
84
|
+
const DEFAULT_RETRY_MAX_MS = 30_000;
|
|
85
|
+
const LOCK_HEARTBEAT_MS = 15_000;
|
|
86
|
+
const LOCK_STALE_MS = 60_000;
|
|
87
|
+
const EVENT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
88
|
+
const ACTIVITY_STATUSES = new Set(["ok", "error", "denied", "skipped"]);
|
|
89
|
+
/** Best-effort delivery of privacy-safe client activity to Agent Security. */
|
|
90
|
+
class AgentSecurityEventReporter {
|
|
91
|
+
options;
|
|
92
|
+
queue = [];
|
|
93
|
+
fetcher;
|
|
94
|
+
maxQueueSize;
|
|
95
|
+
requestTimeoutMs;
|
|
96
|
+
now;
|
|
97
|
+
eventId;
|
|
98
|
+
delivery;
|
|
99
|
+
scheduled = false;
|
|
100
|
+
activeRequest;
|
|
101
|
+
flushDeadline;
|
|
102
|
+
suppressRetry = false;
|
|
103
|
+
retryTimer;
|
|
104
|
+
retryAttempt = 0;
|
|
105
|
+
retryBaseMs;
|
|
106
|
+
retryMaxMs;
|
|
107
|
+
random;
|
|
108
|
+
constructor(options) {
|
|
109
|
+
this.options = options;
|
|
110
|
+
this.fetcher = options.fetch ?? globalThis.fetch;
|
|
111
|
+
this.maxQueueSize = Math.max(1, Math.floor(options.maxQueueSize ?? DEFAULT_QUEUE_SIZE));
|
|
112
|
+
this.requestTimeoutMs = Math.max(1, options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
|
|
113
|
+
this.now = options.now ?? Date.now;
|
|
114
|
+
this.eventId = options.eventId ?? node_crypto_1.randomUUID;
|
|
115
|
+
this.retryBaseMs = Math.max(1, options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS);
|
|
116
|
+
this.retryMaxMs = Math.max(this.retryBaseMs, options.retryMaxMs ?? DEFAULT_RETRY_MAX_MS);
|
|
117
|
+
this.random = options.random ?? Math.random;
|
|
118
|
+
this.syncOutbox();
|
|
119
|
+
}
|
|
120
|
+
get pending() {
|
|
121
|
+
return this.queue.length;
|
|
122
|
+
}
|
|
123
|
+
get pendingScopes() {
|
|
124
|
+
const scopes = new Map();
|
|
125
|
+
for (const pending of this.queue) {
|
|
126
|
+
const scope = pendingScope(pending);
|
|
127
|
+
scopes.set(scopeKey(scope), scope);
|
|
128
|
+
}
|
|
129
|
+
return [...scopes.values()];
|
|
130
|
+
}
|
|
131
|
+
enqueue(entry, principalKey) {
|
|
132
|
+
try {
|
|
133
|
+
const event = projectActivityEntry(entry, this.eventId);
|
|
134
|
+
if (!event)
|
|
135
|
+
return;
|
|
136
|
+
const occurredAt = Date.parse(event.occurred_at);
|
|
137
|
+
if (!Number.isFinite(occurredAt) || occurredAt > this.now() + MAX_FUTURE_SKEW_MS)
|
|
138
|
+
return;
|
|
139
|
+
this.prune();
|
|
140
|
+
const pending = {
|
|
141
|
+
event,
|
|
142
|
+
...(principalKey ? { principalKey } : {}),
|
|
143
|
+
};
|
|
144
|
+
const file = this.persist(pending);
|
|
145
|
+
this.queue.push({ ...pending, file });
|
|
146
|
+
this.enforceBounds();
|
|
147
|
+
if (this.retryTimer) {
|
|
148
|
+
clearTimeout(this.retryTimer);
|
|
149
|
+
this.retryTimer = undefined;
|
|
150
|
+
}
|
|
151
|
+
this.schedule();
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
this.diagnostic("events.enqueue_failed", error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Attempt all currently deliverable batches, bounded by the caller's deadline. */
|
|
158
|
+
async flush(timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
159
|
+
if (timeoutMs <= 0)
|
|
160
|
+
return;
|
|
161
|
+
const deadline = Date.now() + timeoutMs;
|
|
162
|
+
this.flushDeadline = this.flushDeadline === undefined
|
|
163
|
+
? deadline
|
|
164
|
+
: Math.min(this.flushDeadline, deadline);
|
|
165
|
+
if (this.retryTimer) {
|
|
166
|
+
clearTimeout(this.retryTimer);
|
|
167
|
+
this.retryTimer = undefined;
|
|
168
|
+
}
|
|
169
|
+
const work = this.delivery ?? this.startDelivery();
|
|
170
|
+
let timer;
|
|
171
|
+
await Promise.race([
|
|
172
|
+
work,
|
|
173
|
+
new Promise((resolve) => {
|
|
174
|
+
timer = setTimeout(() => {
|
|
175
|
+
this.suppressRetry = true;
|
|
176
|
+
this.activeRequest?.abort();
|
|
177
|
+
resolve();
|
|
178
|
+
}, timeoutMs);
|
|
179
|
+
timer.unref?.();
|
|
180
|
+
}),
|
|
181
|
+
]);
|
|
182
|
+
if (timer)
|
|
183
|
+
clearTimeout(timer);
|
|
184
|
+
if (this.flushDeadline === deadline)
|
|
185
|
+
this.flushDeadline = undefined;
|
|
186
|
+
}
|
|
187
|
+
schedule() {
|
|
188
|
+
if (this.scheduled || this.delivery)
|
|
189
|
+
return;
|
|
190
|
+
this.scheduled = true;
|
|
191
|
+
queueMicrotask(() => {
|
|
192
|
+
this.scheduled = false;
|
|
193
|
+
if (!this.delivery)
|
|
194
|
+
this.startDelivery();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
startDelivery() {
|
|
198
|
+
let shouldRetry = false;
|
|
199
|
+
const work = this.drain().then((retry) => {
|
|
200
|
+
shouldRetry = retry;
|
|
201
|
+
}).catch((error) => {
|
|
202
|
+
shouldRetry = true;
|
|
203
|
+
this.diagnostic("events.delivery_failed", error);
|
|
204
|
+
});
|
|
205
|
+
this.delivery = work;
|
|
206
|
+
void work.finally(() => {
|
|
207
|
+
if (this.delivery === work)
|
|
208
|
+
this.delivery = undefined;
|
|
209
|
+
this.suppressRetry = false;
|
|
210
|
+
if (this.queue.length === 0)
|
|
211
|
+
this.retryAttempt = 0;
|
|
212
|
+
else if (shouldRetry)
|
|
213
|
+
this.scheduleRetry();
|
|
214
|
+
}).catch(() => undefined);
|
|
215
|
+
return work;
|
|
216
|
+
}
|
|
217
|
+
scheduleRetry() {
|
|
218
|
+
if (this.retryTimer || this.delivery || this.queue.length === 0)
|
|
219
|
+
return;
|
|
220
|
+
const ceiling = Math.min(this.retryMaxMs, this.retryBaseMs * 2 ** this.retryAttempt++);
|
|
221
|
+
const delay = Math.max(1, Math.floor(ceiling * (0.5 + this.random() * 0.5)));
|
|
222
|
+
this.retryTimer = setTimeout(() => {
|
|
223
|
+
this.retryTimer = undefined;
|
|
224
|
+
if (!this.delivery)
|
|
225
|
+
this.startDelivery();
|
|
226
|
+
}, delay);
|
|
227
|
+
this.retryTimer.unref?.();
|
|
228
|
+
}
|
|
229
|
+
async drain() {
|
|
230
|
+
const releaseDelivery = this.acquireOutboxLock("delivery");
|
|
231
|
+
if (this.options.outboxDir && !releaseDelivery)
|
|
232
|
+
return true;
|
|
233
|
+
let shouldRetry = false;
|
|
234
|
+
try {
|
|
235
|
+
this.prune();
|
|
236
|
+
const retainedScopes = new Set();
|
|
237
|
+
while (this.queue.length > 0) {
|
|
238
|
+
const first = this.queue.find((pending) => !retainedScopes.has(scopeKey(pendingScope(pending))));
|
|
239
|
+
if (!first)
|
|
240
|
+
return shouldRetry;
|
|
241
|
+
const scope = pendingScope(first);
|
|
242
|
+
const key = scopeKey(scope);
|
|
243
|
+
const batch = this.queue
|
|
244
|
+
.filter((pending) => scopeKey(pendingScope(pending)) === key)
|
|
245
|
+
.slice(0, 100);
|
|
246
|
+
const result = await this.deliver(batch.map(({ event }) => event), scope);
|
|
247
|
+
shouldRetry ||= result.retry;
|
|
248
|
+
if (result.remove.size === 0) {
|
|
249
|
+
retainedScopes.add(key);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
for (const pending of batch) {
|
|
253
|
+
if (result.remove.has(pending.event.event_id))
|
|
254
|
+
this.removeFile(pending.file);
|
|
255
|
+
}
|
|
256
|
+
for (let index = this.queue.length - 1; index >= 0; index--) {
|
|
257
|
+
if (result.remove.has(this.queue[index].event.event_id))
|
|
258
|
+
this.queue.splice(index, 1);
|
|
259
|
+
}
|
|
260
|
+
if (result.remove.size < batch.length)
|
|
261
|
+
retainedScopes.add(key);
|
|
262
|
+
}
|
|
263
|
+
return shouldRetry;
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
releaseDelivery?.();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async deliver(batch, scope) {
|
|
270
|
+
try {
|
|
271
|
+
const result = await this.request(batch, scope);
|
|
272
|
+
if (result === "accepted") {
|
|
273
|
+
return { remove: new Set(batch.map((event) => event.event_id)), retry: false };
|
|
274
|
+
}
|
|
275
|
+
if (result === "reject" && batch.length === 1) {
|
|
276
|
+
return { remove: new Set([batch[0].event_id]), retry: false };
|
|
277
|
+
}
|
|
278
|
+
if (result === "reject") {
|
|
279
|
+
const remove = new Set();
|
|
280
|
+
let retry = false;
|
|
281
|
+
for (const event of batch) {
|
|
282
|
+
const isolated = await this.deliver([event], scope);
|
|
283
|
+
for (const eventId of isolated.remove)
|
|
284
|
+
remove.add(eventId);
|
|
285
|
+
retry ||= isolated.retry;
|
|
286
|
+
}
|
|
287
|
+
return { remove, retry };
|
|
288
|
+
}
|
|
289
|
+
return { remove: new Set(), retry: result === "retain" || result === "retry" };
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
this.diagnostic("events.delivery_failed", error);
|
|
293
|
+
return { remove: new Set(), retry: true };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async request(batch, scope) {
|
|
297
|
+
const token = this.options.token?.(scope.sessionId, scope.principalKey);
|
|
298
|
+
const authenticatedRequest = this.options.request;
|
|
299
|
+
if (!token && !authenticatedRequest)
|
|
300
|
+
return "retain";
|
|
301
|
+
const controller = new AbortController();
|
|
302
|
+
this.activeRequest = controller;
|
|
303
|
+
const remaining = this.flushDeadline === undefined
|
|
304
|
+
? this.requestTimeoutMs
|
|
305
|
+
: Math.max(1, Math.min(this.requestTimeoutMs, this.flushDeadline - Date.now()));
|
|
306
|
+
const timeout = setTimeout(() => controller.abort(), remaining);
|
|
307
|
+
timeout.unref?.();
|
|
308
|
+
try {
|
|
309
|
+
const url = `${this.options.agentSecurityUrl.replace(/\/+$/, "")}/agent-security/v1/events:record`;
|
|
310
|
+
const init = {
|
|
311
|
+
method: "POST",
|
|
312
|
+
headers: {
|
|
313
|
+
"content-type": "application/json",
|
|
314
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
315
|
+
},
|
|
316
|
+
body: JSON.stringify({ project_url: this.options.projectUrl, events: batch }),
|
|
317
|
+
signal: controller.signal,
|
|
318
|
+
};
|
|
319
|
+
const authenticatedResponse = authenticatedRequest?.(url, init, scope.sessionId, scope.principalKey);
|
|
320
|
+
const response = authenticatedResponse
|
|
321
|
+
? await authenticatedResponse
|
|
322
|
+
: token
|
|
323
|
+
? await this.fetcher(url, init)
|
|
324
|
+
: undefined;
|
|
325
|
+
if (!response)
|
|
326
|
+
return "retain";
|
|
327
|
+
if (response.status === 400 || response.status === 409 || response.status === 415) {
|
|
328
|
+
this.diagnostic("events.batch_rejected", new Error(`HTTP ${response.status}`));
|
|
329
|
+
return "reject";
|
|
330
|
+
}
|
|
331
|
+
if (response.status === 401) {
|
|
332
|
+
try {
|
|
333
|
+
if (token)
|
|
334
|
+
this.options.onUnauthorized?.(scope.sessionId, scope.principalKey, token);
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
this.diagnostic("events.credential_invalidation_failed", error);
|
|
338
|
+
}
|
|
339
|
+
return "retain";
|
|
340
|
+
}
|
|
341
|
+
if (response.status === 429 || (response.status >= 500 && response.status <= 599)) {
|
|
342
|
+
return "retry";
|
|
343
|
+
}
|
|
344
|
+
if (response.status !== 202) {
|
|
345
|
+
this.diagnostic("events.delivery_failed", new Error(`HTTP ${response.status}`));
|
|
346
|
+
return "retain";
|
|
347
|
+
}
|
|
348
|
+
let body;
|
|
349
|
+
try {
|
|
350
|
+
body = await abortable(response.json(), controller.signal);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return "retain";
|
|
354
|
+
}
|
|
355
|
+
const ids = body?.event_ids;
|
|
356
|
+
if (!Array.isArray(ids)
|
|
357
|
+
|| ids.length !== batch.length
|
|
358
|
+
|| ids.some((id, index) => id !== batch[index].event_id)) {
|
|
359
|
+
this.diagnostic("events.invalid_response", new Error("response event IDs did not match request order"));
|
|
360
|
+
return "retain";
|
|
361
|
+
}
|
|
362
|
+
return "accepted";
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
clearTimeout(timeout);
|
|
366
|
+
if (this.activeRequest === controller)
|
|
367
|
+
this.activeRequest = undefined;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
persist(pending) {
|
|
371
|
+
const dir = this.options.outboxDir;
|
|
372
|
+
if (!dir || !EVENT_ID.test(pending.event.event_id))
|
|
373
|
+
return undefined;
|
|
374
|
+
const file = path.join(dir, `${pending.event.event_id}.json`);
|
|
375
|
+
const temporary = path.join(dir, `.${pending.event.event_id}.${process.pid}.${(0, node_crypto_1.randomUUID)()}.tmp`);
|
|
376
|
+
let descriptor;
|
|
377
|
+
try {
|
|
378
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
379
|
+
descriptor = fs.openSync(temporary, "wx", 0o600);
|
|
380
|
+
fs.writeFileSync(descriptor, JSON.stringify({
|
|
381
|
+
event: pending.event,
|
|
382
|
+
...(pending.principalKey ? { principal_key: pending.principalKey } : {}),
|
|
383
|
+
}), "utf8");
|
|
384
|
+
fs.fsyncSync(descriptor);
|
|
385
|
+
fs.closeSync(descriptor);
|
|
386
|
+
descriptor = undefined;
|
|
387
|
+
fs.renameSync(temporary, file);
|
|
388
|
+
fsyncDirectory(dir);
|
|
389
|
+
return file;
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
if (descriptor !== undefined) {
|
|
393
|
+
try {
|
|
394
|
+
fs.closeSync(descriptor);
|
|
395
|
+
}
|
|
396
|
+
catch { /* best effort */ }
|
|
397
|
+
}
|
|
398
|
+
this.removeFile(temporary);
|
|
399
|
+
this.diagnostic("events.outbox_write_failed", error);
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
syncOutbox() {
|
|
404
|
+
const dir = this.options.outboxDir;
|
|
405
|
+
if (!dir)
|
|
406
|
+
return;
|
|
407
|
+
const releaseMaintenance = this.acquireOutboxLock("maintenance");
|
|
408
|
+
if (!releaseMaintenance)
|
|
409
|
+
return;
|
|
410
|
+
try {
|
|
411
|
+
let names;
|
|
412
|
+
try {
|
|
413
|
+
names = fs.readdirSync(dir);
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
if (error.code !== "ENOENT") {
|
|
417
|
+
this.diagnostic("events.outbox_read_failed", error);
|
|
418
|
+
}
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const cutoff = this.now() - MAX_EVENT_AGE_MS;
|
|
422
|
+
const loaded = [];
|
|
423
|
+
for (const name of names) {
|
|
424
|
+
const file = path.join(dir, name);
|
|
425
|
+
if (name.endsWith(".tmp")) {
|
|
426
|
+
try {
|
|
427
|
+
if (fs.statSync(file).mtimeMs < cutoff)
|
|
428
|
+
this.removeFile(file);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
// A concurrent writer may have renamed its temporary file already.
|
|
432
|
+
}
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (!name.endsWith(".json"))
|
|
436
|
+
continue;
|
|
437
|
+
try {
|
|
438
|
+
const stored = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
439
|
+
const pending = parsePendingEvent(stored);
|
|
440
|
+
if (!pending || name !== `${pending.event.event_id}.json`) {
|
|
441
|
+
this.removeFile(file);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
const timestamp = Date.parse(pending.event.occurred_at);
|
|
445
|
+
if (!Number.isFinite(timestamp)
|
|
446
|
+
|| timestamp < cutoff
|
|
447
|
+
|| timestamp > this.now() + MAX_FUTURE_SKEW_MS) {
|
|
448
|
+
this.removeFile(file);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
loaded.push({ ...pending, file });
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
this.removeFile(file);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
loaded.sort(comparePending);
|
|
458
|
+
for (const pending of loaded.splice(0, Math.max(0, loaded.length - this.maxQueueSize))) {
|
|
459
|
+
this.removeFile(pending.file);
|
|
460
|
+
}
|
|
461
|
+
const files = new Set(loaded.map(({ file }) => file));
|
|
462
|
+
for (let index = this.queue.length - 1; index >= 0; index--) {
|
|
463
|
+
const file = this.queue[index].file;
|
|
464
|
+
if (file && !files.has(file))
|
|
465
|
+
this.queue.splice(index, 1);
|
|
466
|
+
}
|
|
467
|
+
const ids = new Set(this.queue.map(({ event }) => event.event_id));
|
|
468
|
+
for (const pending of loaded) {
|
|
469
|
+
if (!ids.has(pending.event.event_id))
|
|
470
|
+
this.queue.push(pending);
|
|
471
|
+
}
|
|
472
|
+
this.queue.sort(comparePending);
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
releaseMaintenance();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
acquireOutboxLock(name) {
|
|
479
|
+
const dir = this.options.outboxDir;
|
|
480
|
+
if (!dir)
|
|
481
|
+
return () => undefined;
|
|
482
|
+
const lock = path.join(dir, `.${name}.lock`);
|
|
483
|
+
try {
|
|
484
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
485
|
+
this.cleanRenamedLocks(lock);
|
|
486
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
487
|
+
const token = (0, node_crypto_1.randomUUID)();
|
|
488
|
+
try {
|
|
489
|
+
fs.mkdirSync(lock, { mode: 0o700 });
|
|
490
|
+
const owner = path.join(lock, "owner");
|
|
491
|
+
let descriptor;
|
|
492
|
+
try {
|
|
493
|
+
descriptor = fs.openSync(owner, "wx+", 0o600);
|
|
494
|
+
fs.writeFileSync(descriptor, token, "utf8");
|
|
495
|
+
fs.fsyncSync(descriptor);
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
if (descriptor !== undefined)
|
|
499
|
+
fs.closeSync(descriptor);
|
|
500
|
+
fs.rmSync(lock, { recursive: true, force: true });
|
|
501
|
+
throw error;
|
|
502
|
+
}
|
|
503
|
+
const ownerDescriptor = descriptor;
|
|
504
|
+
const heartbeat = setInterval(() => {
|
|
505
|
+
try {
|
|
506
|
+
if (ownsLockPath(ownerDescriptor, owner, token)) {
|
|
507
|
+
const now = new Date();
|
|
508
|
+
fs.futimesSync(ownerDescriptor, now, now);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
catch { /* best effort */ }
|
|
512
|
+
}, LOCK_HEARTBEAT_MS);
|
|
513
|
+
heartbeat.unref?.();
|
|
514
|
+
return () => {
|
|
515
|
+
clearInterval(heartbeat);
|
|
516
|
+
try {
|
|
517
|
+
if (ownsLockPath(ownerDescriptor, owner, token)) {
|
|
518
|
+
fs.unlinkSync(owner);
|
|
519
|
+
fs.rmdirSync(lock);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch { /* best effort */ }
|
|
523
|
+
try {
|
|
524
|
+
fs.closeSync(ownerDescriptor);
|
|
525
|
+
}
|
|
526
|
+
catch { /* best effort */ }
|
|
527
|
+
this.cleanRenamedLocks(lock);
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
if (error.code !== "EEXIST")
|
|
532
|
+
throw error;
|
|
533
|
+
try {
|
|
534
|
+
const owner = path.join(lock, "owner");
|
|
535
|
+
let modified;
|
|
536
|
+
try {
|
|
537
|
+
modified = fs.statSync(owner).mtimeMs;
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
modified = fs.statSync(lock).mtimeMs;
|
|
541
|
+
}
|
|
542
|
+
if (this.now() - modified <= LOCK_STALE_MS)
|
|
543
|
+
return undefined;
|
|
544
|
+
const stale = `${lock}.stale.${(0, node_crypto_1.randomUUID)()}`;
|
|
545
|
+
fs.renameSync(lock, stale);
|
|
546
|
+
fs.rmSync(stale, { recursive: true, force: true });
|
|
547
|
+
}
|
|
548
|
+
catch (takeoverError) {
|
|
549
|
+
if (takeoverError.code !== "ENOENT")
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
this.diagnostic("events.outbox_lock_failed", error);
|
|
557
|
+
}
|
|
558
|
+
return undefined;
|
|
559
|
+
}
|
|
560
|
+
cleanRenamedLocks(lock) {
|
|
561
|
+
const prefix = `${path.basename(lock)}.stale.`;
|
|
562
|
+
try {
|
|
563
|
+
for (const name of fs.readdirSync(path.dirname(lock))) {
|
|
564
|
+
if (name.startsWith(prefix)) {
|
|
565
|
+
fs.rmSync(path.join(path.dirname(lock), name), { recursive: true, force: true });
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
catch { /* best effort */ }
|
|
570
|
+
}
|
|
571
|
+
enforceBounds() {
|
|
572
|
+
this.syncOutbox();
|
|
573
|
+
this.queue.sort(comparePending);
|
|
574
|
+
while (this.queue.length > this.maxQueueSize) {
|
|
575
|
+
const removed = this.queue.shift();
|
|
576
|
+
this.removeFile(removed?.file);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
removeFile(file) {
|
|
580
|
+
if (!file)
|
|
581
|
+
return;
|
|
582
|
+
try {
|
|
583
|
+
fs.unlinkSync(file);
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
if (error.code !== "ENOENT") {
|
|
587
|
+
this.diagnostic("events.outbox_delete_failed", error);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
prune() {
|
|
592
|
+
this.syncOutbox();
|
|
593
|
+
const cutoff = this.now() - MAX_EVENT_AGE_MS;
|
|
594
|
+
for (let index = this.queue.length - 1; index >= 0; index--) {
|
|
595
|
+
const timestamp = Date.parse(this.queue[index].event.occurred_at);
|
|
596
|
+
if (!Number.isFinite(timestamp) || timestamp < cutoff) {
|
|
597
|
+
this.removeFile(this.queue[index].file);
|
|
598
|
+
this.queue.splice(index, 1);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
diagnostic(event, error) {
|
|
603
|
+
this.options.logger?.debug(event, {
|
|
604
|
+
message: error instanceof Error ? error.message : String(error),
|
|
605
|
+
pending: this.queue.length,
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
exports.AgentSecurityEventReporter = AgentSecurityEventReporter;
|
|
610
|
+
function ownsLockPath(descriptor, owner, token) {
|
|
611
|
+
const opened = fs.fstatSync(descriptor);
|
|
612
|
+
const current = fs.statSync(owner);
|
|
613
|
+
if (opened.dev !== current.dev || opened.ino !== current.ino)
|
|
614
|
+
return false;
|
|
615
|
+
const buffer = Buffer.alloc(Buffer.byteLength(token));
|
|
616
|
+
return fs.readSync(descriptor, buffer, 0, buffer.length, 0) === buffer.length
|
|
617
|
+
&& buffer.toString("utf8") === token;
|
|
618
|
+
}
|
|
619
|
+
function fsyncDirectory(directory) {
|
|
620
|
+
let descriptor;
|
|
621
|
+
try {
|
|
622
|
+
descriptor = fs.openSync(directory, "r");
|
|
623
|
+
fs.fsyncSync(descriptor);
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
// Some platforms do not permit opening or syncing a directory.
|
|
627
|
+
}
|
|
628
|
+
finally {
|
|
629
|
+
if (descriptor !== undefined)
|
|
630
|
+
fs.closeSync(descriptor);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function comparePending(left, right) {
|
|
634
|
+
return Date.parse(left.event.occurred_at) - Date.parse(right.event.occurred_at)
|
|
635
|
+
|| left.event.event_id.localeCompare(right.event.event_id);
|
|
636
|
+
}
|
|
637
|
+
function pendingScope(pending) {
|
|
638
|
+
return {
|
|
639
|
+
...(pending.event.session_id ? { sessionId: pending.event.session_id } : {}),
|
|
640
|
+
...(pending.principalKey ? { principalKey: pending.principalKey } : {}),
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function scopeKey(scope) {
|
|
644
|
+
return `${scope.sessionId ?? ""}\u0000${scope.principalKey ?? ""}`;
|
|
645
|
+
}
|
|
646
|
+
function parsePendingEvent(value) {
|
|
647
|
+
// Accept the original event-only format so an upgrade can drain an existing outbox.
|
|
648
|
+
if (isServerActivityEvent(value))
|
|
649
|
+
return { event: value };
|
|
650
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
651
|
+
return undefined;
|
|
652
|
+
const stored = value;
|
|
653
|
+
if (Object.keys(stored).some((key) => !["event", "principal_key"].includes(key))) {
|
|
654
|
+
return undefined;
|
|
655
|
+
}
|
|
656
|
+
if (!isServerActivityEvent(stored.event))
|
|
657
|
+
return undefined;
|
|
658
|
+
if (stored.principal_key !== undefined
|
|
659
|
+
&& (typeof stored.principal_key !== "string" || !/^[a-f0-9]{32}$/.test(stored.principal_key))) {
|
|
660
|
+
return undefined;
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
event: stored.event,
|
|
664
|
+
...(stored.principal_key ? { principalKey: stored.principal_key } : {}),
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function isServerActivityEvent(value) {
|
|
668
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
669
|
+
return false;
|
|
670
|
+
const event = value;
|
|
671
|
+
if (Object.keys(event).some((key) => ![
|
|
672
|
+
"event_id", "name", "status", "occurred_at", "session_id", "attributes",
|
|
673
|
+
].includes(key)))
|
|
674
|
+
return false;
|
|
675
|
+
if (typeof event.event_id !== "string" || !EVENT_ID.test(event.event_id)
|
|
676
|
+
|| typeof event.name !== "string" || !exports.SERVER_ACTIVITY_EVENTS.has(event.name)
|
|
677
|
+
|| typeof event.status !== "string" || !ACTIVITY_STATUSES.has(event.status)
|
|
678
|
+
|| typeof event.occurred_at !== "string"
|
|
679
|
+
|| (event.session_id !== undefined
|
|
680
|
+
&& (typeof event.session_id !== "string" || event.session_id.length > 512)))
|
|
681
|
+
return false;
|
|
682
|
+
if (event.attributes === undefined)
|
|
683
|
+
return true;
|
|
684
|
+
if (!event.attributes || typeof event.attributes !== "object" || Array.isArray(event.attributes)) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
const attributes = event.attributes;
|
|
688
|
+
const projected = projectAttributes(attributes);
|
|
689
|
+
return Object.keys(projected).length === Object.keys(attributes).length
|
|
690
|
+
&& Object.entries(projected).every(([key, item]) => attributes[key] === item);
|
|
691
|
+
}
|
|
692
|
+
function abortable(promise, signal) {
|
|
693
|
+
if (signal.aborted)
|
|
694
|
+
return Promise.reject(signal.reason);
|
|
695
|
+
return new Promise((resolve, reject) => {
|
|
696
|
+
const abort = () => reject(signal.reason);
|
|
697
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
698
|
+
promise.then((value) => {
|
|
699
|
+
signal.removeEventListener("abort", abort);
|
|
700
|
+
resolve(value);
|
|
701
|
+
}, (error) => {
|
|
702
|
+
signal.removeEventListener("abort", abort);
|
|
703
|
+
reject(error);
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
function projectActivityEntry(entry, createEventId = node_crypto_1.randomUUID) {
|
|
708
|
+
if (!exports.SERVER_ACTIVITY_EVENTS.has(entry.event))
|
|
709
|
+
return undefined;
|
|
710
|
+
const suppliedDuration = entry.attributes?.durationMs;
|
|
711
|
+
const durationMs = entry.durationMs > 0
|
|
712
|
+
? entry.durationMs
|
|
713
|
+
: typeof suppliedDuration === "number"
|
|
714
|
+
? suppliedDuration
|
|
715
|
+
: entry.durationMs;
|
|
716
|
+
const attributes = projectAttributes({
|
|
717
|
+
...entry.attributes,
|
|
718
|
+
harness: entry.harness,
|
|
719
|
+
durationMs,
|
|
720
|
+
});
|
|
721
|
+
const timestamp = Date.parse(entry.timestamp);
|
|
722
|
+
const occurredAt = entry.durationMs === 0
|
|
723
|
+
&& durationMs > 0
|
|
724
|
+
&& Number.isFinite(timestamp)
|
|
725
|
+
? new Date(timestamp - durationMs).toISOString()
|
|
726
|
+
: entry.timestamp;
|
|
727
|
+
return {
|
|
728
|
+
event_id: createEventId(),
|
|
729
|
+
name: entry.event,
|
|
730
|
+
status: entry.status,
|
|
731
|
+
occurred_at: occurredAt,
|
|
732
|
+
...(entry.sessionId !== undefined && entry.sessionId.length <= 512
|
|
733
|
+
? { session_id: entry.sessionId }
|
|
734
|
+
: {}),
|
|
735
|
+
...(Object.keys(attributes).length > 0 ? { attributes } : {}),
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
/** Stable, non-identifying directory name that prevents cross-project replay. */
|
|
739
|
+
function eventOutboxProjectScope(projectUrl) {
|
|
740
|
+
return (0, node_crypto_1.createHash)("sha256").update(projectUrl).digest("hex").slice(0, 16);
|
|
741
|
+
}
|
|
742
|
+
/** Stable local selector for a bearer without persisting its identifying subject. */
|
|
743
|
+
function eventPrincipalKey(subject) {
|
|
744
|
+
return (0, node_crypto_1.createHash)("sha256").update(subject).digest("hex").slice(0, 32);
|
|
745
|
+
}
|
|
746
|
+
function projectAttributes(input) {
|
|
747
|
+
const output = {};
|
|
748
|
+
for (const [name, value] of Object.entries(input)) {
|
|
749
|
+
if (name === "checkRejected" && typeof value === "string" && SAFE_STRING.test(value)) {
|
|
750
|
+
output.checkRejected = true;
|
|
751
|
+
if (!("reasonCode" in input))
|
|
752
|
+
output.reasonCode = value;
|
|
753
|
+
}
|
|
754
|
+
else if (STRING_ATTRIBUTES.has(name) && typeof value === "string" && SAFE_STRING.test(value)) {
|
|
755
|
+
output[name] = value;
|
|
756
|
+
}
|
|
757
|
+
else if (HASH_ATTRIBUTES.has(name) && typeof value === "string" && HASH.test(value)) {
|
|
758
|
+
output[name] = value;
|
|
759
|
+
}
|
|
760
|
+
else if (NUMBER_ATTRIBUTES.has(name) && typeof value === "number" && Number.isFinite(value)) {
|
|
761
|
+
const maximum = name === "durationMs" ? MAX_EVENT_AGE_MS : MAX_NUMBER;
|
|
762
|
+
if (value >= 0 && value <= maximum)
|
|
763
|
+
output[name] = value;
|
|
764
|
+
}
|
|
765
|
+
else if (BOOL_ATTRIBUTES.has(name) && typeof value === "boolean") {
|
|
766
|
+
output[name] = value;
|
|
767
|
+
}
|
|
768
|
+
else if (ARRAY_ATTRIBUTES.has(name)
|
|
769
|
+
&& Array.isArray(value)
|
|
770
|
+
&& value.length <= 100
|
|
771
|
+
&& value.every((item) => typeof item === "string" && SAFE_STRING.test(item))) {
|
|
772
|
+
output[name] = value;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return output;
|
|
776
|
+
}
|