@crawlcheck/sdk 1.1.0 → 1.2.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/dist/index.d.ts +14 -2
- package/dist/index.js +12 -4
- package/package.json +1 -1
- package/test/fixtures/guard-acceptance.json +1534 -761
- package/test/guard-acceptance.test.js +2 -0
package/dist/index.d.ts
CHANGED
|
@@ -196,8 +196,19 @@ export interface PreflightOptions {
|
|
|
196
196
|
agent?: string;
|
|
197
197
|
template?: string;
|
|
198
198
|
policy?: PreflightPolicy; /** 8-128 characters of A-Z a-z 0-9 . _ : - signed into the receipt (replay binding). guard() sends a fresh one when you give none. */
|
|
199
|
-
nonce?: string;
|
|
200
|
-
|
|
199
|
+
nonce?: string; /** The exact operation this decision is for (method + url, or an MCP tool); signed into the receipt. */
|
|
200
|
+
operation?: Operation;
|
|
201
|
+
}
|
|
202
|
+
/** What a receipt can be scoped to. Send only the keys that apply; CrawlCheck signs them back exactly as sent. https://crawlcheck.io/spec/guard#operation */
|
|
203
|
+
export interface Operation {
|
|
204
|
+
method?: "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS";
|
|
205
|
+
url?: string;
|
|
206
|
+
tool?: string;
|
|
207
|
+
tool_schema_sha256?: string;
|
|
208
|
+
scopes?: string[];
|
|
209
|
+
}
|
|
210
|
+
/** The operation for calling an MCP tool: its name, the sha256 of its definition (as in the lockfile) and the scopes you will use. */
|
|
211
|
+
export declare function toolOperation(tool: McpTool, scopes?: string[]): Promise<Operation>;
|
|
201
212
|
/** A signed crawlcheck-decision receipt. Verify it with verifyDocument (or let guard() do it). */
|
|
202
213
|
export interface DecisionReceipt {
|
|
203
214
|
kind: "crawlcheck-decision";
|
|
@@ -237,6 +248,7 @@ export interface DecisionWant {
|
|
|
237
248
|
action: Action | string;
|
|
238
249
|
nonce?: string | null;
|
|
239
250
|
now?: number;
|
|
251
|
+
operation?: Operation | null;
|
|
240
252
|
}
|
|
241
253
|
/**
|
|
242
254
|
* Is this receipt about the operation you are about to perform? Checks the scope only (kind, domain, action, nonce,
|
package/dist/index.js
CHANGED
|
@@ -65,6 +65,9 @@ export async function checkLock(lock, tools) {
|
|
|
65
65
|
const same = lock?.tools_sha256 === now.tools_sha256 && !added.length && !removed.length && !modified.length;
|
|
66
66
|
return { verdict: same ? "unchanged" : "changed", decision: same ? "connect" : "block", approved_tools_sha256: lock?.tools_sha256 ?? "", current_tools_sha256: now.tools_sha256, added, removed, modified };
|
|
67
67
|
}
|
|
68
|
+
/** The operation for calling an MCP tool: its name, the sha256 of its definition (as in the lockfile) and the scopes you will use. */
|
|
69
|
+
export async function toolOperation(tool, scopes) { const o = { tool: String(tool?.name ?? ""), tool_schema_sha256: await lockToolHash(tool) }; if (scopes && scopes.length)
|
|
70
|
+
o.scopes = scopes.slice(); return o; }
|
|
68
71
|
/** The longest a receipt may stay valid, per action (seconds). A receipt that claims longer is refused. Published at https://crawlcheck.io/spec/guard */
|
|
69
72
|
export const ACTION_MAX_LIFETIME_S = { read: 86400, cite: 86400, connect: 3600, transact: 300, administer: 300 };
|
|
70
73
|
/** The most clock skew a receipt may claim (seconds). */
|
|
@@ -86,6 +89,8 @@ export function checkDecision(receipt, want) {
|
|
|
86
89
|
why.push("the receipt is for " + r.domain + ", not " + want.domain);
|
|
87
90
|
if (String(r.action) !== String(want.action))
|
|
88
91
|
why.push("the receipt is for action " + r.action + ", not " + want.action);
|
|
92
|
+
if (want.operation && canon(r.operation ?? null) !== canon(want.operation))
|
|
93
|
+
why.push(r.operation ? "the receipt is for another operation (" + canon(r.operation).slice(0, 120) + ")" : "the receipt is not scoped to an operation, and this request named one");
|
|
89
94
|
if (want.nonce && r.nonce !== want.nonce)
|
|
90
95
|
why.push(r.nonce ? "the receipt carries another request's nonce (replayed)" : "the receipt does not carry the nonce this request sent");
|
|
91
96
|
const now = want.now ?? Date.now(), skew = Math.min(Math.max(Number(r.clock_skew_s) || 0, 0), MAX_CLOCK_SKEW_S) * 1000;
|
|
@@ -246,6 +251,8 @@ export class CrawlCheck {
|
|
|
246
251
|
b.policy = opts.policy;
|
|
247
252
|
if (opts.nonce)
|
|
248
253
|
b.nonce = opts.nonce;
|
|
254
|
+
if (opts.operation)
|
|
255
|
+
b.operation = opts.operation;
|
|
249
256
|
return this.post("/api/v1/preflight", b);
|
|
250
257
|
}
|
|
251
258
|
/** Verify a signed document here. With onlineKeys (default), its signing key must also be in the published directory. */
|
|
@@ -270,7 +277,7 @@ export class CrawlCheck {
|
|
|
270
277
|
}
|
|
271
278
|
if (this.log.forks.length)
|
|
272
279
|
return Object.assign(no(["the transparency log has shown this client two histories (split view)"], receipt), { split_view: this.log.forks.slice() });
|
|
273
|
-
const scope = checkDecision(receipt, { domain, action, nonce, now: opts.now });
|
|
280
|
+
const scope = checkDecision(receipt, { domain, action, nonce, now: opts.now, operation: opts.operation });
|
|
274
281
|
if (!receipt || typeof receipt !== "object")
|
|
275
282
|
return no(scope.why);
|
|
276
283
|
const online = opts.onlineKeys ?? true;
|
|
@@ -330,10 +337,11 @@ export class CrawlCheck {
|
|
|
330
337
|
const dom = normalizeDomain(u.hostname);
|
|
331
338
|
if (!dom)
|
|
332
339
|
return { proceed: false, response: null, url: cur, hops, why: ["not a public domain name: " + u.hostname] };
|
|
333
|
-
|
|
340
|
+
const op = { method: method, url: cur }; // op1: each hop's receipt names that exact request
|
|
341
|
+
let g = seen.get(method + " " + cur);
|
|
334
342
|
if (!g) {
|
|
335
|
-
g = await this.guard(dom, action, opts);
|
|
336
|
-
seen.set(
|
|
343
|
+
g = await this.guard(dom, action, Object.assign({}, opts, { operation: op }));
|
|
344
|
+
seen.set(method + " " + cur, g);
|
|
337
345
|
}
|
|
338
346
|
const hop = { url: cur, domain: dom, proceed: g.proceed, decision: g.receipt ? String(g.receipt.decision) : null, why: g.why };
|
|
339
347
|
hops.push(hop);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlcheck/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Typed client for the CrawlCheck API (generated from its OpenAPI document) and the zero-dependency offline verifier for evidence bundles and remediation receipts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|