@safebrowse/daemon 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parserIsolation.d.ts +12 -0
- package/dist/parserIsolation.d.ts.map +1 -0
- package/dist/parserIsolation.js +57 -0
- package/dist/parserIsolation.js.map +1 -0
- package/dist/parserWorker.d.ts +2 -0
- package/dist/parserWorker.d.ts.map +1 -0
- package/dist/parserWorker.js +89 -0
- package/dist/parserWorker.js.map +1 -0
- package/dist/runtime/config/auditor/v4_prompt_injection_coverage_suite.json +2789 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +489 -22
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/server.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface SafeBrowseDaemonOptions {
|
|
|
8
8
|
policyPack?: PolicyPack;
|
|
9
9
|
knowledgeBase?: KnowledgeBaseContext;
|
|
10
10
|
verifiedRegistry?: VerifiedRegistryBundle;
|
|
11
|
+
parserAllowlistedEgress?: string[];
|
|
11
12
|
}
|
|
12
13
|
export declare function createSafeBrowseServer(options?: SafeBrowseDaemonOptions): Promise<Server>;
|
|
13
14
|
export declare function startSafeBrowseDaemon(options?: SafeBrowseDaemonOptions): Promise<Server>;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AAIjG,OAAO,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AAIjG,OAAO,EA4BL,KAAK,oBAAoB,EAKzB,KAAK,UAAU,EAShB,MAAM,kBAAkB,CAAC;AAS1B,OAAO,KAAK,EAAE,sBAAsB,EAAyB,MAAM,kBAAkB,CAAC;AAEtF,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAC1C,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;CACpC;AAgYD,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,MAAM,CAAC,CA+fjB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,MAAM,CAAC,CAWjB"}
|
package/dist/server.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { 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 { brokerArtifact, brokerArtifactV2, buildReplayBundle, compilePolicy, evaluateAction, evaluateMemoryWrite, evaluateToolRequest, prepareToolOnboarding, sanitizeObservation, verifyToolCallback } from "@safebrowse/core";
|
|
6
|
+
import { applyV4FailClosedMediation, attachCapabilitiesToPlannerInput, brokerArtifact, brokerArtifactV2, buildReplayBundle, compilePolicy, createApprovalGrantHash, evaluateAction, evaluateCapabilityUse, evaluateMemoryWrite, evaluateMemoryWriteV4, evaluateToolRequest, mintCapabilitiesForObservation, prepareToolOnboarding, prepareToolOnboardingV4, promoteMemoryRecordV4, rollbackMemoryRecordV4, sanitizeObservation, verifyToolCallback, verifyToolCallbackV4 } from "@safebrowse/core";
|
|
7
7
|
import { buildRegistryDefaults, loadKnowledgeBaseContext, loadVerifiedRegistryBundle, loadPolicyPackFromPaths, resolvePolicyLayerFiles } from "./loaders.js";
|
|
8
|
+
import { compileObservationInIsolation, probeParserIsolation } from "./parserIsolation.js";
|
|
9
|
+
function hashValue(input) {
|
|
10
|
+
return createHash("sha256").update(JSON.stringify(input)).digest("hex");
|
|
11
|
+
}
|
|
8
12
|
async function readJson(request) {
|
|
9
13
|
const chunks = [];
|
|
10
14
|
for await (const chunk of request) {
|
|
@@ -17,6 +21,17 @@ function writeJson(response, statusCode, payload) {
|
|
|
17
21
|
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
18
22
|
response.end(JSON.stringify(payload, null, 2));
|
|
19
23
|
}
|
|
24
|
+
function legacyResponseMeta(route) {
|
|
25
|
+
return {
|
|
26
|
+
deprecated: true,
|
|
27
|
+
telemetry: {
|
|
28
|
+
deprecated: true,
|
|
29
|
+
claimScope: "legacy_compatibility",
|
|
30
|
+
preventionClaim: false,
|
|
31
|
+
routeVersion: route
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
20
35
|
async function fileExists(path) {
|
|
21
36
|
try {
|
|
22
37
|
await stat(path);
|
|
@@ -45,31 +60,122 @@ async function buildRuntimeContext(options) {
|
|
|
45
60
|
return {
|
|
46
61
|
policy: compilePolicy(policyPack),
|
|
47
62
|
knowledgeBase,
|
|
48
|
-
verifiedRegistry
|
|
63
|
+
verifiedRegistry,
|
|
64
|
+
parserAllowlistedEgress: options.parserAllowlistedEgress ?? []
|
|
49
65
|
};
|
|
50
66
|
}
|
|
51
67
|
function plusMinutes(value, minutes) {
|
|
52
68
|
return new Date(new Date(value).getTime() + minutes * 60_000).toISOString();
|
|
53
69
|
}
|
|
54
|
-
function
|
|
55
|
-
|
|
70
|
+
function plusSeconds(value, seconds) {
|
|
71
|
+
return new Date(new Date(value).getTime() + seconds * 1_000).toISOString();
|
|
72
|
+
}
|
|
73
|
+
function createWorkflowHash(payload) {
|
|
74
|
+
return hashValue({
|
|
75
|
+
taskId: payload.taskId,
|
|
76
|
+
userGoal: payload.userGoal,
|
|
77
|
+
phase: payload.phase ?? "",
|
|
78
|
+
allowedOrigins: payload.allowedOrigins ?? [],
|
|
79
|
+
allowedVerbs: payload.allowedVerbs ?? [],
|
|
80
|
+
forbiddenSinks: payload.forbiddenSinks ?? []
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function createSessionState(request, runtime) {
|
|
84
|
+
const createdAt = new Date().toISOString();
|
|
85
|
+
const allowedOrigins = request.allowedOrigins ??
|
|
86
|
+
[...runtime.policy.readOnlyOrigins, ...runtime.policy.writableOrigins];
|
|
87
|
+
const allowedVerbs = request.allowedVerbs ?? [...runtime.policy.allowedActions];
|
|
88
|
+
const forbiddenSinks = request.forbiddenSinks ?? [];
|
|
89
|
+
const session = {
|
|
90
|
+
sessionId: randomUUID(),
|
|
91
|
+
taskId: request.taskId,
|
|
92
|
+
userGoal: request.userGoal,
|
|
93
|
+
phase: request.phase,
|
|
94
|
+
allowedOrigins,
|
|
95
|
+
allowedVerbs,
|
|
96
|
+
forbiddenSinks,
|
|
97
|
+
workflowHash: createWorkflowHash({
|
|
98
|
+
taskId: request.taskId,
|
|
99
|
+
userGoal: request.userGoal,
|
|
100
|
+
phase: request.phase,
|
|
101
|
+
allowedOrigins,
|
|
102
|
+
allowedVerbs,
|
|
103
|
+
forbiddenSinks
|
|
104
|
+
}),
|
|
105
|
+
currentStep: 0,
|
|
106
|
+
createdAt,
|
|
107
|
+
expiresAt: plusSeconds(createdAt, request.expiresInSeconds ?? 1800)
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
session,
|
|
111
|
+
capabilities: new Map(),
|
|
112
|
+
usedCapabilities: new Set(),
|
|
113
|
+
authorityReduced: false,
|
|
114
|
+
authorityReductionReasons: [],
|
|
115
|
+
approvalGrants: new Map(),
|
|
116
|
+
memoryRecords: new Map(),
|
|
117
|
+
memorySnapshots: new Map(),
|
|
118
|
+
onboardingSessions: new Map()
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function shouldReduceAuthority(sessionState, observation) {
|
|
122
|
+
if (sessionState.authorityReduced) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
const hasPriorSurface = Boolean(sessionState.latestObservation);
|
|
126
|
+
const hasMeaningfulRisk = observation.parseStatus !== "compiled" ||
|
|
127
|
+
observation.riskFindings.length > 0 ||
|
|
128
|
+
observation.secretFindings.length > 0;
|
|
129
|
+
return hasPriorSurface && hasMeaningfulRisk;
|
|
130
|
+
}
|
|
131
|
+
function applyAuthorityReduction(sessionState, observation, plannerInput) {
|
|
132
|
+
const reasons = [
|
|
133
|
+
...sessionState.authorityReductionReasons,
|
|
134
|
+
...(observation.parseStatus !== "compiled"
|
|
135
|
+
? [`parse_status_${observation.parseStatus}`]
|
|
136
|
+
: []),
|
|
137
|
+
...observation.riskFindings,
|
|
138
|
+
...(observation.secretFindings.length ? ["secret_redaction_boundary"] : [])
|
|
139
|
+
];
|
|
140
|
+
sessionState.authorityReduced = true;
|
|
141
|
+
sessionState.authorityReductionReasons = [...new Set(reasons)];
|
|
142
|
+
sessionState.capabilities.clear();
|
|
143
|
+
return {
|
|
144
|
+
...plannerInput,
|
|
145
|
+
candidateCapabilities: [],
|
|
146
|
+
riskMarkers: [
|
|
147
|
+
...new Set([
|
|
148
|
+
...plannerInput.riskMarkers,
|
|
149
|
+
"multimodal_reducer_active",
|
|
150
|
+
...sessionState.authorityReductionReasons.map((reason) => `chain:${reason}`)
|
|
151
|
+
])
|
|
152
|
+
]
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function createOnboardingSession(request, runtime, approvalGrant, verifiedRegistryEntry) {
|
|
156
|
+
const callbackUri = verifiedRegistryEntry?.allowedRedirectUris[0] ??
|
|
157
|
+
request.oauthContext?.callbackUri ??
|
|
56
158
|
request.callbackUri ??
|
|
57
159
|
request.oauthContext?.redirectUri ??
|
|
58
160
|
request.requestedRedirectUri;
|
|
59
161
|
if (!callbackUri) {
|
|
60
162
|
return undefined;
|
|
61
163
|
}
|
|
164
|
+
const callbackOrigin = verifiedRegistryEntry?.allowedCallbackOrigins[0] ?? new URL(callbackUri).origin;
|
|
62
165
|
const createdAt = (runtime.now?.() ?? new Date()).toISOString();
|
|
63
166
|
return {
|
|
64
167
|
sessionId: randomUUID(),
|
|
65
|
-
approvalBindingId:
|
|
66
|
-
workflowBindingId,
|
|
168
|
+
approvalBindingId: approvalGrant.approvalGrantId,
|
|
169
|
+
workflowBindingId: request.sourceObservationId,
|
|
67
170
|
toolId: request.toolId,
|
|
68
|
-
registryEntryId: request.registryEntryId ?? request.toolId,
|
|
69
|
-
registryBundleId:
|
|
171
|
+
registryEntryId: verifiedRegistryEntry?.registryEntryId ?? request.registryEntryId ?? request.toolId,
|
|
172
|
+
registryBundleId: verifiedRegistryEntry?.bundleId ??
|
|
173
|
+
request.registryBundleId ??
|
|
174
|
+
runtime.verifiedRegistry?.bundleId ??
|
|
175
|
+
"unverified-registry",
|
|
70
176
|
callbackUri,
|
|
71
|
-
callbackOrigin
|
|
72
|
-
requestedScopes:
|
|
177
|
+
callbackOrigin,
|
|
178
|
+
requestedScopes: approvalGrant.scopes,
|
|
73
179
|
state: randomUUID(),
|
|
74
180
|
pkceMethod: "S256",
|
|
75
181
|
createdAt,
|
|
@@ -77,9 +183,80 @@ function createOnboardingSession(request, runtime, workflowBindingId) {
|
|
|
77
183
|
status: "prepared"
|
|
78
184
|
};
|
|
79
185
|
}
|
|
186
|
+
function issueApprovalGrant(request, sessionState) {
|
|
187
|
+
const issuedAt = new Date().toISOString();
|
|
188
|
+
const grantWithoutHash = {
|
|
189
|
+
approvalGrantId: randomUUID(),
|
|
190
|
+
sessionId: sessionState.session.sessionId,
|
|
191
|
+
workflowHash: sessionState.session.workflowHash,
|
|
192
|
+
connectorId: request.connectorId,
|
|
193
|
+
scopes: request.scopes ?? [],
|
|
194
|
+
sinkClass: request.sinkClass,
|
|
195
|
+
capabilityIds: request.capabilityIds ?? [],
|
|
196
|
+
targetOrigin: request.targetOrigin,
|
|
197
|
+
issuedAt,
|
|
198
|
+
expiresAt: plusSeconds(issuedAt, request.expiresInSeconds ?? 600)
|
|
199
|
+
};
|
|
200
|
+
return {
|
|
201
|
+
...grantWithoutHash,
|
|
202
|
+
grantHash: createApprovalGrantHash(grantWithoutHash)
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function findSessionState(sessions, sessionId) {
|
|
206
|
+
const sessionState = sessions.get(sessionId);
|
|
207
|
+
if (!sessionState) {
|
|
208
|
+
return undefined;
|
|
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;
|
|
255
|
+
}
|
|
80
256
|
export async function createSafeBrowseServer(options = {}) {
|
|
81
257
|
const runtime = await buildRuntimeContext(options);
|
|
82
258
|
const onboardingSessions = new Map();
|
|
259
|
+
const sessions = new Map();
|
|
83
260
|
return createServer(async (request, response) => {
|
|
84
261
|
if (!request.url) {
|
|
85
262
|
writeJson(response, 400, { error: "missing_url" });
|
|
@@ -87,6 +264,7 @@ export async function createSafeBrowseServer(options = {}) {
|
|
|
87
264
|
}
|
|
88
265
|
try {
|
|
89
266
|
if (request.method === "GET" && request.url === "/health") {
|
|
267
|
+
const parserProbe = await probeParserIsolation();
|
|
90
268
|
writeJson(response, 200, {
|
|
91
269
|
status: "ok",
|
|
92
270
|
profile: runtime.policy.profile,
|
|
@@ -99,7 +277,8 @@ export async function createSafeBrowseServer(options = {}) {
|
|
|
99
277
|
signatureVerified: runtime.verifiedRegistry.signatureVerified,
|
|
100
278
|
entryCount: runtime.verifiedRegistry.entries.length
|
|
101
279
|
}
|
|
102
|
-
: undefined
|
|
280
|
+
: undefined,
|
|
281
|
+
parserIsolation: parserProbe
|
|
103
282
|
});
|
|
104
283
|
return;
|
|
105
284
|
}
|
|
@@ -109,39 +288,73 @@ export async function createSafeBrowseServer(options = {}) {
|
|
|
109
288
|
}
|
|
110
289
|
if (request.url === "/v1/observe") {
|
|
111
290
|
const payload = await readJson(request);
|
|
112
|
-
writeJson(response, 200,
|
|
291
|
+
writeJson(response, 200, {
|
|
292
|
+
...sanitizeObservation(payload, runtime),
|
|
293
|
+
...legacyResponseMeta("/v1/observe")
|
|
294
|
+
});
|
|
113
295
|
return;
|
|
114
296
|
}
|
|
115
297
|
if (request.url === "/v1/action") {
|
|
116
298
|
const payload = await readJson(request);
|
|
117
|
-
writeJson(response, 200,
|
|
299
|
+
writeJson(response, 200, {
|
|
300
|
+
...evaluateAction(payload, runtime),
|
|
301
|
+
...legacyResponseMeta("/v1/action")
|
|
302
|
+
});
|
|
118
303
|
return;
|
|
119
304
|
}
|
|
120
305
|
if (request.url === "/v1/artifact") {
|
|
121
306
|
const payload = await readJson(request);
|
|
122
|
-
writeJson(response, 200,
|
|
307
|
+
writeJson(response, 200, {
|
|
308
|
+
...brokerArtifact(payload, runtime),
|
|
309
|
+
...legacyResponseMeta("/v1/artifact")
|
|
310
|
+
});
|
|
123
311
|
return;
|
|
124
312
|
}
|
|
125
313
|
if (request.url === "/v1/tool") {
|
|
126
314
|
const payload = await readJson(request);
|
|
127
|
-
writeJson(response, 200,
|
|
315
|
+
writeJson(response, 200, {
|
|
316
|
+
...evaluateToolRequest(payload, runtime),
|
|
317
|
+
...legacyResponseMeta("/v1/tool")
|
|
318
|
+
});
|
|
128
319
|
return;
|
|
129
320
|
}
|
|
130
321
|
if (request.url === "/v1/memory") {
|
|
131
322
|
const payload = await readJson(request);
|
|
132
|
-
writeJson(response, 200,
|
|
323
|
+
writeJson(response, 200, {
|
|
324
|
+
...evaluateMemoryWrite(payload, runtime),
|
|
325
|
+
...legacyResponseMeta("/v1/memory")
|
|
326
|
+
});
|
|
133
327
|
return;
|
|
134
328
|
}
|
|
135
329
|
if (request.url === "/v1/replay") {
|
|
136
330
|
const payload = await readJson(request);
|
|
137
|
-
writeJson(response, 200,
|
|
331
|
+
writeJson(response, 200, {
|
|
332
|
+
...buildReplayBundle(payload.events, runtime),
|
|
333
|
+
...legacyResponseMeta("/v1/replay")
|
|
334
|
+
});
|
|
138
335
|
return;
|
|
139
336
|
}
|
|
140
337
|
if (request.url === "/v2/tool/prepare") {
|
|
141
338
|
const payload = await readJson(request);
|
|
142
339
|
const prepared = prepareToolOnboarding(payload, runtime);
|
|
143
340
|
const onboardingSession = prepared.verdict.decision === "ALLOW" && payload.authType === "oauth"
|
|
144
|
-
? createOnboardingSession(payload, runtime,
|
|
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
|
+
})
|
|
145
358
|
: undefined;
|
|
146
359
|
if (onboardingSession) {
|
|
147
360
|
onboardingSessions.set(onboardingSession.sessionId, onboardingSession);
|
|
@@ -150,7 +363,8 @@ export async function createSafeBrowseServer(options = {}) {
|
|
|
150
363
|
verdict: prepared.verdict,
|
|
151
364
|
verifiedRegistryEntry: prepared.verifiedRegistryEntry,
|
|
152
365
|
workflowBinding: prepared.workflowBinding,
|
|
153
|
-
onboardingSession
|
|
366
|
+
onboardingSession,
|
|
367
|
+
...legacyResponseMeta("/v2/tool/prepare")
|
|
154
368
|
});
|
|
155
369
|
return;
|
|
156
370
|
}
|
|
@@ -164,12 +378,265 @@ export async function createSafeBrowseServer(options = {}) {
|
|
|
164
378
|
status: result.verdict.decision === "ALLOW" ? "used" : session.status
|
|
165
379
|
});
|
|
166
380
|
}
|
|
167
|
-
writeJson(response, 200,
|
|
381
|
+
writeJson(response, 200, {
|
|
382
|
+
...result,
|
|
383
|
+
...legacyResponseMeta("/v2/tool/callback/verify")
|
|
384
|
+
});
|
|
168
385
|
return;
|
|
169
386
|
}
|
|
170
387
|
if (request.url === "/v2/artifact") {
|
|
171
388
|
const payload = await readJson(request);
|
|
172
|
-
writeJson(response, 200,
|
|
389
|
+
writeJson(response, 200, {
|
|
390
|
+
...brokerArtifactV2(payload, runtime),
|
|
391
|
+
...legacyResponseMeta("/v2/artifact")
|
|
392
|
+
});
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (request.url === "/v4/session/start") {
|
|
396
|
+
const payload = await readJson(request);
|
|
397
|
+
const sessionState = createSessionState(payload, runtime);
|
|
398
|
+
sessions.set(sessionState.session.sessionId, sessionState);
|
|
399
|
+
writeJson(response, 200, {
|
|
400
|
+
session: sessionState.session
|
|
401
|
+
});
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (request.url === "/v4/observe") {
|
|
405
|
+
const payload = await readJson(request);
|
|
406
|
+
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
407
|
+
if (!sessionState) {
|
|
408
|
+
writeJson(response, 404, { error: "unknown_session" });
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const capture = {
|
|
412
|
+
...payload.capture,
|
|
413
|
+
sessionId: payload.sessionId,
|
|
414
|
+
taskId: sessionState.session.taskId
|
|
415
|
+
};
|
|
416
|
+
const observation = await compileObservationInIsolation({
|
|
417
|
+
capture,
|
|
418
|
+
workflowHash: sessionState.session.workflowHash,
|
|
419
|
+
allowlistedEgress: runtime.parserAllowlistedEgress,
|
|
420
|
+
runtime: {
|
|
421
|
+
knowledgeBase: runtime.knowledgeBase
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
const failClosedObservation = applyV4FailClosedMediation(observation.compiledObservation, observation.plannerInput, "observe");
|
|
425
|
+
let capabilities = mintCapabilitiesForObservation(sessionState.session, observation.compiledObservation, {
|
|
426
|
+
sourceObservationId: observation.compiledObservation.observationId
|
|
427
|
+
});
|
|
428
|
+
let plannerInput = failClosedObservation.plannerInput;
|
|
429
|
+
if (shouldReduceAuthority(sessionState, observation.compiledObservation)) {
|
|
430
|
+
plannerInput = applyAuthorityReduction(sessionState, observation.compiledObservation, plannerInput);
|
|
431
|
+
capabilities = [];
|
|
432
|
+
}
|
|
433
|
+
sessionState.capabilities.clear();
|
|
434
|
+
for (const capability of capabilities) {
|
|
435
|
+
sessionState.capabilities.set(capability.capabilityId, capability);
|
|
436
|
+
}
|
|
437
|
+
sessionState.latestObservation = observation.compiledObservation;
|
|
438
|
+
writeJson(response, 200, {
|
|
439
|
+
compiledObservation: observation.compiledObservation,
|
|
440
|
+
observationVerdict: failClosedObservation.verdict,
|
|
441
|
+
plannerInput: attachCapabilitiesToPlannerInput(plannerInput, capabilities)
|
|
442
|
+
});
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (request.url === "/v4/action/evaluate") {
|
|
446
|
+
const payload = await readJson(request);
|
|
447
|
+
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
448
|
+
const capability = sessionState?.capabilities.get(payload.capabilityId);
|
|
449
|
+
const verdict = evaluateCapabilityUse(payload, sessionState?.session, capability, {
|
|
450
|
+
alreadyUsed: sessionState?.usedCapabilities.has(payload.capabilityId) ?? false
|
|
451
|
+
});
|
|
452
|
+
if (verdict.decision === "ALLOW" && sessionState && capability) {
|
|
453
|
+
sessionState.capabilities.delete(payload.capabilityId);
|
|
454
|
+
sessionState.usedCapabilities.add(payload.capabilityId);
|
|
455
|
+
sessionState.session = {
|
|
456
|
+
...sessionState.session,
|
|
457
|
+
currentStep: sessionState.session.currentStep + 1
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
writeJson(response, 200, {
|
|
461
|
+
verdict,
|
|
462
|
+
executionPlan: verdict.decision === "ALLOW" && capability
|
|
463
|
+
? {
|
|
464
|
+
verb: capability.kind,
|
|
465
|
+
targetUrl: capability.targetUrl,
|
|
466
|
+
targetOrigin: capability.targetOrigin,
|
|
467
|
+
selector: capability.selector,
|
|
468
|
+
derivedSinkClass: capability.derivedSinkClass,
|
|
469
|
+
derivedSensitiveSink: capability.derivedSensitiveSink
|
|
470
|
+
}
|
|
471
|
+
: undefined
|
|
472
|
+
});
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (request.url === "/v4/approval/grant") {
|
|
476
|
+
const payload = await readJson(request);
|
|
477
|
+
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
478
|
+
if (!sessionState) {
|
|
479
|
+
writeJson(response, 404, { error: "unknown_session" });
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const unknownCapability = (payload.capabilityIds ?? []).find((capabilityId) => !sessionState.capabilities.has(capabilityId));
|
|
483
|
+
if (unknownCapability) {
|
|
484
|
+
writeJson(response, 400, {
|
|
485
|
+
error: "unknown_capability",
|
|
486
|
+
capabilityId: unknownCapability
|
|
487
|
+
});
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (payload.sinkClass === "connector_oauth" &&
|
|
491
|
+
!(payload.capabilityIds ?? []).length) {
|
|
492
|
+
writeJson(response, 400, {
|
|
493
|
+
error: "capability_ids_required",
|
|
494
|
+
sinkClass: payload.sinkClass
|
|
495
|
+
});
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const approvalGrant = issueApprovalGrant(payload, sessionState);
|
|
499
|
+
sessionState.approvalGrants.set(approvalGrant.approvalGrantId, approvalGrant);
|
|
500
|
+
writeJson(response, 200, {
|
|
501
|
+
approvalGrant
|
|
502
|
+
});
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (request.url === "/v4/tool/prepare") {
|
|
506
|
+
const payload = await readJson(request);
|
|
507
|
+
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
508
|
+
const approvalGrant = sessionState?.approvalGrants.get(payload.approvalGrantId);
|
|
509
|
+
const prepared = prepareToolOnboardingV4({
|
|
510
|
+
...payload.request,
|
|
511
|
+
approvalGrantId: payload.approvalGrantId
|
|
512
|
+
}, sessionState?.session, approvalGrant, runtime);
|
|
513
|
+
const onboardingSession = prepared.verdict.decision === "ALLOW" && payload.request.authType === "oauth" && approvalGrant
|
|
514
|
+
? createOnboardingSession(payload.request, runtime, approvalGrant, prepared.verifiedRegistryEntry)
|
|
515
|
+
: undefined;
|
|
516
|
+
if (sessionState && onboardingSession) {
|
|
517
|
+
sessionState.onboardingSessions.set(onboardingSession.sessionId, onboardingSession);
|
|
518
|
+
}
|
|
519
|
+
writeJson(response, 200, {
|
|
520
|
+
verdict: prepared.verdict,
|
|
521
|
+
approvalVerdict: prepared.approvalVerdict,
|
|
522
|
+
verifiedRegistryEntry: prepared.verifiedRegistryEntry,
|
|
523
|
+
workflowBinding: prepared.workflowBinding,
|
|
524
|
+
onboardingSession
|
|
525
|
+
});
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (request.url === "/v4/tool/callback/verify") {
|
|
529
|
+
const payload = await readJson(request);
|
|
530
|
+
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
531
|
+
const approvalGrant = sessionState?.approvalGrants.get(payload.approvalGrantId);
|
|
532
|
+
const session = sessionState?.onboardingSessions.get(payload.request.sessionId);
|
|
533
|
+
const result = verifyToolCallbackV4(payload.request, sessionState?.session, session, approvalGrant, runtime);
|
|
534
|
+
if (sessionState && session) {
|
|
535
|
+
sessionState.onboardingSessions.set(payload.request.sessionId, {
|
|
536
|
+
...session,
|
|
537
|
+
status: result.verdict.decision === "ALLOW" ? "used" : session.status
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
writeJson(response, 200, result);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (request.url === "/v4/artifact/ingest") {
|
|
544
|
+
const payload = await readJson(request);
|
|
545
|
+
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
546
|
+
if (!sessionState) {
|
|
547
|
+
writeJson(response, 404, { error: "unknown_session" });
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const capture = {
|
|
551
|
+
...payload.capture,
|
|
552
|
+
sessionId: payload.sessionId,
|
|
553
|
+
taskId: sessionState.session.taskId
|
|
554
|
+
};
|
|
555
|
+
const observation = await compileObservationInIsolation({
|
|
556
|
+
capture,
|
|
557
|
+
workflowHash: sessionState.session.workflowHash,
|
|
558
|
+
allowlistedEgress: runtime.parserAllowlistedEgress,
|
|
559
|
+
runtime: {
|
|
560
|
+
knowledgeBase: runtime.knowledgeBase
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
const failClosedArtifact = applyV4FailClosedMediation(observation.compiledObservation, observation.plannerInput, "artifact");
|
|
564
|
+
const legacyArtifact = buildLegacyObservationCapture(capture);
|
|
565
|
+
const artifactResult = legacyArtifact !== undefined ? brokerArtifact(legacyArtifact, runtime) : undefined;
|
|
566
|
+
sessionState.latestObservation = observation.compiledObservation;
|
|
567
|
+
const effectiveArtifactVerdict = failClosedArtifact.failClosed
|
|
568
|
+
? failClosedArtifact.verdict
|
|
569
|
+
: artifactResult?.verdict;
|
|
570
|
+
const effectiveArtifact = failClosedArtifact.failClosed && artifactResult?.artifact
|
|
571
|
+
? {
|
|
572
|
+
...artifactResult.artifact,
|
|
573
|
+
toolActivationPolicy: "block",
|
|
574
|
+
approvalRequiredForFollowOn: true
|
|
575
|
+
}
|
|
576
|
+
: artifactResult?.artifact;
|
|
577
|
+
const plannerInput = shouldReduceAuthority(sessionState, observation.compiledObservation)
|
|
578
|
+
? applyAuthorityReduction(sessionState, observation.compiledObservation, failClosedArtifact.plannerInput)
|
|
579
|
+
: failClosedArtifact.plannerInput;
|
|
580
|
+
writeJson(response, 200, {
|
|
581
|
+
compiledObservation: observation.compiledObservation,
|
|
582
|
+
plannerInput,
|
|
583
|
+
artifactVerdict: effectiveArtifactVerdict,
|
|
584
|
+
artifact: effectiveArtifact
|
|
585
|
+
});
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (request.url === "/v4/memory/write") {
|
|
589
|
+
const payload = await readJson(request);
|
|
590
|
+
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
591
|
+
const result = evaluateMemoryWriteV4(payload, sessionState?.session, runtime);
|
|
592
|
+
if (sessionState && result.record) {
|
|
593
|
+
sessionState.memoryRecords.set(result.record.recordId, result.record);
|
|
594
|
+
}
|
|
595
|
+
writeJson(response, 200, result);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (request.url === "/v4/memory/promote") {
|
|
599
|
+
const payload = await readJson(request);
|
|
600
|
+
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
601
|
+
const record = sessionState?.memoryRecords.get(payload.recordId);
|
|
602
|
+
const approvalGrant = payload.approvalGrantId && sessionState
|
|
603
|
+
? sessionState.approvalGrants.get(payload.approvalGrantId)
|
|
604
|
+
: undefined;
|
|
605
|
+
const result = promoteMemoryRecordV4(payload, sessionState?.session, record, approvalGrant);
|
|
606
|
+
if (sessionState && result.promotedRecord) {
|
|
607
|
+
if (record && result.promotedRecord.snapshotId) {
|
|
608
|
+
sessionState.memorySnapshots.set(result.promotedRecord.snapshotId, {
|
|
609
|
+
...record,
|
|
610
|
+
tier: "trusted_durable",
|
|
611
|
+
summaryOnly: false,
|
|
612
|
+
snapshotId: result.promotedRecord.snapshotId,
|
|
613
|
+
rollbackPointId: result.promotedRecord.rollbackPointId
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
sessionState.memoryRecords.set(result.promotedRecord.recordId, result.promotedRecord);
|
|
617
|
+
}
|
|
618
|
+
writeJson(response, 200, result);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (request.url === "/v4/memory/rollback") {
|
|
622
|
+
const payload = await readJson(request);
|
|
623
|
+
const sessionState = findSessionState(sessions, payload.sessionId);
|
|
624
|
+
const record = sessionState?.memoryRecords.get(payload.recordId);
|
|
625
|
+
const snapshotRecord = sessionState?.memorySnapshots.get(payload.snapshotId);
|
|
626
|
+
const result = rollbackMemoryRecordV4(payload, sessionState?.session, record, snapshotRecord);
|
|
627
|
+
if (sessionState && result.restoredRecord) {
|
|
628
|
+
sessionState.memoryRecords.set(result.restoredRecord.recordId, result.restoredRecord);
|
|
629
|
+
}
|
|
630
|
+
writeJson(response, 200, {
|
|
631
|
+
...result,
|
|
632
|
+
rollbackEvent: result.verdict.decision === "ALLOW"
|
|
633
|
+
? {
|
|
634
|
+
recordId: payload.recordId,
|
|
635
|
+
snapshotId: payload.snapshotId,
|
|
636
|
+
appliedAt: new Date().toISOString()
|
|
637
|
+
}
|
|
638
|
+
: undefined
|
|
639
|
+
});
|
|
173
640
|
return;
|
|
174
641
|
}
|
|
175
642
|
writeJson(response, 404, { error: "not_found" });
|