@safebrowse/daemon 0.1.3 → 0.1.5
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 +64 -11
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +107 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/loaders.d.ts.map +1 -1
- package/dist/loaders.js +65 -2
- package/dist/loaders.js.map +1 -1
- package/dist/modelGuard.d.ts +28 -0
- package/dist/modelGuard.d.ts.map +1 -0
- package/dist/modelGuard.js +325 -0
- package/dist/modelGuard.js.map +1 -0
- package/dist/parserIsolation.d.ts +38 -4
- package/dist/parserIsolation.d.ts.map +1 -1
- package/dist/parserIsolation.js +187 -37
- package/dist/parserIsolation.js.map +1 -1
- package/dist/parserWorker.js +97 -18
- package/dist/parserWorker.js.map +1 -1
- package/dist/runtime/config/auditor/v6_secure_claim_suite.json +70 -0
- package/dist/server.d.ts +9 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +745 -408
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/runtime/config/auditor/v4_prompt_injection_coverage_suite.json +0 -2789
- package/dist/runtime/config/v2-compromised-fixtures.json +0 -34
package/dist/server.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { stat } from "node:fs/promises";
|
|
1
|
+
import { createHash, createPublicKey, randomUUID } from "node:crypto";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import {
|
|
7
|
-
import { buildRegistryDefaults, loadKnowledgeBaseContext,
|
|
8
|
-
import {
|
|
6
|
+
import { applyModelGuardAssessment, applyV6ObservationMediation, buildReplayBundle, buildModelGuardObservationRequest, compilePolicy, computeToolManifestHash, computeToolSchemaHash, createApprovalIntentPayloadV6, evaluateCapabilityUseV6, extractAttachmentGraphV6, issueApprovalEnvelopeV6, mintCapabilitiesForObservationV6, mintMemoryPromotionCapabilityV6, prepareToolOnboardingV6, promoteMemoryRecordV6, rollbackMemoryRecordV6, stageMemoryRecordV6, tightenAuthoritiesWithModelGuard, verifyApprovalIntentSignatureV6, verifyToolCallbackV6 } from "@safebrowse/core";
|
|
7
|
+
import { buildRegistryDefaults, loadKnowledgeBaseContext, loadPolicyPackFromPaths, loadVerifiedRegistryBundle, resolvePolicyLayerFiles } from "./loaders.js";
|
|
8
|
+
import { createModelGuardClient } from "./modelGuard.js";
|
|
9
|
+
import { createParserIsolationService } from "./parserIsolation.js";
|
|
10
|
+
const PARSER_HEALTH_REFRESH_INTERVAL_MS = 30_000;
|
|
9
11
|
function hashValue(input) {
|
|
10
12
|
return createHash("sha256").update(JSON.stringify(input)).digest("hex");
|
|
11
13
|
}
|
|
@@ -21,16 +23,12 @@ function writeJson(response, statusCode, payload) {
|
|
|
21
23
|
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
22
24
|
response.end(JSON.stringify(payload, null, 2));
|
|
23
25
|
}
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
preventionClaim: false,
|
|
31
|
-
routeVersion: route
|
|
32
|
-
}
|
|
33
|
-
};
|
|
26
|
+
function logServerError(error) {
|
|
27
|
+
if (error instanceof Error) {
|
|
28
|
+
console.error("SafeBrowse daemon error:", error.stack ?? error.message);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.error("SafeBrowse daemon error:", error);
|
|
34
32
|
}
|
|
35
33
|
async function fileExists(path) {
|
|
36
34
|
try {
|
|
@@ -52,21 +50,36 @@ async function resolveDefaultRootDir() {
|
|
|
52
50
|
}
|
|
53
51
|
async function buildRuntimeContext(options) {
|
|
54
52
|
const rootDir = options.rootDir ?? (await resolveDefaultRootDir());
|
|
53
|
+
const deploymentProfile = options.deploymentProfile ?? "development";
|
|
54
|
+
const secureDeployment = deploymentProfile === "secure_v6";
|
|
55
55
|
const policyPack = options.policyPack ??
|
|
56
56
|
(await loadPolicyPackFromPaths(resolvePolicyLayerFiles(resolve(rootDir))));
|
|
57
57
|
const knowledgeBase = options.knowledgeBase ?? (await loadKnowledgeBaseContext(resolve(rootDir, "knowledge_base")));
|
|
58
58
|
const verifiedRegistry = options.verifiedRegistry ??
|
|
59
59
|
(await loadVerifiedRegistryBundle(buildRegistryDefaults(resolve(rootDir))).catch(() => undefined));
|
|
60
|
+
const approvalBrokerPublicKeyPem = options.approvalBrokerPublicKeyPem ??
|
|
61
|
+
(options.approvalBrokerPublicKeyPath
|
|
62
|
+
? await readFile(options.approvalBrokerPublicKeyPath, "utf8").catch(() => undefined)
|
|
63
|
+
: await readFile(resolve(rootDir, "knowledge_base", "signing", "safebrowse_vf_ed25519_public.pem"), "utf8").catch(() => undefined));
|
|
60
64
|
return {
|
|
61
65
|
policy: compilePolicy(policyPack),
|
|
62
66
|
knowledgeBase,
|
|
63
67
|
verifiedRegistry,
|
|
64
|
-
parserAllowlistedEgress: options.parserAllowlistedEgress ?? []
|
|
68
|
+
parserAllowlistedEgress: options.parserAllowlistedEgress ?? [],
|
|
69
|
+
parserIsolationMode: secureDeployment
|
|
70
|
+
? "node_permission_process"
|
|
71
|
+
: options.parserIsolationMode ?? "scrubbed_process",
|
|
72
|
+
deploymentProfile,
|
|
73
|
+
approvalBrokerPublicKey: approvalBrokerPublicKeyPem
|
|
74
|
+
? createPublicKey(approvalBrokerPublicKeyPem)
|
|
75
|
+
: undefined,
|
|
76
|
+
approvalBrokerConfigured: Boolean(approvalBrokerPublicKeyPem),
|
|
77
|
+
approvalBrokerMode: secureDeployment ? "external_service" : options.approvalBrokerMode ?? "signature_verification",
|
|
78
|
+
modelGuardBaseUrl: options.modelGuardBaseUrl?.trim() || undefined,
|
|
79
|
+
modelGuardTimeoutMs: options.modelGuardTimeoutMs ?? 2_500,
|
|
80
|
+
modelGuardEnforcementMode: options.modelGuardEnforcementMode ?? "off"
|
|
65
81
|
};
|
|
66
82
|
}
|
|
67
|
-
function plusMinutes(value, minutes) {
|
|
68
|
-
return new Date(new Date(value).getTime() + minutes * 60_000).toISOString();
|
|
69
|
-
}
|
|
70
83
|
function plusSeconds(value, seconds) {
|
|
71
84
|
return new Date(new Date(value).getTime() + seconds * 1_000).toISOString();
|
|
72
85
|
}
|
|
@@ -75,333 +88,312 @@ function createWorkflowHash(payload) {
|
|
|
75
88
|
taskId: payload.taskId,
|
|
76
89
|
userGoal: payload.userGoal,
|
|
77
90
|
phase: payload.phase ?? "",
|
|
91
|
+
taskPurposeClass: payload.taskPurposeClass ?? "",
|
|
92
|
+
taskPhase: payload.taskPhase ?? "",
|
|
78
93
|
allowedOrigins: payload.allowedOrigins ?? [],
|
|
79
94
|
allowedVerbs: payload.allowedVerbs ?? [],
|
|
80
|
-
forbiddenSinks: payload.forbiddenSinks ?? []
|
|
95
|
+
forbiddenSinks: payload.forbiddenSinks ?? [],
|
|
96
|
+
allowedPathClasses: payload.allowedPathClasses ?? [],
|
|
97
|
+
approvalRequiredPathClasses: payload.approvalRequiredPathClasses ?? []
|
|
81
98
|
});
|
|
82
99
|
}
|
|
83
|
-
function
|
|
100
|
+
function secureParserIsolationSatisfied(probe) {
|
|
101
|
+
return (probe.mode === "node_permission_process" &&
|
|
102
|
+
probe.processIsolated &&
|
|
103
|
+
probe.egressDenied &&
|
|
104
|
+
probe.permissionModelEnabled &&
|
|
105
|
+
probe.childProcessDenied &&
|
|
106
|
+
probe.workerThreadsDenied &&
|
|
107
|
+
probe.envKeys.length === 0);
|
|
108
|
+
}
|
|
109
|
+
function claimBearingReady(runtime, probe) {
|
|
110
|
+
return (runtime.deploymentProfile === "secure_v6" &&
|
|
111
|
+
runtime.approvalBrokerConfigured &&
|
|
112
|
+
Boolean(runtime.approvalBrokerPublicKey) &&
|
|
113
|
+
runtime.approvalBrokerMode === "external_service" &&
|
|
114
|
+
runtime.verifiedRegistry?.signatureVerified === true &&
|
|
115
|
+
runtime.parserIsolationMode === "node_permission_process" &&
|
|
116
|
+
secureParserIsolationSatisfied(probe));
|
|
117
|
+
}
|
|
118
|
+
function createSessionState(request, runtime, secureReady) {
|
|
84
119
|
const createdAt = new Date().toISOString();
|
|
85
|
-
const allowedOrigins = request.allowedOrigins ??
|
|
86
|
-
[...runtime.policy.readOnlyOrigins, ...runtime.policy.writableOrigins];
|
|
120
|
+
const allowedOrigins = request.allowedOrigins ?? [...runtime.policy.readOnlyOrigins, ...runtime.policy.writableOrigins];
|
|
87
121
|
const allowedVerbs = request.allowedVerbs ?? [...runtime.policy.allowedActions];
|
|
88
|
-
const forbiddenSinks = request.forbiddenSinks ?? [];
|
|
89
122
|
const session = {
|
|
90
123
|
sessionId: randomUUID(),
|
|
91
124
|
taskId: request.taskId,
|
|
92
125
|
userGoal: request.userGoal,
|
|
93
126
|
phase: request.phase,
|
|
127
|
+
taskPurposeClass: request.taskPurposeClass,
|
|
128
|
+
taskPhase: request.taskPhase,
|
|
94
129
|
allowedOrigins,
|
|
95
130
|
allowedVerbs,
|
|
96
|
-
forbiddenSinks,
|
|
131
|
+
forbiddenSinks: request.forbiddenSinks ?? [],
|
|
132
|
+
allowedPathClasses: request.allowedPathClasses,
|
|
133
|
+
approvalRequiredPathClasses: request.approvalRequiredPathClasses,
|
|
97
134
|
workflowHash: createWorkflowHash({
|
|
98
|
-
|
|
99
|
-
userGoal: request.userGoal,
|
|
100
|
-
phase: request.phase,
|
|
135
|
+
...request,
|
|
101
136
|
allowedOrigins,
|
|
102
|
-
allowedVerbs
|
|
103
|
-
forbiddenSinks
|
|
137
|
+
allowedVerbs
|
|
104
138
|
}),
|
|
105
139
|
currentStep: 0,
|
|
106
140
|
createdAt,
|
|
107
|
-
expiresAt: plusSeconds(createdAt, request.expiresInSeconds ?? 1800)
|
|
141
|
+
expiresAt: plusSeconds(createdAt, request.expiresInSeconds ?? 1800),
|
|
142
|
+
claimProfile: secureReady ? "secure_v6" : undefined,
|
|
143
|
+
approvalBrokerRequired: runtime.deploymentProfile === "secure_v6",
|
|
144
|
+
legacyRoutesDisabled: true
|
|
108
145
|
};
|
|
109
146
|
return {
|
|
110
147
|
session,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
148
|
+
observations: new Map(),
|
|
149
|
+
authorities: new Map(),
|
|
150
|
+
usedAuthorities: new Set(),
|
|
151
|
+
approvalEnvelopes: new Map(),
|
|
152
|
+
onboardingSessions: new Map(),
|
|
153
|
+
connectorHandles: new Map(),
|
|
116
154
|
memoryRecords: new Map(),
|
|
155
|
+
memorySourceClasses: new Map(),
|
|
117
156
|
memorySnapshots: new Map(),
|
|
118
|
-
|
|
157
|
+
replayEvents: []
|
|
119
158
|
};
|
|
120
159
|
}
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
160
|
+
function findSessionState(sessions, sessionId) {
|
|
161
|
+
const sessionState = sessions.get(sessionId);
|
|
162
|
+
if (!sessionState) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
if (new Date(sessionState.session.expiresAt).getTime() <= Date.now()) {
|
|
166
|
+
sessions.delete(sessionId);
|
|
167
|
+
return undefined;
|
|
124
168
|
}
|
|
125
|
-
|
|
126
|
-
const hasMeaningfulRisk = observation.parseStatus !== "compiled" ||
|
|
127
|
-
observation.riskFindings.length > 0 ||
|
|
128
|
-
observation.secretFindings.length > 0;
|
|
129
|
-
return hasPriorSurface && hasMeaningfulRisk;
|
|
169
|
+
return sessionState;
|
|
130
170
|
}
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
...
|
|
139
|
-
|
|
140
|
-
sessionState.
|
|
141
|
-
|
|
142
|
-
|
|
171
|
+
function appendReplayEvent(sessionState, event) {
|
|
172
|
+
if (!sessionState) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
const replayEvent = {
|
|
176
|
+
eventId: randomUUID(),
|
|
177
|
+
timestamp: new Date().toISOString(),
|
|
178
|
+
...event
|
|
179
|
+
};
|
|
180
|
+
sessionState.replayEvents.push(replayEvent);
|
|
181
|
+
return replayEvent.eventId;
|
|
182
|
+
}
|
|
183
|
+
function authorityCandidateFromDescriptor(authority) {
|
|
143
184
|
return {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
185
|
+
authorityId: authority.capabilityId,
|
|
186
|
+
authorityDigest: authority.capabilityDigest,
|
|
187
|
+
semanticDigest: authority.semanticDigest,
|
|
188
|
+
title: authority.title,
|
|
189
|
+
kind: authority.kind,
|
|
190
|
+
operationClass: authority.operationClass,
|
|
191
|
+
targetPathClass: authority.targetPathClass,
|
|
192
|
+
requiresApproval: authority.requiresApproval,
|
|
193
|
+
evidenceSpanIds: authority.evidenceSpanIds,
|
|
194
|
+
providerId: authority.providerId,
|
|
195
|
+
operationId: authority.operationId,
|
|
196
|
+
method: authority.method,
|
|
197
|
+
pathTemplate: authority.pathTemplate,
|
|
198
|
+
mailboxId: authority.mailboxId,
|
|
199
|
+
accountId: authority.accountId,
|
|
200
|
+
messageId: authority.messageId,
|
|
201
|
+
threadId: authority.threadId,
|
|
202
|
+
recipientSetHash: authority.recipientSetHash,
|
|
203
|
+
subjectHash: authority.subjectHash,
|
|
204
|
+
bodyDigest: authority.bodyDigest,
|
|
205
|
+
attachmentDigestSet: authority.attachmentDigestSet,
|
|
206
|
+
parameterSchema: authority.parameterSchema,
|
|
207
|
+
expiresAt: authority.expiresAt
|
|
153
208
|
};
|
|
154
209
|
}
|
|
155
|
-
function
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
210
|
+
function consumeAuthority(sessionState, authority) {
|
|
211
|
+
sessionState.authorities.set(authority.capabilityId, {
|
|
212
|
+
...authority,
|
|
213
|
+
consumedAt: new Date().toISOString()
|
|
214
|
+
});
|
|
215
|
+
sessionState.usedAuthorities.add(authority.capabilityId);
|
|
216
|
+
}
|
|
217
|
+
function lookupVerifiedRegistryEntry(runtime, toolId, registryEntryId) {
|
|
218
|
+
return runtime.verifiedRegistry?.entries.find((entry) => entry.registryEntryId === registryEntryId ||
|
|
219
|
+
entry.registryEntryId === toolId ||
|
|
220
|
+
entry.adapterId === toolId);
|
|
221
|
+
}
|
|
222
|
+
function lookupVerifiedApiProviderEntry(runtime, providerId) {
|
|
223
|
+
return runtime.verifiedRegistry?.apiProviders?.find((entry) => entry.providerId === providerId);
|
|
224
|
+
}
|
|
225
|
+
function buildArtifactRef(capture, observation) {
|
|
226
|
+
const extractionMethod = capture.surfaceType === "html"
|
|
227
|
+
? "dom"
|
|
228
|
+
: capture.surfaceType === "email_message"
|
|
229
|
+
? "mime"
|
|
230
|
+
: capture.surfaceType === "docx" ||
|
|
231
|
+
capture.surfaceType === "xlsx" ||
|
|
232
|
+
capture.surfaceType === "pptx"
|
|
233
|
+
? "ooxml"
|
|
234
|
+
: capture.surfaceType === "attachment_bundle"
|
|
235
|
+
? "extractor"
|
|
236
|
+
: capture.surfaceType === "tool_manifest" || capture.surfaceType === "memory_candidate"
|
|
237
|
+
? "api"
|
|
238
|
+
: capture.surfaceType === "external_api_response"
|
|
239
|
+
? "api"
|
|
240
|
+
: capture.surfaceType === "image"
|
|
241
|
+
? "ocr"
|
|
242
|
+
: "download";
|
|
243
|
+
const surfaceKind = capture.surfaceType === "tool_manifest"
|
|
244
|
+
? "tool_manifest"
|
|
245
|
+
: capture.surfaceType === "memory_candidate"
|
|
246
|
+
? "memory"
|
|
247
|
+
: capture.surfaceType;
|
|
166
248
|
return {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
state: randomUUID(),
|
|
180
|
-
pkceMethod: "S256",
|
|
181
|
-
createdAt,
|
|
182
|
-
expiresAt: plusMinutes(createdAt, 5),
|
|
183
|
-
status: "prepared"
|
|
249
|
+
artifactId: observation.observationId,
|
|
250
|
+
surfaceKind,
|
|
251
|
+
sourceOrigin: observation.sourceOrigin,
|
|
252
|
+
viewerOrigin: observation.frameOrigin,
|
|
253
|
+
mismatchSignals: observation.parseStatus === "compiled" ? [] : [`parse_status_${observation.parseStatus}`],
|
|
254
|
+
metadataSignals: observation.policyFindings.map((finding) => finding.code),
|
|
255
|
+
provenance: {
|
|
256
|
+
extractionMethod,
|
|
257
|
+
lineageChain: [],
|
|
258
|
+
derivedTaintClass: "tainted"
|
|
259
|
+
},
|
|
260
|
+
authorityEligible: observation.authorityEligible
|
|
184
261
|
};
|
|
185
262
|
}
|
|
186
|
-
function
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
263
|
+
function buildMemorySnapshotState(sessionState, record) {
|
|
264
|
+
const priorTrustedRecord = [...sessionState.memoryRecords.values()]
|
|
265
|
+
.filter((candidate) => candidate.recordId !== record.recordId &&
|
|
266
|
+
candidate.key === record.key &&
|
|
267
|
+
candidate.tier === "trusted_durable")
|
|
268
|
+
.sort((left, right) => right.createdAt.localeCompare(left.createdAt))[0];
|
|
269
|
+
return {
|
|
270
|
+
snapshotId: "",
|
|
190
271
|
sessionId: sessionState.session.sessionId,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
targetOrigin: request.targetOrigin,
|
|
197
|
-
issuedAt,
|
|
198
|
-
expiresAt: plusSeconds(issuedAt, request.expiresInSeconds ?? 600)
|
|
272
|
+
recordId: record.recordId,
|
|
273
|
+
key: record.key,
|
|
274
|
+
createdAt: new Date().toISOString(),
|
|
275
|
+
baselineAbsent: !priorTrustedRecord,
|
|
276
|
+
snapshotRecord: priorTrustedRecord
|
|
199
277
|
};
|
|
278
|
+
}
|
|
279
|
+
function retiredRouteResponse(route) {
|
|
200
280
|
return {
|
|
201
|
-
|
|
202
|
-
|
|
281
|
+
error: "route_retired_use_v6",
|
|
282
|
+
route,
|
|
283
|
+
replacementPrefix: "/v6",
|
|
284
|
+
deprecated: true
|
|
203
285
|
};
|
|
204
286
|
}
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (new Date(sessionState.session.expiresAt).getTime() <= Date.now()) {
|
|
211
|
-
sessions.delete(sessionId);
|
|
212
|
-
return undefined;
|
|
213
|
-
}
|
|
214
|
-
return sessionState;
|
|
215
|
-
}
|
|
216
|
-
function buildLegacyObservationCapture(payload) {
|
|
217
|
-
if (payload.surfaceType === "pdf") {
|
|
218
|
-
return {
|
|
219
|
-
mimeType: "application/pdf",
|
|
220
|
-
sourceOrigin: payload.url,
|
|
221
|
-
viewerOrigin: payload.frameUrl ?? payload.url,
|
|
222
|
-
renderedText: payload.renderedText,
|
|
223
|
-
extractedText: payload.extractedText,
|
|
224
|
-
ocrText: payload.ocrText,
|
|
225
|
-
annotations: payload.annotations,
|
|
226
|
-
metadataText: payload.metadataText,
|
|
227
|
-
extractionMethod: "download",
|
|
228
|
-
trustSignals: payload.trustSignals
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
if (payload.surfaceType === "image") {
|
|
232
|
-
return {
|
|
233
|
-
mimeType: "image/png",
|
|
234
|
-
sourceOrigin: payload.url,
|
|
235
|
-
viewerOrigin: payload.frameUrl ?? payload.url,
|
|
236
|
-
ocrText: payload.ocrText,
|
|
237
|
-
metadataText: payload.metadataText,
|
|
238
|
-
extractionMethod: "ocr",
|
|
239
|
-
trustSignals: payload.trustSignals
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
if (payload.surfaceType === "tool_manifest") {
|
|
243
|
-
return {
|
|
244
|
-
mimeType: "application/json",
|
|
245
|
-
surfaceKind: "tool_manifest",
|
|
246
|
-
sourceOrigin: payload.url,
|
|
247
|
-
viewerOrigin: payload.frameUrl ?? payload.url,
|
|
248
|
-
extractedText: payload.description,
|
|
249
|
-
metadataText: payload.schemaDescriptions,
|
|
250
|
-
extractionMethod: "api",
|
|
251
|
-
trustSignals: payload.trustSignals
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
return undefined;
|
|
287
|
+
function isRetiredRoute(url) {
|
|
288
|
+
return (url.startsWith("/v1/") ||
|
|
289
|
+
url.startsWith("/v2/") ||
|
|
290
|
+
url.startsWith("/v4/") ||
|
|
291
|
+
url.startsWith("/v5/"));
|
|
255
292
|
}
|
|
256
293
|
export async function createSafeBrowseServer(options = {}) {
|
|
257
294
|
const runtime = await buildRuntimeContext(options);
|
|
258
|
-
const
|
|
295
|
+
const modelGuardClient = createModelGuardClient({
|
|
296
|
+
baseUrl: runtime.modelGuardBaseUrl,
|
|
297
|
+
timeoutMs: runtime.modelGuardTimeoutMs,
|
|
298
|
+
enforcementMode: runtime.modelGuardEnforcementMode
|
|
299
|
+
});
|
|
300
|
+
const parserIsolationService = createParserIsolationService(runtime.parserIsolationMode, {
|
|
301
|
+
allowlistedEgress: runtime.parserAllowlistedEgress,
|
|
302
|
+
runtime
|
|
303
|
+
});
|
|
304
|
+
let parserProbeSnapshot = await parserIsolationService.refreshProbe();
|
|
305
|
+
let modelGuardHealthSnapshot = await modelGuardClient.refreshHealth();
|
|
306
|
+
const secureReady = claimBearingReady(runtime, parserProbeSnapshot.probe);
|
|
259
307
|
const sessions = new Map();
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
308
|
+
const parserHealthRefreshTimer = setInterval(() => {
|
|
309
|
+
void parserIsolationService
|
|
310
|
+
.refreshProbe()
|
|
311
|
+
.then((snapshot) => {
|
|
312
|
+
parserProbeSnapshot = snapshot;
|
|
313
|
+
})
|
|
314
|
+
.catch(() => undefined);
|
|
315
|
+
void modelGuardClient
|
|
316
|
+
.refreshHealth()
|
|
317
|
+
.then((snapshot) => {
|
|
318
|
+
modelGuardHealthSnapshot = snapshot;
|
|
319
|
+
})
|
|
320
|
+
.catch(() => undefined);
|
|
321
|
+
}, PARSER_HEALTH_REFRESH_INTERVAL_MS);
|
|
322
|
+
parserHealthRefreshTimer.unref?.();
|
|
323
|
+
const server = createServer(async (request, response) => {
|
|
265
324
|
try {
|
|
266
|
-
|
|
267
|
-
|
|
325
|
+
const requestUrl = request.url ?? "/";
|
|
326
|
+
if (request.method === "GET" && requestUrl === "/health") {
|
|
268
327
|
writeJson(response, 200, {
|
|
269
328
|
status: "ok",
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
329
|
+
deploymentProfile: runtime.deploymentProfile,
|
|
330
|
+
claimBearingReady: claimBearingReady(runtime, parserProbeSnapshot.probe),
|
|
331
|
+
legacyRoutesEnabled: false,
|
|
332
|
+
approvalBroker: {
|
|
333
|
+
configured: runtime.approvalBrokerConfigured,
|
|
334
|
+
mode: runtime.approvalBrokerMode
|
|
335
|
+
},
|
|
336
|
+
parserIsolation: {
|
|
337
|
+
configuredMode: runtime.parserIsolationMode,
|
|
338
|
+
lastCheckedAt: parserProbeSnapshot.lastCheckedAt,
|
|
339
|
+
...parserProbeSnapshot.probe
|
|
340
|
+
},
|
|
273
341
|
verifiedRegistry: runtime.verifiedRegistry
|
|
274
342
|
? {
|
|
275
343
|
bundleId: runtime.verifiedRegistry.bundleId,
|
|
276
344
|
version: runtime.verifiedRegistry.version,
|
|
345
|
+
signer: runtime.verifiedRegistry.signer,
|
|
277
346
|
signatureVerified: runtime.verifiedRegistry.signatureVerified,
|
|
278
|
-
|
|
347
|
+
apiProviderCount: runtime.verifiedRegistry.apiProviders?.length ?? 0,
|
|
348
|
+
extractorProfileCount: runtime.verifiedRegistry.extractorProfiles?.length ?? 0
|
|
279
349
|
}
|
|
280
350
|
: undefined,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (request.url === "/v1/action") {
|
|
298
|
-
const payload = await readJson(request);
|
|
299
|
-
writeJson(response, 200, {
|
|
300
|
-
...evaluateAction(payload, runtime),
|
|
301
|
-
...legacyResponseMeta("/v1/action")
|
|
302
|
-
});
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
if (request.url === "/v1/artifact") {
|
|
306
|
-
const payload = await readJson(request);
|
|
307
|
-
writeJson(response, 200, {
|
|
308
|
-
...brokerArtifact(payload, runtime),
|
|
309
|
-
...legacyResponseMeta("/v1/artifact")
|
|
310
|
-
});
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
if (request.url === "/v1/tool") {
|
|
314
|
-
const payload = await readJson(request);
|
|
315
|
-
writeJson(response, 200, {
|
|
316
|
-
...evaluateToolRequest(payload, runtime),
|
|
317
|
-
...legacyResponseMeta("/v1/tool")
|
|
318
|
-
});
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
if (request.url === "/v1/memory") {
|
|
322
|
-
const payload = await readJson(request);
|
|
323
|
-
writeJson(response, 200, {
|
|
324
|
-
...evaluateMemoryWrite(payload, runtime),
|
|
325
|
-
...legacyResponseMeta("/v1/memory")
|
|
326
|
-
});
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
if (request.url === "/v1/replay") {
|
|
330
|
-
const payload = await readJson(request);
|
|
331
|
-
writeJson(response, 200, {
|
|
332
|
-
...buildReplayBundle(payload.events, runtime),
|
|
333
|
-
...legacyResponseMeta("/v1/replay")
|
|
334
|
-
});
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
if (request.url === "/v2/tool/prepare") {
|
|
338
|
-
const payload = await readJson(request);
|
|
339
|
-
const prepared = prepareToolOnboarding(payload, runtime);
|
|
340
|
-
const onboardingSession = prepared.verdict.decision === "ALLOW" && payload.authType === "oauth"
|
|
341
|
-
? createOnboardingSession(payload, runtime, {
|
|
342
|
-
approvalGrantId: payload.approvalBindingId ?? randomUUID(),
|
|
343
|
-
sessionId: "legacy-session",
|
|
344
|
-
workflowHash: hashValue(payload.sourceObservationId ?? payload.requestId),
|
|
345
|
-
connectorId: payload.toolId,
|
|
346
|
-
scopes: payload.requestedScopes ?? [],
|
|
347
|
-
sinkClass: "connector_oauth",
|
|
348
|
-
capabilityIds: payload.capabilityId ? [payload.capabilityId] : [],
|
|
349
|
-
targetOrigin: payload.callbackOrigin ??
|
|
350
|
-
payload.oauthContext?.callbackOrigin ??
|
|
351
|
-
payload.callbackUri ??
|
|
352
|
-
payload.requestedRedirectUri ??
|
|
353
|
-
"unknown",
|
|
354
|
-
issuedAt: new Date().toISOString(),
|
|
355
|
-
expiresAt: plusMinutes(new Date().toISOString(), 10),
|
|
356
|
-
grantHash: "legacy"
|
|
357
|
-
})
|
|
358
|
-
: undefined;
|
|
359
|
-
if (onboardingSession) {
|
|
360
|
-
onboardingSessions.set(onboardingSession.sessionId, onboardingSession);
|
|
361
|
-
}
|
|
362
|
-
writeJson(response, 200, {
|
|
363
|
-
verdict: prepared.verdict,
|
|
364
|
-
verifiedRegistryEntry: prepared.verifiedRegistryEntry,
|
|
365
|
-
workflowBinding: prepared.workflowBinding,
|
|
366
|
-
onboardingSession,
|
|
367
|
-
...legacyResponseMeta("/v2/tool/prepare")
|
|
351
|
+
policyLayers: runtime.policy.layerProvenance,
|
|
352
|
+
captureAttestation: {
|
|
353
|
+
htmlDom: true,
|
|
354
|
+
required: true
|
|
355
|
+
},
|
|
356
|
+
modelGuard: {
|
|
357
|
+
configured: modelGuardHealthSnapshot.configured,
|
|
358
|
+
ready: modelGuardHealthSnapshot.ready,
|
|
359
|
+
runtimeMode: modelGuardHealthSnapshot.runtimeMode,
|
|
360
|
+
enforcementMode: modelGuardHealthSnapshot.enforcementMode,
|
|
361
|
+
bundleVersion: modelGuardHealthSnapshot.bundleVersion,
|
|
362
|
+
featureSchemaVersion: modelGuardHealthSnapshot.featureSchemaVersion,
|
|
363
|
+
bundleDigest: modelGuardHealthSnapshot.bundleDigest,
|
|
364
|
+
componentDigests: modelGuardHealthSnapshot.componentDigests,
|
|
365
|
+
validationError: modelGuardHealthSnapshot.validationError
|
|
366
|
+
}
|
|
368
367
|
});
|
|
369
368
|
return;
|
|
370
369
|
}
|
|
371
|
-
if (
|
|
372
|
-
|
|
373
|
-
const session = onboardingSessions.get(payload.sessionId);
|
|
374
|
-
const result = verifyToolCallback(payload, session, runtime);
|
|
375
|
-
if (session) {
|
|
376
|
-
onboardingSessions.set(payload.sessionId, {
|
|
377
|
-
...session,
|
|
378
|
-
status: result.verdict.decision === "ALLOW" ? "used" : session.status
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
writeJson(response, 200, {
|
|
382
|
-
...result,
|
|
383
|
-
...legacyResponseMeta("/v2/tool/callback/verify")
|
|
384
|
-
});
|
|
370
|
+
if (isRetiredRoute(requestUrl)) {
|
|
371
|
+
writeJson(response, 410, retiredRouteResponse(requestUrl));
|
|
385
372
|
return;
|
|
386
373
|
}
|
|
387
|
-
if (request.
|
|
388
|
-
|
|
389
|
-
writeJson(response, 200, {
|
|
390
|
-
...brokerArtifactV2(payload, runtime),
|
|
391
|
-
...legacyResponseMeta("/v2/artifact")
|
|
392
|
-
});
|
|
374
|
+
if (request.method !== "POST" || !requestUrl.startsWith("/v6/")) {
|
|
375
|
+
writeJson(response, 404, { error: "not_found" });
|
|
393
376
|
return;
|
|
394
377
|
}
|
|
395
|
-
if (
|
|
378
|
+
if (requestUrl === "/v6/session/start") {
|
|
396
379
|
const payload = await readJson(request);
|
|
397
|
-
const sessionState = createSessionState(payload, runtime);
|
|
380
|
+
const sessionState = createSessionState(payload, runtime, secureReady);
|
|
398
381
|
sessions.set(sessionState.session.sessionId, sessionState);
|
|
382
|
+
appendReplayEvent(sessionState, {
|
|
383
|
+
kind: "observation",
|
|
384
|
+
actor: "sdk",
|
|
385
|
+
payload: {
|
|
386
|
+
route: requestUrl,
|
|
387
|
+
sessionId: sessionState.session.sessionId,
|
|
388
|
+
taskId: sessionState.session.taskId
|
|
389
|
+
}
|
|
390
|
+
});
|
|
399
391
|
writeJson(response, 200, {
|
|
400
392
|
session: sessionState.session
|
|
401
393
|
});
|
|
402
394
|
return;
|
|
403
395
|
}
|
|
404
|
-
if (
|
|
396
|
+
if (requestUrl === "/v6/observe") {
|
|
405
397
|
const payload = await readJson(request);
|
|
406
398
|
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
407
399
|
if (!sessionState) {
|
|
@@ -413,134 +405,346 @@ export async function createSafeBrowseServer(options = {}) {
|
|
|
413
405
|
sessionId: payload.sessionId,
|
|
414
406
|
taskId: sessionState.session.taskId
|
|
415
407
|
};
|
|
416
|
-
const
|
|
408
|
+
const parsed = await parserIsolationService.compileObservation({
|
|
417
409
|
capture,
|
|
418
410
|
workflowHash: sessionState.session.workflowHash,
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
knowledgeBase: runtime.knowledgeBase
|
|
422
|
-
}
|
|
411
|
+
runtime,
|
|
412
|
+
compilerVersion: "v6"
|
|
423
413
|
});
|
|
424
|
-
|
|
425
|
-
let
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
414
|
+
let compiledObservation = parsed.compiledObservation;
|
|
415
|
+
let plannerView = parsed.plannerView;
|
|
416
|
+
let mediated = applyV6ObservationMediation(compiledObservation, plannerView);
|
|
417
|
+
const verifiedRegistryEntry = capture.surfaceType === "tool_manifest"
|
|
418
|
+
? lookupVerifiedRegistryEntry(runtime, capture.toolId, capture.toolId)
|
|
419
|
+
: undefined;
|
|
420
|
+
const verifiedApiProviderEntry = "providerId" in capture && capture.providerId
|
|
421
|
+
? lookupVerifiedApiProviderEntry(runtime, capture.providerId)
|
|
422
|
+
: undefined;
|
|
423
|
+
const manifestHash = capture.surfaceType === "tool_manifest"
|
|
424
|
+
? parsed.toolManifestDigests?.manifestHash ??
|
|
425
|
+
computeToolManifestHash({
|
|
426
|
+
toolId: capture.toolId,
|
|
427
|
+
description: capture.description,
|
|
428
|
+
authType: capture.authType,
|
|
429
|
+
requestedScopes: capture.requestedScopes,
|
|
430
|
+
callbackUri: capture.callbackUri
|
|
431
|
+
})
|
|
432
|
+
: undefined;
|
|
433
|
+
const schemaHash = capture.surfaceType === "tool_manifest"
|
|
434
|
+
? parsed.toolManifestDigests?.schemaHash ??
|
|
435
|
+
computeToolSchemaHash(capture.schemaDescriptions)
|
|
436
|
+
: undefined;
|
|
437
|
+
if (modelGuardClient.configured &&
|
|
438
|
+
modelGuardClient.enforcementMode !== "off" &&
|
|
439
|
+
mediated.verdict.decision === "ALLOW") {
|
|
440
|
+
try {
|
|
441
|
+
const scored = await modelGuardClient.scoreObservation(buildModelGuardObservationRequest(sessionState.session, compiledObservation, plannerView));
|
|
442
|
+
if (modelGuardClient.enforcementMode === "shadow") {
|
|
443
|
+
compiledObservation = {
|
|
444
|
+
...compiledObservation,
|
|
445
|
+
modelAssessment: scored.assessment
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
const applied = applyModelGuardAssessment(compiledObservation, plannerView, mediated.verdict, scored.assessment);
|
|
450
|
+
compiledObservation = applied.compiledObservation;
|
|
451
|
+
plannerView = applied.plannerView;
|
|
452
|
+
mediated = {
|
|
453
|
+
plannerView,
|
|
454
|
+
verdict: applied.verdict,
|
|
455
|
+
failClosed: false
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
if (modelGuardClient.enforcementMode === "tighten") {
|
|
461
|
+
compiledObservation = {
|
|
462
|
+
...compiledObservation,
|
|
463
|
+
authorityEligible: false
|
|
464
|
+
};
|
|
465
|
+
plannerView = {
|
|
466
|
+
...plannerView,
|
|
467
|
+
visibleExcerpt: "",
|
|
468
|
+
riskMarkers: [...new Set([...plannerView.riskMarkers, "model_guard_unavailable"])]
|
|
469
|
+
};
|
|
470
|
+
mediated = {
|
|
471
|
+
plannerView,
|
|
472
|
+
verdict: {
|
|
473
|
+
decision: "REPLAN_READ_ONLY",
|
|
474
|
+
reasonCodes: ["MODEL_GUARD_UNAVAILABLE"],
|
|
475
|
+
riskScore: Math.max(0.6, compiledObservation.riskScore),
|
|
476
|
+
safeConstraints: {
|
|
477
|
+
claim_profile: "secure_v6",
|
|
478
|
+
authority_eligible: false
|
|
479
|
+
},
|
|
480
|
+
telemetryTags: ["v6_observation", "model_guard_unavailable"]
|
|
481
|
+
},
|
|
482
|
+
failClosed: false
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
}
|
|
432
486
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
sessionState.
|
|
487
|
+
const authorities = mediated.verdict.decision === "BLOCK"
|
|
488
|
+
? []
|
|
489
|
+
: mintCapabilitiesForObservationV6(sessionState.session, compiledObservation, plannerView, {
|
|
490
|
+
policy: runtime.policy,
|
|
491
|
+
...(capture.surfaceType === "tool_manifest" && verifiedRegistryEntry
|
|
492
|
+
? {
|
|
493
|
+
verifiedRegistryEntry,
|
|
494
|
+
registryEntryId: capture.toolId,
|
|
495
|
+
connectorId: capture.toolId,
|
|
496
|
+
requestedScopes: capture.requestedScopes,
|
|
497
|
+
callbackUri: capture.callbackUri,
|
|
498
|
+
callbackOrigin: capture.callbackOrigin,
|
|
499
|
+
manifestAuthType: capture.authType,
|
|
500
|
+
manifestHash,
|
|
501
|
+
schemaHash
|
|
502
|
+
}
|
|
503
|
+
: verifiedApiProviderEntry
|
|
504
|
+
? {
|
|
505
|
+
verifiedApiProviderEntry
|
|
506
|
+
}
|
|
507
|
+
: {})
|
|
508
|
+
});
|
|
509
|
+
const tightenedAuthorities = tightenAuthoritiesWithModelGuard(authorities, modelGuardClient.enforcementMode === "tighten"
|
|
510
|
+
? compiledObservation.modelAssessment
|
|
511
|
+
: undefined);
|
|
512
|
+
for (const authority of tightenedAuthorities) {
|
|
513
|
+
sessionState.authorities.set(authority.capabilityId, authority);
|
|
436
514
|
}
|
|
437
|
-
sessionState.latestObservation =
|
|
515
|
+
sessionState.latestObservation = compiledObservation;
|
|
516
|
+
sessionState.latestObservationVerdict = mediated.verdict;
|
|
517
|
+
sessionState.observations.set(compiledObservation.observationId, compiledObservation);
|
|
518
|
+
const replayEventId = appendReplayEvent(sessionState, {
|
|
519
|
+
kind: "observation",
|
|
520
|
+
actor: "sdk",
|
|
521
|
+
payload: {
|
|
522
|
+
route: requestUrl,
|
|
523
|
+
observationId: compiledObservation.observationId,
|
|
524
|
+
parseStatus: compiledObservation.parseStatus,
|
|
525
|
+
authorityCount: tightenedAuthorities.length,
|
|
526
|
+
decision: mediated.verdict.decision,
|
|
527
|
+
surfaceType: capture.surfaceType,
|
|
528
|
+
providerId: "providerId" in capture ? capture.providerId ?? null : null,
|
|
529
|
+
extractorIds: "extractionAttestation" in capture && capture.extractionAttestation
|
|
530
|
+
? [capture.extractionAttestation.extractorId]
|
|
531
|
+
: "extractionAttestations" in capture
|
|
532
|
+
? capture.extractionAttestations?.map((entry) => entry.extractorId) ?? []
|
|
533
|
+
: [],
|
|
534
|
+
modelAssessment: compiledObservation.modelAssessment
|
|
535
|
+
? {
|
|
536
|
+
bundleVersion: compiledObservation.modelAssessment.bundleVersion,
|
|
537
|
+
calibratedDecisionLabel: compiledObservation.modelAssessment.calibratedDecisionLabel,
|
|
538
|
+
coarseReasonCodes: compiledObservation.modelAssessment.coarseReasonCodes,
|
|
539
|
+
evidenceChunkIds: compiledObservation.modelAssessment.evidenceChunkIds
|
|
540
|
+
}
|
|
541
|
+
: null
|
|
542
|
+
}
|
|
543
|
+
});
|
|
438
544
|
writeJson(response, 200, {
|
|
439
|
-
compiledObservation
|
|
440
|
-
|
|
441
|
-
|
|
545
|
+
compiledObservation,
|
|
546
|
+
plannerView,
|
|
547
|
+
authorityCandidates: tightenedAuthorities.map(authorityCandidateFromDescriptor),
|
|
548
|
+
capabilities: tightenedAuthorities.map(authorityCandidateFromDescriptor),
|
|
549
|
+
artifactRefs: [],
|
|
550
|
+
observationVerdict: mediated.verdict,
|
|
551
|
+
replayEventId: replayEventId ?? randomUUID()
|
|
442
552
|
});
|
|
443
553
|
return;
|
|
444
554
|
}
|
|
445
|
-
if (
|
|
555
|
+
if (requestUrl === "/v6/action/evaluate") {
|
|
446
556
|
const payload = await readJson(request);
|
|
447
557
|
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
|
|
558
|
+
const authority = sessionState?.authorities.get(payload.authorityId);
|
|
559
|
+
const approvalEnvelope = payload.approvalId && sessionState
|
|
560
|
+
? sessionState.approvalEnvelopes.get(payload.approvalId)
|
|
561
|
+
: undefined;
|
|
562
|
+
const authorityDecision = evaluateCapabilityUseV6(payload, sessionState?.session, authority, {
|
|
563
|
+
alreadyUsed: sessionState?.usedAuthorities.has(payload.authorityId) ?? false,
|
|
564
|
+
approvalEnvelope
|
|
451
565
|
});
|
|
452
|
-
if (
|
|
453
|
-
sessionState
|
|
454
|
-
|
|
566
|
+
if (sessionState && authority && authorityDecision.decision === "ALLOW") {
|
|
567
|
+
consumeAuthority(sessionState, authority);
|
|
568
|
+
if (approvalEnvelope) {
|
|
569
|
+
sessionState.approvalEnvelopes.set(approvalEnvelope.approvalId, {
|
|
570
|
+
...approvalEnvelope,
|
|
571
|
+
consumedAt: new Date().toISOString()
|
|
572
|
+
});
|
|
573
|
+
}
|
|
455
574
|
sessionState.session = {
|
|
456
575
|
...sessionState.session,
|
|
457
576
|
currentStep: sessionState.session.currentStep + 1
|
|
458
577
|
};
|
|
459
578
|
}
|
|
579
|
+
appendReplayEvent(sessionState, {
|
|
580
|
+
kind: "action",
|
|
581
|
+
actor: "sdk",
|
|
582
|
+
payload: {
|
|
583
|
+
route: requestUrl,
|
|
584
|
+
authorityId: payload.authorityId,
|
|
585
|
+
decision: authorityDecision.decision,
|
|
586
|
+
operationClass: authority?.operationClass ?? null,
|
|
587
|
+
providerId: authority?.providerId ?? null,
|
|
588
|
+
operationId: authority?.operationId ?? null
|
|
589
|
+
}
|
|
590
|
+
});
|
|
460
591
|
writeJson(response, 200, {
|
|
461
|
-
|
|
462
|
-
|
|
592
|
+
observationDecision: sessionState?.latestObservationVerdict ?? {
|
|
593
|
+
decision: "ALLOW",
|
|
594
|
+
reasonCodes: [],
|
|
595
|
+
riskScore: 0
|
|
596
|
+
},
|
|
597
|
+
authorityDecision,
|
|
598
|
+
effectDecision: authorityDecision,
|
|
599
|
+
executionPlan: authorityDecision.decision === "ALLOW" && authority
|
|
463
600
|
? {
|
|
464
|
-
verb:
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
601
|
+
verb: authority.kind,
|
|
602
|
+
operationClass: authority.operationClass,
|
|
603
|
+
targetUrl: authority.targetUrl,
|
|
604
|
+
targetOrigin: authority.targetOrigin,
|
|
605
|
+
selector: authority.selector,
|
|
606
|
+
targetPathClass: authority.targetPathClass,
|
|
607
|
+
derivedSinkClass: authority.derivedSinkClass,
|
|
608
|
+
derivedSensitiveSink: authority.derivedSensitiveSink,
|
|
609
|
+
providerId: authority.providerId,
|
|
610
|
+
operationId: authority.operationId,
|
|
611
|
+
method: authority.method,
|
|
612
|
+
pathTemplate: authority.pathTemplate,
|
|
613
|
+
mailboxId: authority.mailboxId,
|
|
614
|
+
accountId: authority.accountId,
|
|
615
|
+
messageId: authority.messageId,
|
|
616
|
+
threadId: authority.threadId,
|
|
617
|
+
recipientSetHash: authority.recipientSetHash,
|
|
618
|
+
subjectHash: authority.subjectHash,
|
|
619
|
+
bodyDigest: authority.bodyDigest,
|
|
620
|
+
attachmentDigestSet: authority.attachmentDigestSet
|
|
470
621
|
}
|
|
471
622
|
: undefined
|
|
472
623
|
});
|
|
473
624
|
return;
|
|
474
625
|
}
|
|
475
|
-
if (
|
|
626
|
+
if (requestUrl === "/v6/approval/issue") {
|
|
476
627
|
const payload = await readJson(request);
|
|
477
628
|
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
writeJson(response, 400, {
|
|
485
|
-
error: "unknown_capability",
|
|
486
|
-
capabilityId: unknownCapability
|
|
629
|
+
const authorityId = payload.authorityId ?? payload.capabilityId ?? "";
|
|
630
|
+
const authorityDigest = payload.authorityDigest ?? payload.capabilityDigest ?? "";
|
|
631
|
+
const authority = sessionState?.authorities.get(authorityId);
|
|
632
|
+
if (!sessionState || !authority) {
|
|
633
|
+
writeJson(response, 404, {
|
|
634
|
+
error: !sessionState ? "unknown_session" : "unknown_authority"
|
|
487
635
|
});
|
|
488
636
|
return;
|
|
489
637
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
638
|
+
const signatureValid = authority.capabilityDigest === authorityDigest &&
|
|
639
|
+
verifyApprovalIntentSignatureV6(createApprovalIntentPayloadV6({
|
|
640
|
+
sessionId: sessionState.session.sessionId,
|
|
641
|
+
workflowHash: sessionState.session.workflowHash,
|
|
642
|
+
capabilityId: authority.capabilityId,
|
|
643
|
+
capabilityDigest: authority.capabilityDigest,
|
|
644
|
+
expiresInSeconds: payload.expiresInSeconds
|
|
645
|
+
}), payload.brokerSignature, runtime.approvalBrokerPublicKey);
|
|
646
|
+
const issued = issueApprovalEnvelopeV6({
|
|
647
|
+
session: sessionState.session,
|
|
648
|
+
capability: authority,
|
|
649
|
+
brokerSignature: payload.brokerSignature,
|
|
650
|
+
brokerSignatureVerified: signatureValid,
|
|
651
|
+
expiresInSeconds: payload.expiresInSeconds
|
|
652
|
+
});
|
|
653
|
+
if (issued.approvalEnvelope) {
|
|
654
|
+
sessionState.approvalEnvelopes.set(issued.approvalEnvelope.approvalId, issued.approvalEnvelope);
|
|
497
655
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
656
|
+
appendReplayEvent(sessionState, {
|
|
657
|
+
kind: "tool",
|
|
658
|
+
actor: "sdk",
|
|
659
|
+
payload: {
|
|
660
|
+
route: requestUrl,
|
|
661
|
+
authorityId,
|
|
662
|
+
decision: issued.verdict.decision
|
|
663
|
+
}
|
|
502
664
|
});
|
|
665
|
+
writeJson(response, 200, issued);
|
|
503
666
|
return;
|
|
504
667
|
}
|
|
505
|
-
if (
|
|
668
|
+
if (requestUrl === "/v6/tool/prepare") {
|
|
506
669
|
const payload = await readJson(request);
|
|
507
670
|
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
508
|
-
const
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
? createOnboardingSession(payload.request, runtime, approvalGrant, prepared.verifiedRegistryEntry)
|
|
671
|
+
const approvalEnvelope = sessionState?.approvalEnvelopes.get(payload.approvalId);
|
|
672
|
+
const authority = approvalEnvelope && sessionState
|
|
673
|
+
? sessionState.authorities.get(approvalEnvelope.capabilityId)
|
|
674
|
+
: undefined;
|
|
675
|
+
const verifiedRegistryEntry = authority?.registryEntryId
|
|
676
|
+
? lookupVerifiedRegistryEntry(runtime, authority.connectorId, authority.registryEntryId)
|
|
515
677
|
: undefined;
|
|
516
|
-
|
|
517
|
-
sessionState
|
|
678
|
+
const prepared = prepareToolOnboardingV6({
|
|
679
|
+
session: sessionState?.session,
|
|
680
|
+
capability: authority,
|
|
681
|
+
approvalEnvelope,
|
|
682
|
+
verifiedRegistryEntry
|
|
683
|
+
});
|
|
684
|
+
if (sessionState && approvalEnvelope && authority && prepared.onboardingSession) {
|
|
685
|
+
sessionState.onboardingSessions.set(prepared.onboardingSession.onboardingSessionId, prepared.onboardingSession);
|
|
686
|
+
sessionState.approvalEnvelopes.set(approvalEnvelope.approvalId, {
|
|
687
|
+
...approvalEnvelope,
|
|
688
|
+
consumedAt: new Date().toISOString(),
|
|
689
|
+
onboardingSessionId: prepared.onboardingSession.onboardingSessionId
|
|
690
|
+
});
|
|
691
|
+
consumeAuthority(sessionState, authority);
|
|
692
|
+
sessionState.session = {
|
|
693
|
+
...sessionState.session,
|
|
694
|
+
currentStep: sessionState.session.currentStep + 1
|
|
695
|
+
};
|
|
518
696
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
697
|
+
appendReplayEvent(sessionState, {
|
|
698
|
+
kind: "tool",
|
|
699
|
+
actor: "sdk",
|
|
700
|
+
payload: {
|
|
701
|
+
route: requestUrl,
|
|
702
|
+
approvalId: payload.approvalId,
|
|
703
|
+
decision: prepared.verdict.decision
|
|
704
|
+
}
|
|
525
705
|
});
|
|
706
|
+
writeJson(response, 200, prepared);
|
|
526
707
|
return;
|
|
527
708
|
}
|
|
528
|
-
if (
|
|
709
|
+
if (requestUrl === "/v6/tool/callback/verify") {
|
|
529
710
|
const payload = await readJson(request);
|
|
530
711
|
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
531
|
-
const
|
|
532
|
-
const
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
712
|
+
const approvalEnvelope = sessionState?.approvalEnvelopes.get(payload.approvalId);
|
|
713
|
+
const onboardingSession = sessionState?.onboardingSessions.get(payload.onboardingSessionId);
|
|
714
|
+
const authority = approvalEnvelope && sessionState
|
|
715
|
+
? sessionState.authorities.get(approvalEnvelope.capabilityId)
|
|
716
|
+
: undefined;
|
|
717
|
+
const verifiedRegistryEntry = authority?.registryEntryId
|
|
718
|
+
? lookupVerifiedRegistryEntry(runtime, authority.connectorId, authority.registryEntryId)
|
|
719
|
+
: undefined;
|
|
720
|
+
const verified = verifyToolCallbackV6({
|
|
721
|
+
session: sessionState?.session,
|
|
722
|
+
capability: authority,
|
|
723
|
+
approvalEnvelope,
|
|
724
|
+
onboardingSession,
|
|
725
|
+
verifiedRegistryEntry,
|
|
726
|
+
request: payload.request
|
|
727
|
+
});
|
|
728
|
+
if (sessionState && onboardingSession && verified.connectorHandle) {
|
|
729
|
+
sessionState.onboardingSessions.set(onboardingSession.onboardingSessionId, {
|
|
730
|
+
...onboardingSession,
|
|
731
|
+
status: "used"
|
|
538
732
|
});
|
|
733
|
+
sessionState.connectorHandles.set(verified.connectorHandle.handleId, verified.connectorHandle);
|
|
539
734
|
}
|
|
540
|
-
|
|
735
|
+
appendReplayEvent(sessionState, {
|
|
736
|
+
kind: "tool",
|
|
737
|
+
actor: "sdk",
|
|
738
|
+
payload: {
|
|
739
|
+
route: requestUrl,
|
|
740
|
+
onboardingSessionId: payload.onboardingSessionId,
|
|
741
|
+
decision: verified.verdict.decision
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
writeJson(response, 200, verified);
|
|
541
745
|
return;
|
|
542
746
|
}
|
|
543
|
-
if (
|
|
747
|
+
if (requestUrl === "/v6/artifact/ingest") {
|
|
544
748
|
const payload = await readJson(request);
|
|
545
749
|
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
546
750
|
if (!sessionState) {
|
|
@@ -552,81 +756,194 @@ export async function createSafeBrowseServer(options = {}) {
|
|
|
552
756
|
sessionId: payload.sessionId,
|
|
553
757
|
taskId: sessionState.session.taskId
|
|
554
758
|
};
|
|
555
|
-
const
|
|
759
|
+
const parsed = await parserIsolationService.compileObservation({
|
|
556
760
|
capture,
|
|
557
761
|
workflowHash: sessionState.session.workflowHash,
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
762
|
+
runtime,
|
|
763
|
+
compilerVersion: "v6"
|
|
764
|
+
});
|
|
765
|
+
const mediated = applyV6ObservationMediation(parsed.compiledObservation, parsed.plannerView);
|
|
766
|
+
const artifactRef = buildArtifactRef(capture, parsed.compiledObservation);
|
|
767
|
+
sessionState.latestObservation = parsed.compiledObservation;
|
|
768
|
+
sessionState.latestObservationVerdict = mediated.verdict;
|
|
769
|
+
sessionState.observations.set(parsed.compiledObservation.observationId, parsed.compiledObservation);
|
|
770
|
+
const replayEventId = appendReplayEvent(sessionState, {
|
|
771
|
+
kind: "artifact",
|
|
772
|
+
actor: "sdk",
|
|
773
|
+
payload: {
|
|
774
|
+
route: requestUrl,
|
|
775
|
+
observationId: parsed.compiledObservation.observationId,
|
|
776
|
+
decision: mediated.verdict.decision,
|
|
777
|
+
surfaceType: capture.surfaceType,
|
|
778
|
+
providerId: "providerId" in capture ? capture.providerId ?? null : null,
|
|
779
|
+
extractorIds: "extractionAttestation" in capture && capture.extractionAttestation
|
|
780
|
+
? [capture.extractionAttestation.extractorId]
|
|
781
|
+
: []
|
|
561
782
|
}
|
|
562
783
|
});
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
:
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
784
|
+
writeJson(response, 200, {
|
|
785
|
+
compiledObservation: parsed.compiledObservation,
|
|
786
|
+
plannerView: mediated.plannerView,
|
|
787
|
+
artifactRef,
|
|
788
|
+
mismatchSignals: artifactRef.mismatchSignals,
|
|
789
|
+
artifactVerdict: mediated.verdict,
|
|
790
|
+
replayEventId: replayEventId ?? randomUUID()
|
|
791
|
+
});
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (requestUrl === "/v6/artifact/extract") {
|
|
795
|
+
const payload = await readJson(request);
|
|
796
|
+
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
797
|
+
if (!sessionState) {
|
|
798
|
+
writeJson(response, 404, { error: "unknown_session" });
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const result = extractAttachmentGraphV6({
|
|
802
|
+
...payload.capture,
|
|
803
|
+
sessionId: payload.sessionId,
|
|
804
|
+
taskId: sessionState.session.taskId
|
|
805
|
+
}, runtime);
|
|
806
|
+
const replayEventId = appendReplayEvent(sessionState, {
|
|
807
|
+
kind: "artifact",
|
|
808
|
+
actor: "sdk",
|
|
809
|
+
payload: {
|
|
810
|
+
route: requestUrl,
|
|
811
|
+
rootNodeCount: result.artifactGraph.rootNodeIds.length,
|
|
812
|
+
blockedChildren: result.blockedChildren.length,
|
|
813
|
+
unsupportedChildren: result.unsupportedChildren.length,
|
|
814
|
+
extractorIds: result.extractionAttestations.map((entry) => entry.extractorId),
|
|
815
|
+
decision: result.artifactVerdict.decision
|
|
575
816
|
}
|
|
576
|
-
|
|
577
|
-
const plannerInput = shouldReduceAuthority(sessionState, observation.compiledObservation)
|
|
578
|
-
? applyAuthorityReduction(sessionState, observation.compiledObservation, failClosedArtifact.plannerInput)
|
|
579
|
-
: failClosedArtifact.plannerInput;
|
|
817
|
+
});
|
|
580
818
|
writeJson(response, 200, {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
artifactVerdict: effectiveArtifactVerdict,
|
|
584
|
-
artifact: effectiveArtifact
|
|
819
|
+
...result,
|
|
820
|
+
replayEventId: replayEventId ?? randomUUID()
|
|
585
821
|
});
|
|
586
822
|
return;
|
|
587
823
|
}
|
|
588
|
-
if (
|
|
824
|
+
if (requestUrl === "/v6/memory/stage") {
|
|
589
825
|
const payload = await readJson(request);
|
|
590
826
|
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
591
|
-
const result =
|
|
827
|
+
const result = stageMemoryRecordV6(payload, sessionState?.session);
|
|
828
|
+
let promotionAuthority;
|
|
592
829
|
if (sessionState && result.record) {
|
|
593
830
|
sessionState.memoryRecords.set(result.record.recordId, result.record);
|
|
831
|
+
sessionState.memorySourceClasses.set(result.record.recordId, payload.sourceClass);
|
|
832
|
+
promotionAuthority = mintMemoryPromotionCapabilityV6(sessionState.session, {
|
|
833
|
+
recordId: result.record.recordId,
|
|
834
|
+
sourceDigest: result.record.sourceDigest,
|
|
835
|
+
sourceObservationId: result.record.sourceObservationId,
|
|
836
|
+
key: result.record.key,
|
|
837
|
+
valueDigest: result.record.sourceDigest ?? hashValue(result.record.value)
|
|
838
|
+
});
|
|
839
|
+
sessionState.authorities.set(promotionAuthority.capabilityId, promotionAuthority);
|
|
594
840
|
}
|
|
595
|
-
|
|
841
|
+
appendReplayEvent(sessionState, {
|
|
842
|
+
kind: "memory",
|
|
843
|
+
actor: "sdk",
|
|
844
|
+
payload: {
|
|
845
|
+
route: requestUrl,
|
|
846
|
+
recordId: result.record?.recordId ?? null,
|
|
847
|
+
decision: result.verdict.decision
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
writeJson(response, 200, {
|
|
851
|
+
...result,
|
|
852
|
+
promotionAuthority: promotionAuthority
|
|
853
|
+
? authorityCandidateFromDescriptor(promotionAuthority)
|
|
854
|
+
: undefined,
|
|
855
|
+
promotionTicket: promotionAuthority
|
|
856
|
+
? {
|
|
857
|
+
ticketId: promotionAuthority.capabilityId,
|
|
858
|
+
ticketDigest: promotionAuthority.capabilityDigest,
|
|
859
|
+
semanticDigest: promotionAuthority.semanticDigest,
|
|
860
|
+
recordId: result.record?.recordId,
|
|
861
|
+
sourceClass: payload.sourceClass,
|
|
862
|
+
expiresAt: promotionAuthority.expiresAt
|
|
863
|
+
}
|
|
864
|
+
: undefined
|
|
865
|
+
});
|
|
596
866
|
return;
|
|
597
867
|
}
|
|
598
|
-
if (
|
|
868
|
+
if (requestUrl === "/v6/memory/promote") {
|
|
599
869
|
const payload = await readJson(request);
|
|
600
870
|
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
601
871
|
const record = sessionState?.memoryRecords.get(payload.recordId);
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const result =
|
|
872
|
+
const authority = sessionState?.authorities.get(payload.ticketId);
|
|
873
|
+
const approvalEnvelope = sessionState?.approvalEnvelopes.get(payload.approvalId);
|
|
874
|
+
const snapshotState = sessionState && record ? buildMemorySnapshotState(sessionState, record) : undefined;
|
|
875
|
+
const result = promoteMemoryRecordV6(payload, sessionState?.session, record, authority, approvalEnvelope, {
|
|
876
|
+
sourceClass: record
|
|
877
|
+
? sessionState?.memorySourceClasses.get(record.recordId)
|
|
878
|
+
: undefined,
|
|
879
|
+
priorTrustedRecord: snapshotState?.snapshotRecord
|
|
880
|
+
});
|
|
606
881
|
if (sessionState && result.promotedRecord) {
|
|
607
|
-
if (
|
|
882
|
+
if (snapshotState && result.promotedRecord.snapshotId) {
|
|
608
883
|
sessionState.memorySnapshots.set(result.promotedRecord.snapshotId, {
|
|
609
|
-
...
|
|
610
|
-
|
|
611
|
-
summaryOnly: false,
|
|
612
|
-
snapshotId: result.promotedRecord.snapshotId,
|
|
613
|
-
rollbackPointId: result.promotedRecord.rollbackPointId
|
|
884
|
+
...snapshotState,
|
|
885
|
+
snapshotId: result.promotedRecord.snapshotId
|
|
614
886
|
});
|
|
615
887
|
}
|
|
616
888
|
sessionState.memoryRecords.set(result.promotedRecord.recordId, result.promotedRecord);
|
|
889
|
+
if (authority) {
|
|
890
|
+
consumeAuthority(sessionState, authority);
|
|
891
|
+
}
|
|
892
|
+
if (approvalEnvelope) {
|
|
893
|
+
sessionState.approvalEnvelopes.set(payload.approvalId, {
|
|
894
|
+
...approvalEnvelope,
|
|
895
|
+
consumedAt: new Date().toISOString()
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
sessionState.session = {
|
|
899
|
+
...sessionState.session,
|
|
900
|
+
currentStep: sessionState.session.currentStep + 1
|
|
901
|
+
};
|
|
617
902
|
}
|
|
618
|
-
|
|
903
|
+
appendReplayEvent(sessionState, {
|
|
904
|
+
kind: "memory",
|
|
905
|
+
actor: "sdk",
|
|
906
|
+
payload: {
|
|
907
|
+
route: requestUrl,
|
|
908
|
+
recordId: payload.recordId,
|
|
909
|
+
decision: result.verdict.decision
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
writeJson(response, 200, {
|
|
913
|
+
...result,
|
|
914
|
+
approvalEnvelope: payload.approvalId && sessionState
|
|
915
|
+
? sessionState.approvalEnvelopes.get(payload.approvalId)
|
|
916
|
+
: approvalEnvelope
|
|
917
|
+
});
|
|
619
918
|
return;
|
|
620
919
|
}
|
|
621
|
-
if (
|
|
920
|
+
if (requestUrl === "/v6/memory/rollback") {
|
|
622
921
|
const payload = await readJson(request);
|
|
623
922
|
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
624
923
|
const record = sessionState?.memoryRecords.get(payload.recordId);
|
|
625
|
-
const
|
|
626
|
-
const result =
|
|
627
|
-
|
|
628
|
-
|
|
924
|
+
const snapshotState = sessionState?.memorySnapshots.get(payload.snapshotId);
|
|
925
|
+
const result = rollbackMemoryRecordV6(payload, sessionState?.session, record, {
|
|
926
|
+
snapshotRecord: snapshotState?.snapshotRecord,
|
|
927
|
+
baselineAbsent: snapshotState?.baselineAbsent
|
|
928
|
+
});
|
|
929
|
+
if (sessionState && result.verdict.decision === "ALLOW") {
|
|
930
|
+
if (result.restoredRecord) {
|
|
931
|
+
sessionState.memoryRecords.set(result.restoredRecord.recordId, result.restoredRecord);
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
sessionState.memoryRecords.delete(payload.recordId);
|
|
935
|
+
}
|
|
629
936
|
}
|
|
937
|
+
appendReplayEvent(sessionState, {
|
|
938
|
+
kind: "memory",
|
|
939
|
+
actor: "sdk",
|
|
940
|
+
payload: {
|
|
941
|
+
route: requestUrl,
|
|
942
|
+
recordId: payload.recordId,
|
|
943
|
+
snapshotId: payload.snapshotId,
|
|
944
|
+
decision: result.verdict.decision
|
|
945
|
+
}
|
|
946
|
+
});
|
|
630
947
|
writeJson(response, 200, {
|
|
631
948
|
...result,
|
|
632
949
|
rollbackEvent: result.verdict.decision === "ALLOW"
|
|
@@ -639,15 +956,35 @@ export async function createSafeBrowseServer(options = {}) {
|
|
|
639
956
|
});
|
|
640
957
|
return;
|
|
641
958
|
}
|
|
959
|
+
if (requestUrl === "/v6/replay/bundle") {
|
|
960
|
+
const payload = await readJson(request);
|
|
961
|
+
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
962
|
+
if (!sessionState) {
|
|
963
|
+
writeJson(response, 404, { error: "unknown_session" });
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
writeJson(response, 200, buildReplayBundle(sessionState.replayEvents, runtime));
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
642
969
|
writeJson(response, 404, { error: "not_found" });
|
|
643
970
|
}
|
|
644
971
|
catch (error) {
|
|
972
|
+
logServerError(error);
|
|
645
973
|
writeJson(response, 500, {
|
|
646
|
-
error: "server_error"
|
|
647
|
-
message: error instanceof Error ? error.message : String(error)
|
|
974
|
+
error: "server_error"
|
|
648
975
|
});
|
|
649
976
|
}
|
|
650
977
|
});
|
|
978
|
+
const originalClose = server.close.bind(server);
|
|
979
|
+
server.close = ((callback) => {
|
|
980
|
+
clearInterval(parserHealthRefreshTimer);
|
|
981
|
+
return originalClose((error) => {
|
|
982
|
+
void Promise.allSettled([parserIsolationService.close(), modelGuardClient.close()]).finally(() => {
|
|
983
|
+
callback?.(error);
|
|
984
|
+
});
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
return server;
|
|
651
988
|
}
|
|
652
989
|
export async function startSafeBrowseDaemon(options = {}) {
|
|
653
990
|
const server = await createSafeBrowseServer(options);
|