@svsprotocol/solana 0.1.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/LICENSE +158 -0
- package/README.md +365 -0
- package/dist/action-production-proof-evidence.js +553 -0
- package/dist/adapter-catalog.d.ts +29 -0
- package/dist/adapter-catalog.js +146 -0
- package/dist/adapter-core.d.ts +48 -0
- package/dist/adapter-core.js +249 -0
- package/dist/approval-signature.js +197 -0
- package/dist/base58.js +69 -0
- package/dist/bot-auth.js +50 -0
- package/dist/bot-certification-evidence.js +342 -0
- package/dist/bot-first-action-runbook.js +299 -0
- package/dist/bot-integration-contract.js +41 -0
- package/dist/certified-submit-status.js +176 -0
- package/dist/common.d.ts +1135 -0
- package/dist/elizaos.d.ts +43 -0
- package/dist/elizaos.js +227 -0
- package/dist/goat.d.ts +47 -0
- package/dist/goat.js +261 -0
- package/dist/index.d.ts +330 -0
- package/dist/index.js +128 -0
- package/dist/protocol.d.ts +205 -0
- package/dist/protocol.js +900 -0
- package/dist/receipt.js +51 -0
- package/dist/signed-proof-read-protection.js +495 -0
- package/dist/solana-agent-kit.d.ts +35 -0
- package/dist/solana-agent-kit.js +151 -0
- package/dist/svs-client.js +1232 -0
- package/dist/vercel-ai.d.ts +47 -0
- package/dist/vercel-ai.js +266 -0
- package/dist/verified-agent-adoption-kit.js +471 -0
- package/dist/verified-agent-profile.js +329 -0
- package/dist/verified-agent-registry-consumer.js +421 -0
- package/dist/verified-agent-registry.d.ts +36 -0
- package/dist/verified-agent-registry.js +826 -0
- package/dist/verified-agent-trust-score.js +335 -0
- package/dist/webhooks.js +834 -0
- package/package.json +72 -0
package/dist/receipt.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export function hashObject(value) {
|
|
4
|
+
return createHash("sha256")
|
|
5
|
+
.update(JSON.stringify(sortObject(value)))
|
|
6
|
+
.digest("hex");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createReceipt({ intent, policy, decision, risk, simulation }) {
|
|
10
|
+
const issuedAt = new Date().toISOString();
|
|
11
|
+
const policyHash = hashObject(policy);
|
|
12
|
+
const simulationHash = hashObject(simulation ?? {});
|
|
13
|
+
|
|
14
|
+
const receipt = {
|
|
15
|
+
receiptId: randomUUID(),
|
|
16
|
+
issuedAt,
|
|
17
|
+
botId: intent.botId,
|
|
18
|
+
controllerWallet: intent.controllerWallet,
|
|
19
|
+
policyId: policy.id,
|
|
20
|
+
policyHash,
|
|
21
|
+
authorizationType: decision.authorizationType,
|
|
22
|
+
allowed: decision.allowed,
|
|
23
|
+
riskScore: risk.score,
|
|
24
|
+
riskFindings: risk.findings,
|
|
25
|
+
simulationHash,
|
|
26
|
+
intentHash: hashObject(intent),
|
|
27
|
+
transactionSignature: intent.transactionSignature ?? null
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
...receipt,
|
|
32
|
+
receiptHash: hashObject(receipt)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function sortObject(value) {
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
return value.map(sortObject);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (value && typeof value === "object") {
|
|
42
|
+
return Object.keys(value)
|
|
43
|
+
.sort()
|
|
44
|
+
.reduce((result, key) => {
|
|
45
|
+
result[key] = sortObject(value[key]);
|
|
46
|
+
return result;
|
|
47
|
+
}, {});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import { hashObject } from "./receipt.js";
|
|
2
|
+
import { createSignedBotRequest } from "./svs-client.js";
|
|
3
|
+
|
|
4
|
+
export const SIGNED_PROOF_READ_PROTECTION_VERSION = "svs.signed-proof-read-protection-evidence.v1";
|
|
5
|
+
export const SIGNED_PROOF_READ_PROTECTION_VERIFICATION_VERSION = "svs.signed-proof-read-protection-verification.v1";
|
|
6
|
+
export const DEFAULT_SIGNED_PROOF_READ_PROTECTION_EVIDENCE_PATH = "./data/security/signed-proof-read-protection-evidence.json";
|
|
7
|
+
|
|
8
|
+
export async function createSignedProofReadProtectionEvidence({
|
|
9
|
+
serverUrl = "http://127.0.0.1:4173",
|
|
10
|
+
recordId,
|
|
11
|
+
apiKey,
|
|
12
|
+
requestSigningSecret,
|
|
13
|
+
botId = null,
|
|
14
|
+
forbiddenBotId = null,
|
|
15
|
+
forbiddenApiKey = null,
|
|
16
|
+
forbiddenRequestSigningSecret = null,
|
|
17
|
+
checkReceiptRegistryChain = false,
|
|
18
|
+
fetchImpl = globalThis.fetch,
|
|
19
|
+
now = new Date()
|
|
20
|
+
} = {}) {
|
|
21
|
+
if (!recordId) {
|
|
22
|
+
throw new Error("recordId is required.");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
throw new Error("apiKey is required.");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!requestSigningSecret) {
|
|
30
|
+
throw new Error("requestSigningSecret is required.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof fetchImpl !== "function") {
|
|
34
|
+
throw new Error("A fetch implementation is required.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const checkedAt = now instanceof Date ? now.toISOString() : new Date(now).toISOString();
|
|
38
|
+
const baseUrl = String(serverUrl).replace(/\/+$/, "");
|
|
39
|
+
const statusPath = `/api/actions/${encodeURIComponent(recordId)}/production-proof-status`;
|
|
40
|
+
const proofPath = `/api/actions/${encodeURIComponent(recordId)}/production-proof?checkReceiptRegistryChain=${checkReceiptRegistryChain ? "true" : "false"}`;
|
|
41
|
+
const unsignedStatus = await requestJson({
|
|
42
|
+
fetchImpl,
|
|
43
|
+
url: `${baseUrl}${statusPath}`,
|
|
44
|
+
headers: {
|
|
45
|
+
authorization: `Bearer ${apiKey}`
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
const signedStatus = await signedGetJson({
|
|
49
|
+
fetchImpl,
|
|
50
|
+
url: `${baseUrl}${statusPath}`,
|
|
51
|
+
apiKey,
|
|
52
|
+
requestSigningSecret,
|
|
53
|
+
timestamp: checkedAt
|
|
54
|
+
});
|
|
55
|
+
const signedProof = await signedGetJson({
|
|
56
|
+
fetchImpl,
|
|
57
|
+
url: `${baseUrl}${proofPath}`,
|
|
58
|
+
apiKey,
|
|
59
|
+
requestSigningSecret,
|
|
60
|
+
timestamp: checkedAt
|
|
61
|
+
});
|
|
62
|
+
const shouldCheckForeignBot = Boolean(forbiddenApiKey || forbiddenRequestSigningSecret || forbiddenBotId);
|
|
63
|
+
const missingForeignCredentials = shouldCheckForeignBot && (!forbiddenApiKey || !forbiddenRequestSigningSecret);
|
|
64
|
+
const foreignSignedStatus = !missingForeignCredentials && shouldCheckForeignBot
|
|
65
|
+
? await signedGetJson({
|
|
66
|
+
fetchImpl,
|
|
67
|
+
url: `${baseUrl}${statusPath}`,
|
|
68
|
+
apiKey: forbiddenApiKey,
|
|
69
|
+
requestSigningSecret: forbiddenRequestSigningSecret,
|
|
70
|
+
timestamp: checkedAt
|
|
71
|
+
})
|
|
72
|
+
: null;
|
|
73
|
+
const foreignSignedProof = !missingForeignCredentials && shouldCheckForeignBot
|
|
74
|
+
? await signedGetJson({
|
|
75
|
+
fetchImpl,
|
|
76
|
+
url: `${baseUrl}${proofPath}`,
|
|
77
|
+
apiKey: forbiddenApiKey,
|
|
78
|
+
requestSigningSecret: forbiddenRequestSigningSecret,
|
|
79
|
+
timestamp: checkedAt
|
|
80
|
+
})
|
|
81
|
+
: null;
|
|
82
|
+
const unsigned = {
|
|
83
|
+
version: SIGNED_PROOF_READ_PROTECTION_VERSION,
|
|
84
|
+
ok: null,
|
|
85
|
+
status: null,
|
|
86
|
+
checkedAt,
|
|
87
|
+
serverUrl: baseUrl,
|
|
88
|
+
botId,
|
|
89
|
+
recordId,
|
|
90
|
+
proofReadPolicy: {
|
|
91
|
+
unsignedReadMustFail: true,
|
|
92
|
+
signedStatusReadMustSucceed: true,
|
|
93
|
+
signedProofReadMustSucceed: true,
|
|
94
|
+
foreignSignedReadMustFail: shouldCheckForeignBot,
|
|
95
|
+
signedReadBody: "",
|
|
96
|
+
checkReceiptRegistryChain: Boolean(checkReceiptRegistryChain),
|
|
97
|
+
scope: {
|
|
98
|
+
type: "record_owner_bot",
|
|
99
|
+
ownerBotId: botId,
|
|
100
|
+
forbiddenBotId,
|
|
101
|
+
missingForeignCredentials
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
unsignedStatusRead: summarizeResponse(unsignedStatus),
|
|
105
|
+
signedStatusRead: summarizeResponse(signedStatus, {
|
|
106
|
+
expectedVersion: "svs.action-production-proof-status.v1",
|
|
107
|
+
recordId
|
|
108
|
+
}),
|
|
109
|
+
signedProofRead: summarizeResponse(signedProof, {
|
|
110
|
+
expectedVersion: "svs.action-production-proof-response.v1",
|
|
111
|
+
recordId
|
|
112
|
+
}),
|
|
113
|
+
foreignSignedStatusRead: shouldCheckForeignBot
|
|
114
|
+
? summarizeResponse(foreignSignedStatus, {
|
|
115
|
+
expectedVersion: "svs.action-production-proof-status.v1",
|
|
116
|
+
recordId,
|
|
117
|
+
missingCredentials: missingForeignCredentials
|
|
118
|
+
})
|
|
119
|
+
: null,
|
|
120
|
+
foreignSignedProofRead: shouldCheckForeignBot
|
|
121
|
+
? summarizeResponse(foreignSignedProof, {
|
|
122
|
+
expectedVersion: "svs.action-production-proof-response.v1",
|
|
123
|
+
recordId,
|
|
124
|
+
missingCredentials: missingForeignCredentials
|
|
125
|
+
})
|
|
126
|
+
: null,
|
|
127
|
+
secretMaterialRedacted: true
|
|
128
|
+
};
|
|
129
|
+
const failedChecks = createSignedProofReadProtectionChecks(unsigned)
|
|
130
|
+
.filter((check) => !check.ok)
|
|
131
|
+
.map((check) => ({
|
|
132
|
+
name: check.name,
|
|
133
|
+
category: signedProofReadProtectionFailureCategory(check),
|
|
134
|
+
detail: check.detail
|
|
135
|
+
}));
|
|
136
|
+
const evidence = {
|
|
137
|
+
...unsigned,
|
|
138
|
+
ok: failedChecks.length === 0,
|
|
139
|
+
status: failedChecks.length === 0 ? "passed" : "failed",
|
|
140
|
+
failedCheckCount: failedChecks.length,
|
|
141
|
+
failedChecks
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
...evidence,
|
|
146
|
+
evidenceHash: hashSignedProofReadProtectionEvidence(evidence)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function verifySignedProofReadProtectionEvidence(evidence, {
|
|
151
|
+
expectedRecordId = null,
|
|
152
|
+
staleAfterMs = null,
|
|
153
|
+
now = new Date()
|
|
154
|
+
} = {}) {
|
|
155
|
+
const recomputedHash = hashSignedProofReadProtectionEvidence(evidence);
|
|
156
|
+
const freshness = checkEvidenceFreshness({
|
|
157
|
+
generatedAt: evidence?.checkedAt,
|
|
158
|
+
staleAfterMs,
|
|
159
|
+
now
|
|
160
|
+
});
|
|
161
|
+
const checks = [
|
|
162
|
+
check(
|
|
163
|
+
"Signed proof-read protection evidence version is supported",
|
|
164
|
+
evidence?.version === SIGNED_PROOF_READ_PROTECTION_VERSION,
|
|
165
|
+
evidence?.version ?? "missing"
|
|
166
|
+
),
|
|
167
|
+
check(
|
|
168
|
+
"Signed proof-read protection evidence hash is valid",
|
|
169
|
+
evidence?.evidenceHash === recomputedHash,
|
|
170
|
+
`declared=${evidence?.evidenceHash ?? "missing"} recomputed=${recomputedHash ?? "missing"}`
|
|
171
|
+
),
|
|
172
|
+
check(
|
|
173
|
+
"Signed proof-read protection evidence excludes secrets",
|
|
174
|
+
evidence?.secretMaterialRedacted === true && !containsSecretMaterial(evidence),
|
|
175
|
+
`secretMaterialRedacted=${evidence?.secretMaterialRedacted ?? "missing"}`
|
|
176
|
+
),
|
|
177
|
+
...createSignedProofReadProtectionChecks(evidence)
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
if (expectedRecordId) {
|
|
181
|
+
checks.push(check(
|
|
182
|
+
"Signed proof-read protection record matches expected record",
|
|
183
|
+
evidence?.recordId === expectedRecordId,
|
|
184
|
+
`expected=${expectedRecordId} actual=${evidence?.recordId ?? "missing"}`
|
|
185
|
+
));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (staleAfterMs !== null && staleAfterMs !== undefined) {
|
|
189
|
+
checks.push(check(
|
|
190
|
+
"Signed proof-read protection evidence is fresh",
|
|
191
|
+
!freshness.stale,
|
|
192
|
+
freshness.ageMs === null
|
|
193
|
+
? `checkedAt=${evidence?.checkedAt ?? "missing"}`
|
|
194
|
+
: `ageMs=${freshness.ageMs} staleAfterMs=${staleAfterMs}`
|
|
195
|
+
));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const failedChecks = checks
|
|
199
|
+
.filter((item) => !item.ok)
|
|
200
|
+
.map((item) => ({
|
|
201
|
+
name: item.name,
|
|
202
|
+
category: signedProofReadProtectionFailureCategory(item),
|
|
203
|
+
detail: item.detail
|
|
204
|
+
}));
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
version: SIGNED_PROOF_READ_PROTECTION_VERIFICATION_VERSION,
|
|
208
|
+
ok: failedChecks.length === 0,
|
|
209
|
+
status: failedChecks.length === 0 ? "verified" : freshness.stale ? "stale" : "failed",
|
|
210
|
+
checkedAt: now instanceof Date ? now.toISOString() : new Date(now).toISOString(),
|
|
211
|
+
found: Boolean(evidence),
|
|
212
|
+
stale: freshness.stale,
|
|
213
|
+
ageMs: freshness.ageMs,
|
|
214
|
+
staleAfterMs,
|
|
215
|
+
evidenceHash: evidence?.evidenceHash ?? null,
|
|
216
|
+
computedEvidenceHash: recomputedHash,
|
|
217
|
+
botId: evidence?.botId ?? null,
|
|
218
|
+
recordId: evidence?.recordId ?? null,
|
|
219
|
+
unsignedRejected: evidence?.unsignedStatusRead?.accepted === false,
|
|
220
|
+
signedStatusAccepted: evidence?.signedStatusRead?.accepted === true,
|
|
221
|
+
signedProofAccepted: evidence?.signedProofRead?.accepted === true,
|
|
222
|
+
signedProofSetHash: evidence?.signedProofRead?.proofSetHash ?? null,
|
|
223
|
+
foreignBotId: evidence?.proofReadPolicy?.scope?.forbiddenBotId ?? null,
|
|
224
|
+
foreignStatusRejected: evidence?.foreignSignedStatusRead?.accepted === false,
|
|
225
|
+
foreignProofRejected: evidence?.foreignSignedProofRead?.accepted === false,
|
|
226
|
+
failedCheckCount: failedChecks.length,
|
|
227
|
+
failedChecks,
|
|
228
|
+
checks
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function hashSignedProofReadProtectionEvidence(evidence) {
|
|
233
|
+
if (!evidence || typeof evidence !== "object") {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const {
|
|
238
|
+
evidenceHash,
|
|
239
|
+
ok,
|
|
240
|
+
status,
|
|
241
|
+
failedCheckCount,
|
|
242
|
+
failedChecks,
|
|
243
|
+
...hashable
|
|
244
|
+
} = evidence;
|
|
245
|
+
|
|
246
|
+
return hashObject(hashable);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function signedGetJson({
|
|
250
|
+
fetchImpl,
|
|
251
|
+
url,
|
|
252
|
+
apiKey,
|
|
253
|
+
requestSigningSecret,
|
|
254
|
+
timestamp
|
|
255
|
+
}) {
|
|
256
|
+
const signed = createSignedBotRequest({
|
|
257
|
+
body: "",
|
|
258
|
+
requestSigningSecret,
|
|
259
|
+
timestamp,
|
|
260
|
+
headers: {
|
|
261
|
+
authorization: `Bearer ${apiKey}`
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return await requestJson({
|
|
266
|
+
fetchImpl,
|
|
267
|
+
url,
|
|
268
|
+
headers: signed.headers
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function requestJson({
|
|
273
|
+
fetchImpl,
|
|
274
|
+
url,
|
|
275
|
+
headers
|
|
276
|
+
}) {
|
|
277
|
+
let response;
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
response = await fetchImpl(url, {
|
|
281
|
+
method: "GET",
|
|
282
|
+
headers
|
|
283
|
+
});
|
|
284
|
+
} catch (error) {
|
|
285
|
+
return {
|
|
286
|
+
url,
|
|
287
|
+
statusCode: null,
|
|
288
|
+
ok: false,
|
|
289
|
+
body: {
|
|
290
|
+
error: error?.message ?? "fetch failed",
|
|
291
|
+
cause: error?.cause?.message ?? null,
|
|
292
|
+
code: error?.cause?.code ?? null
|
|
293
|
+
},
|
|
294
|
+
bodyHash: hashObject({
|
|
295
|
+
error: error?.message ?? "fetch failed",
|
|
296
|
+
cause: error?.cause?.message ?? null,
|
|
297
|
+
code: error?.cause?.code ?? null
|
|
298
|
+
}),
|
|
299
|
+
bodyBytes: 0
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const text = await response.text();
|
|
304
|
+
let body = null;
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
body = text ? JSON.parse(text) : null;
|
|
308
|
+
} catch {
|
|
309
|
+
body = null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
url,
|
|
314
|
+
statusCode: response.status,
|
|
315
|
+
ok: response.ok === true,
|
|
316
|
+
body,
|
|
317
|
+
bodyHash: hashObject({ text }),
|
|
318
|
+
bodyBytes: Buffer.byteLength(text, "utf8")
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function summarizeResponse(response, {
|
|
323
|
+
expectedVersion = null,
|
|
324
|
+
recordId = null,
|
|
325
|
+
missingCredentials = false
|
|
326
|
+
} = {}) {
|
|
327
|
+
const body = response?.body;
|
|
328
|
+
const proof = body?.proof;
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
statusCode: response?.statusCode ?? null,
|
|
332
|
+
accepted: response?.statusCode >= 200 && response?.statusCode < 300,
|
|
333
|
+
missingCredentials: Boolean(missingCredentials),
|
|
334
|
+
bodyHash: response?.bodyHash ?? null,
|
|
335
|
+
bodyBytes: response?.bodyBytes ?? null,
|
|
336
|
+
version: body?.version ?? null,
|
|
337
|
+
expectedVersion,
|
|
338
|
+
recordId: body?.recordId ?? proof?.recordId ?? body?.evidence?.productionProof?.recordId ?? null,
|
|
339
|
+
expectedRecordId: recordId,
|
|
340
|
+
ready: body?.ready ?? null,
|
|
341
|
+
error: body?.error ?? null,
|
|
342
|
+
errorCause: body?.cause ?? null,
|
|
343
|
+
errorCode: body?.code ?? null,
|
|
344
|
+
proofSetHash: proof?.verificationSetHash ?? proof?.manifest?.verificationSetHash ?? null
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function createSignedProofReadProtectionChecks(evidence) {
|
|
349
|
+
const checks = [
|
|
350
|
+
check(
|
|
351
|
+
"Unsigned production proof status read is rejected",
|
|
352
|
+
evidence?.unsignedStatusRead?.accepted === false &&
|
|
353
|
+
evidence.unsignedStatusRead.statusCode === 401 &&
|
|
354
|
+
/signature/i.test(evidence.unsignedStatusRead.error ?? ""),
|
|
355
|
+
`status=${evidence?.unsignedStatusRead?.statusCode ?? "missing"} error=${evidence?.unsignedStatusRead?.error ?? "missing"}`
|
|
356
|
+
),
|
|
357
|
+
check(
|
|
358
|
+
"Signed production proof status read succeeds",
|
|
359
|
+
evidence?.signedStatusRead?.accepted === true &&
|
|
360
|
+
evidence.signedStatusRead.statusCode === 200 &&
|
|
361
|
+
evidence.signedStatusRead.version === "svs.action-production-proof-status.v1",
|
|
362
|
+
`status=${evidence?.signedStatusRead?.statusCode ?? "missing"} version=${evidence?.signedStatusRead?.version ?? "missing"}`
|
|
363
|
+
),
|
|
364
|
+
check(
|
|
365
|
+
"Signed production proof status read is ready",
|
|
366
|
+
evidence?.signedStatusRead?.ready === true,
|
|
367
|
+
`ready=${evidence?.signedStatusRead?.ready ?? "missing"}`
|
|
368
|
+
),
|
|
369
|
+
check(
|
|
370
|
+
"Signed production proof response read succeeds",
|
|
371
|
+
evidence?.signedProofRead?.accepted === true &&
|
|
372
|
+
evidence.signedProofRead.statusCode === 200 &&
|
|
373
|
+
evidence.signedProofRead.version === "svs.action-production-proof-response.v1",
|
|
374
|
+
`status=${evidence?.signedProofRead?.statusCode ?? "missing"} version=${evidence?.signedProofRead?.version ?? "missing"}`
|
|
375
|
+
),
|
|
376
|
+
check(
|
|
377
|
+
"Signed proof-read responses match the expected action record",
|
|
378
|
+
evidence?.recordId &&
|
|
379
|
+
evidence.signedStatusRead?.recordId === evidence.recordId &&
|
|
380
|
+
evidence.signedProofRead?.recordId === evidence.recordId,
|
|
381
|
+
`expected=${evidence?.recordId ?? "missing"} status=${evidence?.signedStatusRead?.recordId ?? "missing"} proof=${evidence?.signedProofRead?.recordId ?? "missing"}`
|
|
382
|
+
)
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
if (evidence?.proofReadPolicy?.foreignSignedReadMustFail === true) {
|
|
386
|
+
checks.push(
|
|
387
|
+
check(
|
|
388
|
+
"Foreign signed production proof status read is rejected",
|
|
389
|
+
evidence?.foreignSignedStatusRead?.accepted === false &&
|
|
390
|
+
evidence.foreignSignedStatusRead.statusCode === 403 &&
|
|
391
|
+
/cannot inspect action|not allowed|forbidden|unauthorized/i.test(evidence.foreignSignedStatusRead.error ?? ""),
|
|
392
|
+
`status=${evidence?.foreignSignedStatusRead?.statusCode ?? "missing"} error=${evidence?.foreignSignedStatusRead?.error ?? "missing"} missingCredentials=${evidence?.foreignSignedStatusRead?.missingCredentials ?? "missing"}`
|
|
393
|
+
),
|
|
394
|
+
check(
|
|
395
|
+
"Foreign signed production proof response read is rejected",
|
|
396
|
+
evidence?.foreignSignedProofRead?.accepted === false &&
|
|
397
|
+
evidence.foreignSignedProofRead.statusCode === 403 &&
|
|
398
|
+
/cannot inspect action|not allowed|forbidden|unauthorized/i.test(evidence.foreignSignedProofRead.error ?? ""),
|
|
399
|
+
`status=${evidence?.foreignSignedProofRead?.statusCode ?? "missing"} error=${evidence?.foreignSignedProofRead?.error ?? "missing"} missingCredentials=${evidence?.foreignSignedProofRead?.missingCredentials ?? "missing"}`
|
|
400
|
+
)
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return checks;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function check(name, ok, detail = null) {
|
|
408
|
+
return {
|
|
409
|
+
name,
|
|
410
|
+
ok: Boolean(ok),
|
|
411
|
+
detail
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function checkEvidenceFreshness({
|
|
416
|
+
generatedAt,
|
|
417
|
+
staleAfterMs,
|
|
418
|
+
now
|
|
419
|
+
}) {
|
|
420
|
+
if (staleAfterMs === null || staleAfterMs === undefined) {
|
|
421
|
+
return {
|
|
422
|
+
stale: false,
|
|
423
|
+
ageMs: null
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const generatedMs = Date.parse(generatedAt);
|
|
428
|
+
const nowMs = now instanceof Date ? now.getTime() : Date.parse(now);
|
|
429
|
+
|
|
430
|
+
if (!Number.isFinite(generatedMs) || !Number.isFinite(nowMs)) {
|
|
431
|
+
return {
|
|
432
|
+
stale: true,
|
|
433
|
+
ageMs: null
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const ageMs = nowMs - generatedMs;
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
stale: ageMs < 0 || ageMs > staleAfterMs,
|
|
441
|
+
ageMs
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function containsSecretMaterial(value) {
|
|
446
|
+
const text = JSON.stringify(value ?? {});
|
|
447
|
+
|
|
448
|
+
return [
|
|
449
|
+
/SVS_BOT_API_KEY=/i,
|
|
450
|
+
/SVS_BOT_REQUEST_SIGNING_SECRET=/i,
|
|
451
|
+
/requestSigningSecret/i,
|
|
452
|
+
/apiKey/i,
|
|
453
|
+
/authorization:\s*bearer/i,
|
|
454
|
+
/svs-request-signature/i,
|
|
455
|
+
/v1=[0-9a-f]{64}/i
|
|
456
|
+
].some((pattern) => pattern.test(text));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function signedProofReadProtectionFailureCategory(check) {
|
|
460
|
+
const name = String(check?.name ?? "").toLowerCase();
|
|
461
|
+
|
|
462
|
+
if (name.includes("unsigned")) {
|
|
463
|
+
return "unsigned_proof_read_not_rejected";
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (name.includes("status read succeeds") || name.includes("status read is ready")) {
|
|
467
|
+
return "signed_proof_status_read_failed";
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (name.includes("response read succeeds")) {
|
|
471
|
+
return "signed_proof_response_read_failed";
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (name.includes("foreign signed")) {
|
|
475
|
+
return "foreign_signed_proof_read_not_rejected";
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (name.includes("record matches")) {
|
|
479
|
+
return "signed_proof_read_record_mismatch";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (name.includes("fresh")) {
|
|
483
|
+
return "signed_proof_read_evidence_stale";
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (name.includes("secret")) {
|
|
487
|
+
return "signed_proof_read_secret_leak";
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (name.includes("hash") || name.includes("version")) {
|
|
491
|
+
return "signed_proof_read_evidence_invalid";
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return "signed_proof_read_failed";
|
|
495
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SvsAdapterActionInput,
|
|
3
|
+
SvsAdapterActionResult,
|
|
4
|
+
SvsAdapterOptions,
|
|
5
|
+
SvsAdapterReadinessResult,
|
|
6
|
+
JsonObject
|
|
7
|
+
} from "./common.js";
|
|
8
|
+
import { SvsAdapterCoreError } from "./adapter-core.js";
|
|
9
|
+
|
|
10
|
+
export const SOLANA_AGENT_KIT_ADAPTER_VERSION: "svs.solana-agent-kit-adapter.v1";
|
|
11
|
+
export const SOLANA_AGENT_KIT_TOOL_NAME: "svs_verify_and_submit_solana_action";
|
|
12
|
+
|
|
13
|
+
export class SvsSolanaAgentKitAdapterError extends SvsAdapterCoreError {}
|
|
14
|
+
|
|
15
|
+
export interface SvsSolanaAgentKitAdapter {
|
|
16
|
+
version: typeof SOLANA_AGENT_KIT_ADAPTER_VERSION;
|
|
17
|
+
name: typeof SOLANA_AGENT_KIT_TOOL_NAME;
|
|
18
|
+
description: string;
|
|
19
|
+
botId: string;
|
|
20
|
+
policyId: string | null;
|
|
21
|
+
rpcUrl: string | null;
|
|
22
|
+
client: unknown;
|
|
23
|
+
requireSvsProductionReady(options?: Partial<SvsAdapterOptions>): Promise<SvsAdapterReadinessResult>;
|
|
24
|
+
verifyAndSubmitSolanaAction(input: SvsAdapterActionInput, options?: Partial<SvsAdapterOptions>): Promise<SvsAdapterActionResult>;
|
|
25
|
+
tool: {
|
|
26
|
+
name: typeof SOLANA_AGENT_KIT_TOOL_NAME;
|
|
27
|
+
description: string;
|
|
28
|
+
parameters: JsonObject;
|
|
29
|
+
execute(input: SvsAdapterActionInput): Promise<SvsAdapterActionResult>;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createSvsSolanaAgentKitAdapter(options: SvsAdapterOptions): SvsSolanaAgentKitAdapter;
|
|
34
|
+
export function requireSvsProductionReady(options: SvsAdapterOptions): Promise<SvsAdapterReadinessResult>;
|
|
35
|
+
export function verifyAndSubmitSolanaAction(options: SvsAdapterOptions & SvsAdapterActionInput): Promise<SvsAdapterActionResult>;
|