protect-mcp 0.9.1 → 0.9.2
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/CHANGELOG.md +64 -0
- package/README.md +2 -2
- package/dist/{claim-U7Q3A4WW.mjs → claim-TUDH2WPB.mjs} +72 -0
- package/dist/cli.js +107 -3
- package/dist/cli.mjs +36 -4
- package/package.json +1 -1
- package/policies/agent.cedar +0 -50
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.2: anchor a claim to the public log
|
|
4
|
+
|
|
5
|
+
Closes the one honest gap in a bare claim: that the disclosed set is complete.
|
|
6
|
+
|
|
7
|
+
- Added `protect-mcp claim --anchor`: records the claim's digest in the public,
|
|
8
|
+
append-only ScopeBlind transparency log, so a counterparty who does not trust
|
|
9
|
+
you can confirm the exact claim existed at a time and was not quietly re-cut.
|
|
10
|
+
Only the hash is sent; the claim, its leaves, and every receipt stay on your
|
|
11
|
+
machine. Writes a `<claim>.anchor.json` sidecar with the log entry and a
|
|
12
|
+
verify URL. Anonymous and free; enrolling an org identity a counterparty can
|
|
13
|
+
pin is the paid upgrade (scopeblind.com/enroll).
|
|
14
|
+
|
|
15
|
+
## 0.9.1: prove a claim without revealing the record
|
|
16
|
+
|
|
17
|
+
The record viewer closes the loop from a decision to a portable proof.
|
|
18
|
+
|
|
19
|
+
- The `record` viewer surfaces the exact `claim` command when you filter by a
|
|
20
|
+
capability, so a decision becomes a signed, position-blind attestation in one
|
|
21
|
+
step, with copy-to-clipboard and an offline `verify-claim` hint.
|
|
22
|
+
- The browser drop-viewer (legate.scopeblind.com/record) reached parity with
|
|
23
|
+
the CLI: signed capability tags, a capability facet, and a provenance tree.
|
|
24
|
+
|
|
25
|
+
## 0.9.0: signed, position-blind claim attestations
|
|
26
|
+
|
|
27
|
+
Prove a predicate over the record without revealing it.
|
|
28
|
+
|
|
29
|
+
- Added `protect-mcp claim`: mints a signed attestation of a predicate over the
|
|
30
|
+
record (`--no <cap>` for "no action carried this capability", `--only`,
|
|
31
|
+
`--count`/`--no-verdict <verdict>`), disclosing only per-decision categories
|
|
32
|
+
(receipt digest, verdict, capability tags), never inputs, outputs, or data.
|
|
33
|
+
- Added `protect-mcp verify-claim`: checks it offline via Ed25519 over the pack,
|
|
34
|
+
a recomputed Merkle root over the disclosed set, and the predicate recomputed
|
|
35
|
+
independently. Honest trust model: an accountable position-blind attestation,
|
|
36
|
+
not zero-knowledge; set-completeness is issuer-attested.
|
|
37
|
+
|
|
38
|
+
## 0.8.0: deterministic receipt enrichment
|
|
39
|
+
|
|
40
|
+
Each signed decision now carries minimum-disclosure, rule-derived metadata.
|
|
41
|
+
|
|
42
|
+
- `input_digest`: SHA-256 of the canonicalized tool input, so you can prove what
|
|
43
|
+
was requested without storing it.
|
|
44
|
+
- Signed `capabilities`: deterministic, rule-derived tags (fs.read, fs.write,
|
|
45
|
+
net.egress, secret.adjacent, destructive, financial, exec.shell, ...) so the
|
|
46
|
+
record is queryable by what an action touched.
|
|
47
|
+
- `resource`: the hashed target (path, host, or command). The gate sees tool
|
|
48
|
+
calls, not the model's prompt or reasoning, and does not claim to.
|
|
49
|
+
|
|
50
|
+
## 0.7.6: the live record viewer
|
|
51
|
+
|
|
52
|
+
- Added `protect-mcp record --live`: a local, streaming view of the receipt
|
|
53
|
+
file that turns decisions into a searchable, auto-labelled record as the agent
|
|
54
|
+
runs (stat strip, per-row signed badge and digest, a List/Tree provenance
|
|
55
|
+
view), all local, nothing uploaded.
|
|
56
|
+
- One-click export: `.jsonl` carrying the real signatures (so a recipient
|
|
57
|
+
verifies offline with `npx @veritasacta/verify`) plus a Markdown report.
|
|
58
|
+
|
|
59
|
+
## 0.7.5: honest version strings and the record viewer
|
|
60
|
+
|
|
61
|
+
- Added `protect-mcp record`, a local browser viewer over the receipts file
|
|
62
|
+
(the answer to "the receipts are a hidden dotfile I cannot find").
|
|
63
|
+
- Fixed hard-coded version strings in the banner and health/serverInfo: they
|
|
64
|
+
read the resolved package version now.
|
|
65
|
+
- WebAuthn co-sign hardening: verify the rpIdHash and enforce challenge expiry.
|
|
66
|
+
|
|
3
67
|
## 0.7.4: the self-serve trial path
|
|
4
68
|
|
|
5
69
|
Everything the scopeblind.com trial and docs pages describe now ships in the
|
package/README.md
CHANGED
|
@@ -189,7 +189,7 @@ session always runs the gate you tested:
|
|
|
189
189
|
"hooks": [
|
|
190
190
|
{
|
|
191
191
|
"type": "command",
|
|
192
|
-
"command": "npx protect-mcp@0.
|
|
192
|
+
"command": "npx protect-mcp@0.9.1 evaluate --cedar ./cedar --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\""
|
|
193
193
|
}
|
|
194
194
|
]
|
|
195
195
|
}
|
|
@@ -200,7 +200,7 @@ session always runs the gate you tested:
|
|
|
200
200
|
"hooks": [
|
|
201
201
|
{
|
|
202
202
|
"type": "command",
|
|
203
|
-
"command": "npx protect-mcp@0.
|
|
203
|
+
"command": "npx protect-mcp@0.9.1 sign --tool \"$TOOL_NAME\" --receipts ./receipts --key ./keys/gateway.json"
|
|
204
204
|
}
|
|
205
205
|
]
|
|
206
206
|
}
|
|
@@ -118,9 +118,81 @@ function verifyClaim(pack, overridePublicKey) {
|
|
|
118
118
|
reasons
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
|
+
var ANCHOR_SCHEMA = "scopeblind.protect-mcp.anchor.v1";
|
|
122
|
+
var DEFAULT_LOG = "https://scopeblind.com";
|
|
123
|
+
function anchorDeepSort(o) {
|
|
124
|
+
if (o === null || typeof o !== "object") return o;
|
|
125
|
+
if (Array.isArray(o)) return o.map(anchorDeepSort);
|
|
126
|
+
const src = o;
|
|
127
|
+
const out = {};
|
|
128
|
+
for (const k of Object.keys(src).sort()) out[k] = anchorDeepSort(src[k]);
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
function toBase64(bytes) {
|
|
132
|
+
let bin = "";
|
|
133
|
+
for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
|
|
134
|
+
return btoa(bin);
|
|
135
|
+
}
|
|
136
|
+
function claimDigest(pack) {
|
|
137
|
+
return sha256Hex(canonicalJson(pack));
|
|
138
|
+
}
|
|
139
|
+
function buildAnchorEnvelope(pack, key, issuedAt) {
|
|
140
|
+
const signed = {
|
|
141
|
+
type: "evidence_pack",
|
|
142
|
+
schema: ANCHOR_SCHEMA,
|
|
143
|
+
anchors: "protect-mcp-claim",
|
|
144
|
+
claim_digest: claimDigest(pack),
|
|
145
|
+
record_root: pack.record.root,
|
|
146
|
+
statement: pack.claim.statement,
|
|
147
|
+
holds: pack.claim.holds,
|
|
148
|
+
matched: pack.claim.matched,
|
|
149
|
+
total: pack.scope.total,
|
|
150
|
+
issued_at: issuedAt,
|
|
151
|
+
verification_key: key.publicKey,
|
|
152
|
+
disclosure: "internal"
|
|
153
|
+
};
|
|
154
|
+
const hash = sha256(new TextEncoder().encode(JSON.stringify(anchorDeepSort(signed))));
|
|
155
|
+
const digest = bytesToHex(hash);
|
|
156
|
+
const signature = bytesToHex(ed25519.sign(hash, hexToBytes(key.privateKey)));
|
|
157
|
+
return { ...signed, signature, digest };
|
|
158
|
+
}
|
|
159
|
+
async function anchorClaim(pack, key, opts) {
|
|
160
|
+
const envelope = buildAnchorEnvelope(pack, key, opts.issuedAt);
|
|
161
|
+
const base = (opts.log || DEFAULT_LOG).replace(/\/+$/, "");
|
|
162
|
+
const doFetch = opts.fetchImpl || globalThis.fetch;
|
|
163
|
+
if (!doFetch) return { ok: false, claim_digest: envelope.claim_digest, error: "fetch_unavailable", envelope };
|
|
164
|
+
const encoded = toBase64(new TextEncoder().encode(JSON.stringify(envelope)));
|
|
165
|
+
try {
|
|
166
|
+
const resp = await doFetch(`${base}/fn/log/anchor-pack`, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers: { "content-type": "application/json" },
|
|
169
|
+
body: JSON.stringify({ encoded })
|
|
170
|
+
});
|
|
171
|
+
const data = await resp.json().catch(() => null);
|
|
172
|
+
if (!resp.ok || !data || !data.ok || typeof data.seq !== "number") {
|
|
173
|
+
return { ok: false, claim_digest: envelope.claim_digest, error: data && data.error || `http_${resp.status}`, envelope };
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
ok: true,
|
|
177
|
+
claim_digest: envelope.claim_digest,
|
|
178
|
+
seq: data.seq,
|
|
179
|
+
entry_url: `${base}/fn/log/${data.seq}`,
|
|
180
|
+
anchored_at: data.anchored_at,
|
|
181
|
+
already_anchored: !!data.already_anchored,
|
|
182
|
+
envelope
|
|
183
|
+
};
|
|
184
|
+
} catch {
|
|
185
|
+
return { ok: false, claim_digest: envelope.claim_digest, error: "network_error", envelope };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
121
188
|
export {
|
|
189
|
+
ANCHOR_SCHEMA,
|
|
122
190
|
CLAIM_TYPE,
|
|
191
|
+
DEFAULT_LOG,
|
|
192
|
+
anchorClaim,
|
|
193
|
+
buildAnchorEnvelope,
|
|
123
194
|
buildClaim,
|
|
195
|
+
claimDigest,
|
|
124
196
|
evaluate,
|
|
125
197
|
leafHash,
|
|
126
198
|
merkleRoot,
|
package/dist/cli.js
CHANGED
|
@@ -5176,8 +5176,13 @@ var init_receipt_enrichment = __esm({
|
|
|
5176
5176
|
// src/claim.ts
|
|
5177
5177
|
var claim_exports = {};
|
|
5178
5178
|
__export(claim_exports, {
|
|
5179
|
+
ANCHOR_SCHEMA: () => ANCHOR_SCHEMA,
|
|
5179
5180
|
CLAIM_TYPE: () => CLAIM_TYPE,
|
|
5181
|
+
DEFAULT_LOG: () => DEFAULT_LOG,
|
|
5182
|
+
anchorClaim: () => anchorClaim,
|
|
5183
|
+
buildAnchorEnvelope: () => buildAnchorEnvelope,
|
|
5180
5184
|
buildClaim: () => buildClaim,
|
|
5185
|
+
claimDigest: () => claimDigest,
|
|
5181
5186
|
evaluate: () => evaluate,
|
|
5182
5187
|
leafHash: () => leafHash,
|
|
5183
5188
|
merkleRoot: () => merkleRoot,
|
|
@@ -5286,7 +5291,72 @@ function verifyClaim(pack, overridePublicKey) {
|
|
|
5286
5291
|
reasons
|
|
5287
5292
|
};
|
|
5288
5293
|
}
|
|
5289
|
-
|
|
5294
|
+
function anchorDeepSort(o) {
|
|
5295
|
+
if (o === null || typeof o !== "object") return o;
|
|
5296
|
+
if (Array.isArray(o)) return o.map(anchorDeepSort);
|
|
5297
|
+
const src = o;
|
|
5298
|
+
const out = {};
|
|
5299
|
+
for (const k of Object.keys(src).sort()) out[k] = anchorDeepSort(src[k]);
|
|
5300
|
+
return out;
|
|
5301
|
+
}
|
|
5302
|
+
function toBase64(bytes) {
|
|
5303
|
+
let bin = "";
|
|
5304
|
+
for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
|
|
5305
|
+
return btoa(bin);
|
|
5306
|
+
}
|
|
5307
|
+
function claimDigest(pack) {
|
|
5308
|
+
return sha256Hex3(canonicalJson(pack));
|
|
5309
|
+
}
|
|
5310
|
+
function buildAnchorEnvelope(pack, key, issuedAt) {
|
|
5311
|
+
const signed = {
|
|
5312
|
+
type: "evidence_pack",
|
|
5313
|
+
schema: ANCHOR_SCHEMA,
|
|
5314
|
+
anchors: "protect-mcp-claim",
|
|
5315
|
+
claim_digest: claimDigest(pack),
|
|
5316
|
+
record_root: pack.record.root,
|
|
5317
|
+
statement: pack.claim.statement,
|
|
5318
|
+
holds: pack.claim.holds,
|
|
5319
|
+
matched: pack.claim.matched,
|
|
5320
|
+
total: pack.scope.total,
|
|
5321
|
+
issued_at: issuedAt,
|
|
5322
|
+
verification_key: key.publicKey,
|
|
5323
|
+
disclosure: "internal"
|
|
5324
|
+
};
|
|
5325
|
+
const hash = sha2562(new TextEncoder().encode(JSON.stringify(anchorDeepSort(signed))));
|
|
5326
|
+
const digest = bytesToHex(hash);
|
|
5327
|
+
const signature = bytesToHex(ed25519.sign(hash, hexToBytes(key.privateKey)));
|
|
5328
|
+
return { ...signed, signature, digest };
|
|
5329
|
+
}
|
|
5330
|
+
async function anchorClaim(pack, key, opts) {
|
|
5331
|
+
const envelope = buildAnchorEnvelope(pack, key, opts.issuedAt);
|
|
5332
|
+
const base = (opts.log || DEFAULT_LOG).replace(/\/+$/, "");
|
|
5333
|
+
const doFetch = opts.fetchImpl || globalThis.fetch;
|
|
5334
|
+
if (!doFetch) return { ok: false, claim_digest: envelope.claim_digest, error: "fetch_unavailable", envelope };
|
|
5335
|
+
const encoded = toBase64(new TextEncoder().encode(JSON.stringify(envelope)));
|
|
5336
|
+
try {
|
|
5337
|
+
const resp = await doFetch(`${base}/fn/log/anchor-pack`, {
|
|
5338
|
+
method: "POST",
|
|
5339
|
+
headers: { "content-type": "application/json" },
|
|
5340
|
+
body: JSON.stringify({ encoded })
|
|
5341
|
+
});
|
|
5342
|
+
const data = await resp.json().catch(() => null);
|
|
5343
|
+
if (!resp.ok || !data || !data.ok || typeof data.seq !== "number") {
|
|
5344
|
+
return { ok: false, claim_digest: envelope.claim_digest, error: data && data.error || `http_${resp.status}`, envelope };
|
|
5345
|
+
}
|
|
5346
|
+
return {
|
|
5347
|
+
ok: true,
|
|
5348
|
+
claim_digest: envelope.claim_digest,
|
|
5349
|
+
seq: data.seq,
|
|
5350
|
+
entry_url: `${base}/fn/log/${data.seq}`,
|
|
5351
|
+
anchored_at: data.anchored_at,
|
|
5352
|
+
already_anchored: !!data.already_anchored,
|
|
5353
|
+
envelope
|
|
5354
|
+
};
|
|
5355
|
+
} catch {
|
|
5356
|
+
return { ok: false, claim_digest: envelope.claim_digest, error: "network_error", envelope };
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5359
|
+
var CLAIM_TYPE, ANCHOR_SCHEMA, DEFAULT_LOG;
|
|
5290
5360
|
var init_claim = __esm({
|
|
5291
5361
|
"src/claim.ts"() {
|
|
5292
5362
|
"use strict";
|
|
@@ -5295,6 +5365,8 @@ var init_claim = __esm({
|
|
|
5295
5365
|
init_ed25519();
|
|
5296
5366
|
init_receipt_enrichment();
|
|
5297
5367
|
CLAIM_TYPE = "scopeblind.claim.v1";
|
|
5368
|
+
ANCHOR_SCHEMA = "scopeblind.protect-mcp.anchor.v1";
|
|
5369
|
+
DEFAULT_LOG = "https://scopeblind.com";
|
|
5298
5370
|
}
|
|
5299
5371
|
});
|
|
5300
5372
|
|
|
@@ -10712,8 +10784,11 @@ Attest a signed, position-blind claim over your record:
|
|
|
10712
10784
|
--only <c1,c2,...> all actions confined to these capabilities
|
|
10713
10785
|
--no-verdict <verdict> e.g. ${dim("--no-verdict blocked")}
|
|
10714
10786
|
--count <verdict> how many, e.g. ${dim("--count blocked")}
|
|
10787
|
+
--anchor also record the claim digest in the public append-only
|
|
10788
|
+
log so a counterparty can trust it is complete (only the
|
|
10789
|
+
hash is sent; your record stays local)
|
|
10715
10790
|
|
|
10716
|
-
Example: ${bold("npx protect-mcp claim --no net.egress")}
|
|
10791
|
+
Example: ${bold("npx protect-mcp claim --no net.egress --anchor")}
|
|
10717
10792
|
|
|
10718
10793
|
`);
|
|
10719
10794
|
process.exit(0);
|
|
@@ -10789,7 +10864,36 @@ ${bold("\u{1F6E1}\uFE0F Signed claim")}
|
|
|
10789
10864
|
process.stdout.write(` Written to ${out}
|
|
10790
10865
|
`);
|
|
10791
10866
|
process.stdout.write(` Hand it to anyone. They verify offline: ${bold("npx protect-mcp verify-claim " + out)}
|
|
10792
|
-
|
|
10867
|
+
`);
|
|
10868
|
+
if (argv.indexOf("--anchor") !== -1) {
|
|
10869
|
+
const { anchorClaim: anchorClaim2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
10870
|
+
const li = argv.indexOf("--log");
|
|
10871
|
+
const logBase = li !== -1 && argv[li + 1] ? argv[li + 1] : void 0;
|
|
10872
|
+
process.stdout.write(`
|
|
10873
|
+
${dim("Anchoring the claim digest to the public append-only log (only the hash leaves your machine)...")}
|
|
10874
|
+
`);
|
|
10875
|
+
const res = await anchorClaim2(
|
|
10876
|
+
pack,
|
|
10877
|
+
{ privateKey: key.privateKey, publicKey: key.publicKey, kid: key.kid || "gateway", issuer: "protect-mcp" },
|
|
10878
|
+
{ log: logBase, issuedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
10879
|
+
);
|
|
10880
|
+
if (res.ok) {
|
|
10881
|
+
const sidecar = out.replace(/\.json$/, "") + ".anchor.json";
|
|
10882
|
+
writeFileSync4(sidecar, JSON.stringify({ log: logBase || "https://scopeblind.com", seq: res.seq, entry_url: res.entry_url, anchored_at: res.anchored_at, claim_digest: res.claim_digest, envelope: res.envelope }, null, 2) + "\n");
|
|
10883
|
+
process.stdout.write(` ${green("Anchored")} as log entry ${bold("#" + res.seq)}${res.already_anchored ? dim(" (already present)") : ""} ${dim(res.entry_url || "")}
|
|
10884
|
+
`);
|
|
10885
|
+
process.stdout.write(` ${dim("A counterparty can now confirm this exact claim existed at " + (res.anchored_at || "this time") + " and cannot be quietly re-cut.")}
|
|
10886
|
+
`);
|
|
10887
|
+
process.stdout.write(` ${dim("Anchor record written to " + sidecar + ". Only the digest was sent; your record stayed local.")}
|
|
10888
|
+
`);
|
|
10889
|
+
process.stdout.write(` ${dim("To anchor as your enrolled org identity (a key a counterparty can pin), see")} ${bold("scopeblind.com/enroll")}
|
|
10890
|
+
`);
|
|
10891
|
+
} else {
|
|
10892
|
+
process.stdout.write(` ${yellow("Anchor skipped")} ${dim("(" + (res.error || "unavailable") + "). The claim above is complete and verifiable offline without it.")}
|
|
10893
|
+
`);
|
|
10894
|
+
}
|
|
10895
|
+
}
|
|
10896
|
+
process.stdout.write(`
|
|
10793
10897
|
`);
|
|
10794
10898
|
process.exit(0);
|
|
10795
10899
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -2235,7 +2235,7 @@ if(META.live){var poll=function(){fetch('/data',{cache:'no-store'}).then(functio
|
|
|
2235
2235
|
async function handleClaim(argv) {
|
|
2236
2236
|
const { readFileSync, existsSync, writeFileSync } = await import("fs");
|
|
2237
2237
|
const { join } = await import("path");
|
|
2238
|
-
const { buildClaim } = await import("./claim-
|
|
2238
|
+
const { buildClaim } = await import("./claim-TUDH2WPB.mjs");
|
|
2239
2239
|
let dir = process.cwd();
|
|
2240
2240
|
const di = argv.indexOf("--dir");
|
|
2241
2241
|
if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
|
|
@@ -2254,8 +2254,11 @@ Attest a signed, position-blind claim over your record:
|
|
|
2254
2254
|
--only <c1,c2,...> all actions confined to these capabilities
|
|
2255
2255
|
--no-verdict <verdict> e.g. ${dim("--no-verdict blocked")}
|
|
2256
2256
|
--count <verdict> how many, e.g. ${dim("--count blocked")}
|
|
2257
|
+
--anchor also record the claim digest in the public append-only
|
|
2258
|
+
log so a counterparty can trust it is complete (only the
|
|
2259
|
+
hash is sent; your record stays local)
|
|
2257
2260
|
|
|
2258
|
-
Example: ${bold("npx protect-mcp claim --no net.egress")}
|
|
2261
|
+
Example: ${bold("npx protect-mcp claim --no net.egress --anchor")}
|
|
2259
2262
|
|
|
2260
2263
|
`);
|
|
2261
2264
|
process.exit(0);
|
|
@@ -2331,13 +2334,42 @@ ${bold("\u{1F6E1}\uFE0F Signed claim")}
|
|
|
2331
2334
|
process.stdout.write(` Written to ${out}
|
|
2332
2335
|
`);
|
|
2333
2336
|
process.stdout.write(` Hand it to anyone. They verify offline: ${bold("npx protect-mcp verify-claim " + out)}
|
|
2334
|
-
|
|
2337
|
+
`);
|
|
2338
|
+
if (argv.indexOf("--anchor") !== -1) {
|
|
2339
|
+
const { anchorClaim } = await import("./claim-TUDH2WPB.mjs");
|
|
2340
|
+
const li = argv.indexOf("--log");
|
|
2341
|
+
const logBase = li !== -1 && argv[li + 1] ? argv[li + 1] : void 0;
|
|
2342
|
+
process.stdout.write(`
|
|
2343
|
+
${dim("Anchoring the claim digest to the public append-only log (only the hash leaves your machine)...")}
|
|
2344
|
+
`);
|
|
2345
|
+
const res = await anchorClaim(
|
|
2346
|
+
pack,
|
|
2347
|
+
{ privateKey: key.privateKey, publicKey: key.publicKey, kid: key.kid || "gateway", issuer: "protect-mcp" },
|
|
2348
|
+
{ log: logBase, issuedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
2349
|
+
);
|
|
2350
|
+
if (res.ok) {
|
|
2351
|
+
const sidecar = out.replace(/\.json$/, "") + ".anchor.json";
|
|
2352
|
+
writeFileSync(sidecar, JSON.stringify({ log: logBase || "https://scopeblind.com", seq: res.seq, entry_url: res.entry_url, anchored_at: res.anchored_at, claim_digest: res.claim_digest, envelope: res.envelope }, null, 2) + "\n");
|
|
2353
|
+
process.stdout.write(` ${green("Anchored")} as log entry ${bold("#" + res.seq)}${res.already_anchored ? dim(" (already present)") : ""} ${dim(res.entry_url || "")}
|
|
2354
|
+
`);
|
|
2355
|
+
process.stdout.write(` ${dim("A counterparty can now confirm this exact claim existed at " + (res.anchored_at || "this time") + " and cannot be quietly re-cut.")}
|
|
2356
|
+
`);
|
|
2357
|
+
process.stdout.write(` ${dim("Anchor record written to " + sidecar + ". Only the digest was sent; your record stayed local.")}
|
|
2358
|
+
`);
|
|
2359
|
+
process.stdout.write(` ${dim("To anchor as your enrolled org identity (a key a counterparty can pin), see")} ${bold("scopeblind.com/enroll")}
|
|
2360
|
+
`);
|
|
2361
|
+
} else {
|
|
2362
|
+
process.stdout.write(` ${yellow("Anchor skipped")} ${dim("(" + (res.error || "unavailable") + "). The claim above is complete and verifiable offline without it.")}
|
|
2363
|
+
`);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
process.stdout.write(`
|
|
2335
2367
|
`);
|
|
2336
2368
|
process.exit(0);
|
|
2337
2369
|
}
|
|
2338
2370
|
async function handleVerifyClaim(argv) {
|
|
2339
2371
|
const { readFileSync, existsSync } = await import("fs");
|
|
2340
|
-
const { verifyClaim } = await import("./claim-
|
|
2372
|
+
const { verifyClaim } = await import("./claim-TUDH2WPB.mjs");
|
|
2341
2373
|
const file = argv.find((a) => !a.startsWith("--"));
|
|
2342
2374
|
if (!file || !existsSync(file)) {
|
|
2343
2375
|
process.stderr.write(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "protect-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"mcpName": "com.scopeblind/protect-mcp",
|
|
5
5
|
"description": "Fail-closed Cedar policy gate + Ed25519 signed receipts for AI agent tool calls. Denies on any policy error, proves the gate is live with a startup self-test, and turns every decision into a local searchable record you own. The open gate behind Legate by ScopeBlind. scopeblind.com",
|
|
6
6
|
"main": "dist/index.js",
|
package/policies/agent.cedar
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// Generated by protect-mcp init-hooks
|
|
2
|
-
// Customize these policies to match your security requirements.
|
|
3
|
-
// Cedar deny decisions are AUTHORITATIVE — they cannot be overridden.
|
|
4
|
-
|
|
5
|
-
// Allow all read-only tools by default
|
|
6
|
-
permit(
|
|
7
|
-
principal,
|
|
8
|
-
action == Action::"MCP::Tool::call",
|
|
9
|
-
resource == Tool::"Read"
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
permit(
|
|
13
|
-
principal,
|
|
14
|
-
action == Action::"MCP::Tool::call",
|
|
15
|
-
resource == Tool::"Glob"
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
permit(
|
|
19
|
-
principal,
|
|
20
|
-
action == Action::"MCP::Tool::call",
|
|
21
|
-
resource == Tool::"Grep"
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
// Allow write/edit tools (remove these to require explicit approval)
|
|
25
|
-
permit(
|
|
26
|
-
principal,
|
|
27
|
-
action == Action::"MCP::Tool::call",
|
|
28
|
-
resource == Tool::"Write"
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
permit(
|
|
32
|
-
principal,
|
|
33
|
-
action == Action::"MCP::Tool::call",
|
|
34
|
-
resource == Tool::"Edit"
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
// Allow Bash with caution (Cedar evaluates before hook patterns)
|
|
38
|
-
permit(
|
|
39
|
-
principal,
|
|
40
|
-
action == Action::"MCP::Tool::call",
|
|
41
|
-
resource == Tool::"Bash"
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
// Block dangerous tools entirely
|
|
45
|
-
// Uncomment any of these to block specific tools:
|
|
46
|
-
// forbid(
|
|
47
|
-
// principal,
|
|
48
|
-
// action == Action::"MCP::Tool::call",
|
|
49
|
-
// resource == Tool::"delete_file"
|
|
50
|
-
// );
|