@crawlcheck/sdk 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.d.ts +54 -3
- package/dist/index.js +41 -5
- package/dist/mcp.js +5 -1
- package/dist/verify.d.ts +62 -0
- package/dist/verify.js +146 -0
- package/package.json +1 -1
- package/test/fixtures/guard-acceptance.json +1392 -449
- package/test/fixtures/key-policy.json +1983 -0
- package/test/guard-acceptance.test.js +1 -0
- package/test/key-policy.test.js +20 -0
- package/test/mcp.test.js +8 -1
|
@@ -16,6 +16,7 @@ function fakeFetch(c, log) {
|
|
|
16
16
|
const p = new URL(url).pathname, cc = c.crawlcheck;
|
|
17
17
|
if (cc.unreachable) throw new TypeError("fetch failed");
|
|
18
18
|
if (p === "/.well-known/http-message-signatures-directory") return new Response(JSON.stringify(suite.directory), { headers: { "content-type": "application/json" } });
|
|
19
|
+
if (p === "/.well-known/security-key-rotation.json") { const rot = cc.rotation || suite.rotation; return rot ? new Response(JSON.stringify(rot), { headers: { "content-type": "application/json" } }) : new Response("{}", { status: 404 }); } // conf2
|
|
19
20
|
if (p === "/api/v1/preflight") {
|
|
20
21
|
const b = JSON.parse(init.body || "{}"); log.asked.push(b.domain);
|
|
21
22
|
if (cc.by_op) { const o = b.operation || {}; const r = cc.by_op[o.method + " " + o.url]; return r ? new Response(JSON.stringify(r), { headers: { "content-type": "application/json" } }) : new Response("{\"error\":\"none\"}", { status: 404 }); }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The signing-policy suite (https://crawlcheck.io/keys#policy) against this SDK. Same cases as the Python package and
|
|
2
|
+
// crawlcheck-verify.mjs (spec/key-policy/run.mjs runs all three). CC_SDK=<path or package name> runs another build.
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
6
|
+
const here = new URL(".", import.meta.url);
|
|
7
|
+
const suite = JSON.parse(readFileSync([new URL("../../spec/key-policy/cases.json", here), new URL("./fixtures/key-policy.json", here)].find((u) => existsSync(u)), "utf8"));
|
|
8
|
+
const S = await import(process.env.CC_SDK || "../dist/index.js");
|
|
9
|
+
|
|
10
|
+
test("KEY_POLICY is the published policy", () => {
|
|
11
|
+
for (const k of ["object_purpose", "chains", "starts", "bound", "purpose_aliases"]) assert.deepEqual(S.KEY_POLICY[k], suite.policy[k], k);
|
|
12
|
+
});
|
|
13
|
+
for (const c of suite.cases) {
|
|
14
|
+
test(c.id + ": " + c.title, async () => {
|
|
15
|
+
const kp = await S.keyPolicyFrom(suite.directory, c.keys === "directory" ? null : suite.rotation);
|
|
16
|
+
const r = S.applyKeyPolicy(await S.verifyDocument(c.doc), c.doc, kp);
|
|
17
|
+
const why = (r.checks.find((x) => x.id === "key_purpose") || {}).why;
|
|
18
|
+
for (const [k, v] of Object.entries(c.expect)) assert.equal(r[k], v, k + ": " + why);
|
|
19
|
+
});
|
|
20
|
+
}
|
package/test/mcp.test.js
CHANGED
|
@@ -5,7 +5,7 @@ import { withPreflight, mcpPreflight, McpPreflightRefused, transportUrl, lockFro
|
|
|
5
5
|
function cc(table) {
|
|
6
6
|
const calls = [];
|
|
7
7
|
return { calls, async guard(host, action, opts) {
|
|
8
|
-
calls.push({ host, action, template: opts.template, tool: opts.operation && opts.operation.tool, op: opts.operation });
|
|
8
|
+
calls.push({ host, action, template: opts.template, tool: opts.operation && opts.operation.tool, op: opts.operation, mcp: opts.mcp });
|
|
9
9
|
const d = table[host] || "unsupported";
|
|
10
10
|
const receipt = { kind: "crawlcheck-decision", domain: host, action, decision: d, expires_at: new Date(Date.now() + 3600e3).toISOString(), steps: [{ why: "table" }], sha256: "x", signature: { kid: "k" } };
|
|
11
11
|
let proceed = d === "allow" || (d === "warn" && !!opts.allowWarn);
|
|
@@ -48,6 +48,13 @@ test("require_confirmation: refused without a confirm, connects when a person sa
|
|
|
48
48
|
let seen = null; const c = withPreflight(mcpClient(TOOLS), k, { confirm: async (r) => { seen = r; return true; } });
|
|
49
49
|
await c.connect(T("https://ask.example/mcp")); assert.equal(seen.decision, "require_confirmation"); assert.equal(c.sent.length, 1);
|
|
50
50
|
});
|
|
51
|
+
test("tscope1: the connect decision names the server and the approved tools_sha256", async () => {
|
|
52
|
+
const lock = await lockFromTools([TOOLS[0]]), k = cc({ "good.example": "allow" });
|
|
53
|
+
await withPreflight(mcpClient([TOOLS[0]]), k, { lock }).connect(T("https://good.example/mcp"));
|
|
54
|
+
assert.deepEqual(k.calls[0].mcp, { url: "https://good.example/mcp", tools_sha256: lock.tools_sha256 });
|
|
55
|
+
const k2 = cc({ "good.example": "allow" }); await withPreflight(mcpClient(TOOLS), k2).connect(T("https://good.example/mcp"));
|
|
56
|
+
assert.deepEqual(k2.calls[0].mcp, { url: "https://good.example/mcp" });
|
|
57
|
+
});
|
|
51
58
|
test("lockfile: changed tools close the client again; a tool outside the lock is refused", async () => {
|
|
52
59
|
const lock = await lockFromTools([TOOLS[0]]);
|
|
53
60
|
const c = withPreflight(mcpClient(TOOLS), cc({ "good.example": "allow" }), { lock });
|