@permission-protocol/mcp-guard 0.1.0 → 0.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/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +101 -59
- package/SECURITY.md +40 -0
- package/dist/src/approval-server.d.ts +69 -3
- package/dist/src/approval-server.js +345 -86
- package/dist/src/approval-server.js.map +1 -1
- package/dist/src/approval-ui.d.ts +5 -0
- package/dist/src/approval-ui.js +120 -81
- package/dist/src/approval-ui.js.map +1 -1
- package/dist/src/classify.d.ts +29 -0
- package/dist/src/classify.js +107 -0
- package/dist/src/classify.js.map +1 -0
- package/dist/src/cli.js +49 -12
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.d.ts +21 -1
- package/dist/src/config.js +92 -5
- package/dist/src/config.js.map +1 -1
- package/dist/src/enforcement/index.d.ts +12 -0
- package/dist/src/enforcement/index.js +53 -0
- package/dist/src/enforcement/index.js.map +1 -0
- package/dist/src/enforcement/policy.d.ts +29 -0
- package/dist/src/enforcement/policy.js +60 -0
- package/dist/src/enforcement/policy.js.map +1 -0
- package/dist/src/enforcement/types.d.ts +19 -0
- package/dist/src/enforcement/types.js +3 -0
- package/dist/src/enforcement/types.js.map +1 -0
- package/dist/src/enforcement/verifier.d.ts +4 -0
- package/dist/src/enforcement/verifier.js +110 -0
- package/dist/src/enforcement/verifier.js.map +1 -0
- package/dist/src/engine.d.ts +11 -2
- package/dist/src/engine.js +20 -14
- package/dist/src/engine.js.map +1 -1
- package/dist/src/frame.d.ts +67 -0
- package/dist/src/frame.js +113 -0
- package/dist/src/frame.js.map +1 -0
- package/dist/src/pending.d.ts +108 -12
- package/dist/src/pending.js +174 -24
- package/dist/src/pending.js.map +1 -1
- package/dist/src/proxy.d.ts +29 -2
- package/dist/src/proxy.js +274 -105
- package/dist/src/proxy.js.map +1 -1
- package/dist/src/receipt-verify.d.ts +36 -0
- package/dist/src/receipt-verify.js +88 -0
- package/dist/src/receipt-verify.js.map +1 -0
- package/dist/src/receipt.d.ts +81 -7
- package/dist/src/receipt.js +100 -17
- package/dist/src/receipt.js.map +1 -1
- package/dist/src/signer.d.ts +38 -0
- package/dist/src/signer.js +112 -0
- package/dist/src/signer.js.map +1 -0
- package/dist/src/verify-cli.d.ts +1 -0
- package/dist/src/verify-cli.js +133 -0
- package/dist/src/verify-cli.js.map +1 -0
- package/dist/src/verify-core.d.ts +22 -0
- package/dist/src/verify-core.js +50 -0
- package/dist/src/verify-core.js.map +1 -0
- package/dist/src/webhook.d.ts +35 -0
- package/dist/src/webhook.js +57 -0
- package/dist/src/webhook.js.map +1 -0
- package/example/test.sh +5 -7
- package/package.json +33 -7
- package/templates/github-actions/receipt-gate.yml +64 -0
- package/assets/demo.gif +0 -0
- package/demo/_demo-scenario.sh +0 -43
- package/dist/src/tests/config.test.d.ts +0 -1
- package/dist/src/tests/config.test.js +0 -63
- package/dist/src/tests/config.test.js.map +0 -1
- package/dist/src/tests/engine.test.d.ts +0 -1
- package/dist/src/tests/engine.test.js +0 -46
- package/dist/src/tests/engine.test.js.map +0 -1
- package/dist/src/tests/receipt.test.d.ts +0 -1
- package/dist/src/tests/receipt.test.js +0 -44
- package/dist/src/tests/receipt.test.js.map +0 -1
- package/docs/index.html +0 -771
- package/permission-protocol-mcp-guard-0.1.0.tgz +0 -0
- package/src/approval-server.ts +0 -136
- package/src/approval-ui.ts +0 -154
- package/src/cli.ts +0 -84
- package/src/config.ts +0 -68
- package/src/engine.ts +0 -31
- package/src/pending.ts +0 -65
- package/src/proxy.ts +0 -173
- package/src/receipt.ts +0 -52
- package/src/tests/config.test.ts +0 -61
- package/src/tests/engine.test.ts +0 -47
- package/src/tests/receipt.test.ts +0 -44
- package/tsconfig.json +0 -16
package/dist/src/receipt.d.ts
CHANGED
|
@@ -1,15 +1,89 @@
|
|
|
1
1
|
import type { Decision } from './engine.js';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Receipt v2.
|
|
4
|
+
*
|
|
5
|
+
* A receipt is a small signed ENVELOPE — the fields a verifier decides on — plus an
|
|
6
|
+
* unsigned DISPLAY block generated from it for humans. The signature covers
|
|
7
|
+
* `SIGNING_DOMAIN + canonical JSON(envelope)`, so every authority-relevant field is
|
|
8
|
+
* bound and no field boundary can be re-interpreted. Verifiers never read `display`.
|
|
9
|
+
*
|
|
10
|
+
* Receipts never carry raw tool arguments: only their sha256 (`args_sha256`) and the
|
|
11
|
+
* argument names. Raw arguments are shown only in the authenticated approval queue.
|
|
12
|
+
*/
|
|
13
|
+
export declare const RECEIPT_VERSION = 2;
|
|
14
|
+
export declare const SIGNING_DOMAIN = "pp-mcp-guard-receipt-v2\n";
|
|
15
|
+
export type ApprovalKind = 'human' | 'policy';
|
|
16
|
+
/** Who or what authorized the action. `via` names the channel or mechanism. */
|
|
17
|
+
export interface Approval {
|
|
18
|
+
kind: ApprovalKind;
|
|
19
|
+
id: string;
|
|
20
|
+
via: string;
|
|
21
|
+
}
|
|
22
|
+
/** Scope binding for code/infra actions, as derived from a request. */
|
|
23
|
+
export interface ScopeBinding {
|
|
24
|
+
scope?: string;
|
|
25
|
+
scope_ref?: string;
|
|
26
|
+
scope_sha?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ReceiptScope {
|
|
29
|
+
scope: string;
|
|
30
|
+
ref: string | null;
|
|
31
|
+
sha: string | null;
|
|
32
|
+
}
|
|
33
|
+
export interface ReceiptEnvelope {
|
|
34
|
+
v: 2;
|
|
3
35
|
receipt_id: string;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
36
|
+
method: string;
|
|
37
|
+
tool: string | null;
|
|
38
|
+
args_sha256: string;
|
|
39
|
+
scope: ReceiptScope | null;
|
|
7
40
|
decision: Decision['decision'];
|
|
8
|
-
|
|
41
|
+
approval: Approval | null;
|
|
42
|
+
actor: string;
|
|
9
43
|
rule_id: string | null;
|
|
10
|
-
|
|
44
|
+
policy_sha256: string;
|
|
45
|
+
mode: 'enforce' | 'observe';
|
|
46
|
+
issued_at: string;
|
|
47
|
+
expires_at: string;
|
|
48
|
+
nonce: string;
|
|
49
|
+
key_id: string;
|
|
50
|
+
}
|
|
51
|
+
export interface ReceiptDisplay {
|
|
52
|
+
summary: string;
|
|
53
|
+
reason: string;
|
|
54
|
+
risk_tier: 'low' | 'medium' | 'high' | 'critical';
|
|
11
55
|
target_server: string;
|
|
56
|
+
arg_keys: string[];
|
|
57
|
+
viewer_url: string;
|
|
58
|
+
}
|
|
59
|
+
export interface Receipt {
|
|
60
|
+
envelope: ReceiptEnvelope;
|
|
61
|
+
signature: {
|
|
62
|
+
alg: 'ed25519';
|
|
63
|
+
key_id: string;
|
|
64
|
+
value: string;
|
|
65
|
+
};
|
|
66
|
+
display: ReceiptDisplay;
|
|
67
|
+
}
|
|
68
|
+
export interface ReceiptInput {
|
|
69
|
+
method: string;
|
|
70
|
+
tool: string | null;
|
|
71
|
+
args: Record<string, unknown> | undefined;
|
|
72
|
+
decision: Decision;
|
|
73
|
+
approval: Approval | null;
|
|
74
|
+
actor: string;
|
|
12
75
|
mode: 'enforce' | 'observe';
|
|
76
|
+
policy_sha256: string;
|
|
77
|
+
ttl_seconds: number;
|
|
78
|
+
target_server: string;
|
|
79
|
+
scope?: ScopeBinding;
|
|
80
|
+
now?: Date;
|
|
13
81
|
}
|
|
14
|
-
export declare function
|
|
82
|
+
export declare function signingBytes(envelope: ReceiptEnvelope): string;
|
|
83
|
+
/** sha256 of the canonical JSON of the arguments the server receives. */
|
|
84
|
+
export declare function hashArgs(args: Record<string, unknown> | undefined): string;
|
|
85
|
+
/** Build and sign a receipt. */
|
|
86
|
+
export declare function issueReceipt(input: ReceiptInput): Receipt;
|
|
87
|
+
export declare function defaultReceiptsPath(): string;
|
|
88
|
+
/** Log, persist and (optionally) publish a receipt. Receipts never hold raw arguments. */
|
|
15
89
|
export declare function emitReceipt(receipt: Receipt, receiptsPath?: string): void;
|
package/dist/src/receipt.js
CHANGED
|
@@ -1,34 +1,117 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
6
|
+
exports.SIGNING_DOMAIN = exports.RECEIPT_VERSION = void 0;
|
|
7
|
+
exports.signingBytes = signingBytes;
|
|
8
|
+
exports.hashArgs = hashArgs;
|
|
9
|
+
exports.issueReceipt = issueReceipt;
|
|
10
|
+
exports.defaultReceiptsPath = defaultReceiptsPath;
|
|
4
11
|
exports.emitReceipt = emitReceipt;
|
|
5
12
|
const node_crypto_1 = require("node:crypto");
|
|
6
13
|
const node_fs_1 = require("node:fs");
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
const fast_json_stable_stringify_1 = __importDefault(require("fast-json-stable-stringify"));
|
|
15
|
+
const signer_js_1 = require("./signer.js");
|
|
16
|
+
/**
|
|
17
|
+
* Receipt v2.
|
|
18
|
+
*
|
|
19
|
+
* A receipt is a small signed ENVELOPE — the fields a verifier decides on — plus an
|
|
20
|
+
* unsigned DISPLAY block generated from it for humans. The signature covers
|
|
21
|
+
* `SIGNING_DOMAIN + canonical JSON(envelope)`, so every authority-relevant field is
|
|
22
|
+
* bound and no field boundary can be re-interpreted. Verifiers never read `display`.
|
|
23
|
+
*
|
|
24
|
+
* Receipts never carry raw tool arguments: only their sha256 (`args_sha256`) and the
|
|
25
|
+
* argument names. Raw arguments are shown only in the authenticated approval queue.
|
|
26
|
+
*/
|
|
27
|
+
exports.RECEIPT_VERSION = 2;
|
|
28
|
+
exports.SIGNING_DOMAIN = 'pp-mcp-guard-receipt-v2\n';
|
|
29
|
+
function signingBytes(envelope) {
|
|
30
|
+
return exports.SIGNING_DOMAIN + (0, fast_json_stable_stringify_1.default)(envelope);
|
|
31
|
+
}
|
|
32
|
+
/** sha256 of the canonical JSON of the arguments the server receives. */
|
|
33
|
+
function hashArgs(args) {
|
|
34
|
+
return (0, node_crypto_1.createHash)('sha256').update((0, fast_json_stable_stringify_1.default)(args ?? {})).digest('hex');
|
|
35
|
+
}
|
|
36
|
+
function toScope(binding) {
|
|
37
|
+
if (!binding?.scope)
|
|
38
|
+
return null;
|
|
39
|
+
return { scope: binding.scope, ref: binding.scope_ref ?? null, sha: binding.scope_sha ?? null };
|
|
40
|
+
}
|
|
41
|
+
function classifyRisk(tool, argKeys) {
|
|
42
|
+
const text = `${tool} ${argKeys.join(' ')}`.toLowerCase();
|
|
43
|
+
if (/(delete|destroy|drop|truncate|wipe|remove)/.test(text))
|
|
44
|
+
return 'critical';
|
|
45
|
+
if (/(prod|deploy|release|publish|billing|payment|spend|merge|migrat|auth|admin|secret|token|password|key)/.test(text))
|
|
46
|
+
return 'high';
|
|
47
|
+
if (/(write|update|create|patch|modify|sync|email|post|notify)/.test(text))
|
|
48
|
+
return 'medium';
|
|
49
|
+
return 'low';
|
|
50
|
+
}
|
|
51
|
+
/** Build and sign a receipt. */
|
|
52
|
+
function issueReceipt(input) {
|
|
53
|
+
const now = input.now ?? new Date();
|
|
54
|
+
const receiptId = `rcpt_mg_${now.getTime().toString(36)}_${(0, node_crypto_1.randomBytes)(6).toString('hex')}`;
|
|
55
|
+
const argKeys = Object.keys(input.args ?? {}).sort();
|
|
56
|
+
const label = input.tool ?? input.method;
|
|
57
|
+
const unsigned = {
|
|
58
|
+
v: exports.RECEIPT_VERSION,
|
|
59
|
+
receipt_id: receiptId,
|
|
60
|
+
method: input.method,
|
|
61
|
+
tool: input.tool,
|
|
62
|
+
args_sha256: hashArgs(input.args),
|
|
63
|
+
scope: toScope(input.scope),
|
|
64
|
+
decision: input.decision.decision,
|
|
65
|
+
approval: input.approval,
|
|
66
|
+
actor: input.actor,
|
|
67
|
+
rule_id: input.decision.rule_id,
|
|
68
|
+
policy_sha256: input.policy_sha256,
|
|
69
|
+
mode: input.mode,
|
|
70
|
+
issued_at: now.toISOString(),
|
|
71
|
+
expires_at: new Date(now.getTime() + input.ttl_seconds * 1000).toISOString(),
|
|
72
|
+
nonce: (0, node_crypto_1.randomBytes)(16).toString('hex'),
|
|
73
|
+
};
|
|
74
|
+
// The key id is part of the signed envelope, so resolve it before signing.
|
|
75
|
+
const envelope = { ...unsigned, key_id: (0, signer_js_1.currentKeyId)() };
|
|
76
|
+
const sig = (0, signer_js_1.signBytes)(signingBytes(envelope));
|
|
77
|
+
const viewerBase = (process.env.PP_VIEWER_BASE_URL || 'https://app.permissionprotocol.com/r').replace(/\/$/, '');
|
|
78
|
+
const by = input.approval ? ` by ${input.approval.kind} ${input.approval.id}` : '';
|
|
11
79
|
return {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
80
|
+
envelope,
|
|
81
|
+
signature: { alg: signer_js_1.SIGN_ALGORITHM, key_id: sig.keyId, value: sig.value },
|
|
82
|
+
display: {
|
|
83
|
+
summary: `MCP Guard ${input.decision.decision.replace(/_/g, ' ')} "${label}"${by}`,
|
|
84
|
+
reason: input.decision.reason,
|
|
85
|
+
risk_tier: classifyRisk(label, argKeys),
|
|
86
|
+
target_server: input.target_server,
|
|
87
|
+
arg_keys: argKeys,
|
|
88
|
+
viewer_url: `${viewerBase}/${receiptId}`,
|
|
89
|
+
},
|
|
22
90
|
};
|
|
23
91
|
}
|
|
24
|
-
function
|
|
92
|
+
function defaultReceiptsPath() {
|
|
93
|
+
return process.env.PP_SHARED_RECEIPTS_PATH || 'pp-receipts.jsonl';
|
|
94
|
+
}
|
|
95
|
+
/** Log, persist and (optionally) publish a receipt. Receipts never hold raw arguments. */
|
|
96
|
+
function emitReceipt(receipt, receiptsPath = defaultReceiptsPath()) {
|
|
97
|
+
const e = receipt.envelope;
|
|
25
98
|
const json = JSON.stringify(receipt);
|
|
26
|
-
process.stderr.write(`[mcp-guard] receipt
|
|
99
|
+
process.stderr.write(`[mcp-guard] receipt ${e.receipt_id} ${e.decision} "${e.tool ?? e.method}"${e.approval ? ` (${e.approval.kind}:${e.approval.id})` : ''}\n`);
|
|
27
100
|
try {
|
|
28
101
|
(0, node_fs_1.appendFileSync)(receiptsPath, json + '\n');
|
|
29
102
|
}
|
|
30
103
|
catch {
|
|
31
104
|
// Best effort — don't crash the proxy if file write fails
|
|
32
105
|
}
|
|
106
|
+
const endpoint = process.env.PP_RECEIPT_ENDPOINT;
|
|
107
|
+
if (!endpoint)
|
|
108
|
+
return;
|
|
109
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
110
|
+
if (process.env.PP_RECEIPT_TOKEN)
|
|
111
|
+
headers.Authorization = `Bearer ${process.env.PP_RECEIPT_TOKEN}`;
|
|
112
|
+
void fetch(endpoint, { method: 'POST', headers, body: json }).catch((error) => {
|
|
113
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
114
|
+
process.stderr.write(`[mcp-guard] receipt publish failed: ${message}\n`);
|
|
115
|
+
});
|
|
33
116
|
}
|
|
34
117
|
//# sourceMappingURL=receipt.js.map
|
package/dist/src/receipt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"receipt.js","sourceRoot":"","sources":["../../src/receipt.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"receipt.js","sourceRoot":"","sources":["../../src/receipt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type KeyObject } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Local Ed25519 dev signer.
|
|
4
|
+
*
|
|
5
|
+
* The keypair is generated once and kept in the operator's config directory
|
|
6
|
+
* (`$PP_KEYS_DIR`, else `$XDG_CONFIG_HOME/mcp-guard/keys`, else
|
|
7
|
+
* `~/.config/mcp-guard/keys`) — never in the working directory, which is usually the
|
|
8
|
+
* agent's workspace. The directory is 0700 and the private key 0600; a key with looser
|
|
9
|
+
* permissions is refused rather than used.
|
|
10
|
+
*
|
|
11
|
+
* The key id is derived from the public key (`pp-dev-<fingerprint>`), so pinning a
|
|
12
|
+
* public key pins its id and a regenerated key can never pass as the old one.
|
|
13
|
+
*
|
|
14
|
+
* This is the dev signer of the open-core boundary; the managed PP signer replaces it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SIGN_ALGORITHM = "ed25519";
|
|
17
|
+
export declare class KeyPermissionError extends Error {
|
|
18
|
+
readonly code = "PP_KEY_PERMISSIONS";
|
|
19
|
+
}
|
|
20
|
+
export declare function keysDir(): string;
|
|
21
|
+
/** `pp-dev-` + the first 16 hex chars of sha256(SPKI DER). */
|
|
22
|
+
export declare function keyIdForPublicKey(publicKey: KeyObject | string): string;
|
|
23
|
+
/** The active signing key id. */
|
|
24
|
+
export declare function currentKeyId(): string;
|
|
25
|
+
/** The active public key in SPKI PEM form — what verifiers pin. */
|
|
26
|
+
export declare function getPublicKeyPem(): string;
|
|
27
|
+
/** Sign bytes with the dev key. Returns the hex signature and the key id it belongs to. */
|
|
28
|
+
export declare function signBytes(data: string | Buffer): {
|
|
29
|
+
keyId: string;
|
|
30
|
+
value: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Write the public key to `path` (0644) so it can be committed or distributed as the
|
|
34
|
+
* pinned trust anchor. Never writes the private key.
|
|
35
|
+
*/
|
|
36
|
+
export declare function exportPublicKey(path: string): string;
|
|
37
|
+
/** Test/teardown helper: forget the cached keypair. */
|
|
38
|
+
export declare function _resetSigner(): void;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KeyPermissionError = exports.SIGN_ALGORITHM = void 0;
|
|
4
|
+
exports.keysDir = keysDir;
|
|
5
|
+
exports.keyIdForPublicKey = keyIdForPublicKey;
|
|
6
|
+
exports.currentKeyId = currentKeyId;
|
|
7
|
+
exports.getPublicKeyPem = getPublicKeyPem;
|
|
8
|
+
exports.signBytes = signBytes;
|
|
9
|
+
exports.exportPublicKey = exportPublicKey;
|
|
10
|
+
exports._resetSigner = _resetSigner;
|
|
11
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
|
+
const node_fs_1 = require("node:fs");
|
|
13
|
+
const node_os_1 = require("node:os");
|
|
14
|
+
const node_path_1 = require("node:path");
|
|
15
|
+
/**
|
|
16
|
+
* Local Ed25519 dev signer.
|
|
17
|
+
*
|
|
18
|
+
* The keypair is generated once and kept in the operator's config directory
|
|
19
|
+
* (`$PP_KEYS_DIR`, else `$XDG_CONFIG_HOME/mcp-guard/keys`, else
|
|
20
|
+
* `~/.config/mcp-guard/keys`) — never in the working directory, which is usually the
|
|
21
|
+
* agent's workspace. The directory is 0700 and the private key 0600; a key with looser
|
|
22
|
+
* permissions is refused rather than used.
|
|
23
|
+
*
|
|
24
|
+
* The key id is derived from the public key (`pp-dev-<fingerprint>`), so pinning a
|
|
25
|
+
* public key pins its id and a regenerated key can never pass as the old one.
|
|
26
|
+
*
|
|
27
|
+
* This is the dev signer of the open-core boundary; the managed PP signer replaces it.
|
|
28
|
+
*/
|
|
29
|
+
exports.SIGN_ALGORITHM = 'ed25519';
|
|
30
|
+
const PRIVATE_FILE = 'signing.private.pem';
|
|
31
|
+
const PUBLIC_FILE = 'signing.public.pem';
|
|
32
|
+
class KeyPermissionError extends Error {
|
|
33
|
+
code = 'PP_KEY_PERMISSIONS';
|
|
34
|
+
}
|
|
35
|
+
exports.KeyPermissionError = KeyPermissionError;
|
|
36
|
+
function keysDir() {
|
|
37
|
+
if (process.env.PP_KEYS_DIR)
|
|
38
|
+
return process.env.PP_KEYS_DIR;
|
|
39
|
+
const base = process.env.XDG_CONFIG_HOME || (0, node_path_1.join)((0, node_os_1.homedir)(), '.config');
|
|
40
|
+
return (0, node_path_1.join)(base, 'mcp-guard', 'keys');
|
|
41
|
+
}
|
|
42
|
+
/** `pp-dev-` + the first 16 hex chars of sha256(SPKI DER). */
|
|
43
|
+
function keyIdForPublicKey(publicKey) {
|
|
44
|
+
const key = typeof publicKey === 'string' ? (0, node_crypto_1.createPublicKey)(publicKey) : publicKey;
|
|
45
|
+
const der = key.export({ type: 'spki', format: 'der' });
|
|
46
|
+
return 'pp-dev-' + (0, node_crypto_1.createHash)('sha256').update(der).digest('hex').slice(0, 16);
|
|
47
|
+
}
|
|
48
|
+
let cached = null;
|
|
49
|
+
function assertPrivate(path, mask) {
|
|
50
|
+
if (process.platform === 'win32')
|
|
51
|
+
return;
|
|
52
|
+
const mode = (0, node_fs_1.statSync)(path).mode & 0o777;
|
|
53
|
+
if (mode & mask) {
|
|
54
|
+
throw new KeyPermissionError(`Refusing to use signing key material at ${path}: mode ${mode.toString(8)} is readable or writable by others (key must be 0600 in a directory only you can write)`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function ensureKeypair() {
|
|
58
|
+
const dir = keysDir();
|
|
59
|
+
if (cached && cached.dir === dir)
|
|
60
|
+
return cached;
|
|
61
|
+
const privPath = (0, node_path_1.join)(dir, PRIVATE_FILE);
|
|
62
|
+
const pubPath = (0, node_path_1.join)(dir, PUBLIC_FILE);
|
|
63
|
+
let privateKey;
|
|
64
|
+
let publicKey;
|
|
65
|
+
if ((0, node_fs_1.existsSync)(privPath)) {
|
|
66
|
+
assertPrivate(dir, 0o022);
|
|
67
|
+
assertPrivate(privPath, 0o077);
|
|
68
|
+
privateKey = (0, node_crypto_1.createPrivateKey)((0, node_fs_1.readFileSync)(privPath, 'utf-8'));
|
|
69
|
+
publicKey = (0, node_crypto_1.createPublicKey)(privateKey);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true, mode: 0o700 });
|
|
73
|
+
try {
|
|
74
|
+
(0, node_fs_1.chmodSync)(dir, 0o700);
|
|
75
|
+
}
|
|
76
|
+
catch { /* best effort on platforms without chmod */ }
|
|
77
|
+
const pair = (0, node_crypto_1.generateKeyPairSync)('ed25519');
|
|
78
|
+
privateKey = pair.privateKey;
|
|
79
|
+
publicKey = pair.publicKey;
|
|
80
|
+
(0, node_fs_1.writeFileSync)(privPath, privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(), { mode: 0o600, flag: 'wx' });
|
|
81
|
+
(0, node_fs_1.writeFileSync)(pubPath, publicKey.export({ type: 'spki', format: 'pem' }).toString(), { mode: 0o644 });
|
|
82
|
+
}
|
|
83
|
+
cached = { dir, privateKey, publicKey, keyId: keyIdForPublicKey(publicKey) };
|
|
84
|
+
return cached;
|
|
85
|
+
}
|
|
86
|
+
/** The active signing key id. */
|
|
87
|
+
function currentKeyId() {
|
|
88
|
+
return ensureKeypair().keyId;
|
|
89
|
+
}
|
|
90
|
+
/** The active public key in SPKI PEM form — what verifiers pin. */
|
|
91
|
+
function getPublicKeyPem() {
|
|
92
|
+
return ensureKeypair().publicKey.export({ type: 'spki', format: 'pem' }).toString();
|
|
93
|
+
}
|
|
94
|
+
/** Sign bytes with the dev key. Returns the hex signature and the key id it belongs to. */
|
|
95
|
+
function signBytes(data) {
|
|
96
|
+
const { privateKey, keyId } = ensureKeypair();
|
|
97
|
+
const buf = typeof data === 'string' ? Buffer.from(data) : data;
|
|
98
|
+
return { keyId, value: (0, node_crypto_1.sign)(null, buf, privateKey).toString('hex') };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Write the public key to `path` (0644) so it can be committed or distributed as the
|
|
102
|
+
* pinned trust anchor. Never writes the private key.
|
|
103
|
+
*/
|
|
104
|
+
function exportPublicKey(path) {
|
|
105
|
+
(0, node_fs_1.writeFileSync)(path, getPublicKeyPem(), { mode: 0o644 });
|
|
106
|
+
return path;
|
|
107
|
+
}
|
|
108
|
+
/** Test/teardown helper: forget the cached keypair. */
|
|
109
|
+
function _resetSigner() {
|
|
110
|
+
cached = null;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/signer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runVerify(argv: string[]): number;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runVerify = runVerify;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const receipt_verify_js_1 = require("./receipt-verify.js");
|
|
7
|
+
const signer_js_1 = require("./signer.js");
|
|
8
|
+
/**
|
|
9
|
+
* `mcp-guard verify` — the OFFLINE receipt gate a CI step runs.
|
|
10
|
+
*
|
|
11
|
+
* mcp-guard verify --receipt-file receipt.json --pubkey pinned.pub.pem \
|
|
12
|
+
* --scope deploy:production --scope-sha <sha>
|
|
13
|
+
*
|
|
14
|
+
* The public key must be supplied (`--pubkey`, repeatable): the gate trusts only keys it
|
|
15
|
+
* is given and never generates or discovers one. Exit 0 = authorized, 1 = refused,
|
|
16
|
+
* 2 = missing input. "No receipt = No deploy. This is intentional."
|
|
17
|
+
*/
|
|
18
|
+
const USAGE = 'usage: mcp-guard verify (--receipt-file <path> | --receipt-id <id> [--receipts-path <jsonl>]) --pubkey <pem> [--pubkey <pem> ...] --scope s [--scope-ref r] [--scope-sha h] [--allow-policy-approval]';
|
|
19
|
+
function ciError(message) {
|
|
20
|
+
console.log(`::error::${message.replace(/\n/g, '%0A')}`);
|
|
21
|
+
console.error(message);
|
|
22
|
+
}
|
|
23
|
+
function parseArgs(argv) {
|
|
24
|
+
const flags = {};
|
|
25
|
+
const pubkeys = [];
|
|
26
|
+
const bools = new Set();
|
|
27
|
+
for (let i = 0; i < argv.length; i++) {
|
|
28
|
+
const a = argv[i];
|
|
29
|
+
if (!a.startsWith('--'))
|
|
30
|
+
continue;
|
|
31
|
+
const key = a.slice(2);
|
|
32
|
+
if (key === 'allow-policy-approval' || key === 'help') {
|
|
33
|
+
bools.add(key);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const next = argv[i + 1];
|
|
37
|
+
if (next === undefined || next.startsWith('--')) {
|
|
38
|
+
flags[key] = '';
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (key === 'pubkey')
|
|
42
|
+
pubkeys.push(next);
|
|
43
|
+
else
|
|
44
|
+
flags[key] = next;
|
|
45
|
+
i++;
|
|
46
|
+
}
|
|
47
|
+
return { flags, pubkeys, bools };
|
|
48
|
+
}
|
|
49
|
+
function loadReceiptById(id, path) {
|
|
50
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
51
|
+
return `receipt store not found at ${path}`;
|
|
52
|
+
let found = null;
|
|
53
|
+
for (const line of (0, node_fs_1.readFileSync)(path, 'utf-8').split('\n')) {
|
|
54
|
+
if (!line.trim())
|
|
55
|
+
continue;
|
|
56
|
+
try {
|
|
57
|
+
const r = JSON.parse(line);
|
|
58
|
+
if (r?.envelope?.receipt_id === id)
|
|
59
|
+
found = r;
|
|
60
|
+
}
|
|
61
|
+
catch { /* skip malformed line */ }
|
|
62
|
+
}
|
|
63
|
+
return found ?? `no receipt with id "${id}" in ${path}`;
|
|
64
|
+
}
|
|
65
|
+
function runVerify(argv) {
|
|
66
|
+
const { flags, pubkeys, bools } = parseArgs(argv);
|
|
67
|
+
const fail = (msg) => { ciError(`No receipt = No deploy. This is intentional. (${msg})`); return 2; };
|
|
68
|
+
if (bools.has('help')) {
|
|
69
|
+
console.log(USAGE);
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
let receipt;
|
|
73
|
+
if (flags['receipt-file']) {
|
|
74
|
+
const p = (0, node_path_1.resolve)(flags['receipt-file']);
|
|
75
|
+
if (!(0, node_fs_1.existsSync)(p))
|
|
76
|
+
return fail(`receipt file not found: ${p}`);
|
|
77
|
+
try {
|
|
78
|
+
receipt = JSON.parse((0, node_fs_1.readFileSync)(p, 'utf-8'));
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return fail(`receipt file is not valid JSON: ${e.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (flags['receipt-id']) {
|
|
85
|
+
const r = loadReceiptById(flags['receipt-id'], (0, node_path_1.resolve)(flags['receipts-path'] || process.env.PP_SHARED_RECEIPTS_PATH || 'pp-receipts.jsonl'));
|
|
86
|
+
if (typeof r === 'string')
|
|
87
|
+
return fail(r);
|
|
88
|
+
receipt = r;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
return fail(USAGE);
|
|
92
|
+
}
|
|
93
|
+
if (pubkeys.length === 0)
|
|
94
|
+
return fail('--pubkey is required: the gate verifies only against keys you pin');
|
|
95
|
+
const keys = {};
|
|
96
|
+
for (const k of pubkeys) {
|
|
97
|
+
const p = (0, node_path_1.resolve)(k);
|
|
98
|
+
if (!(0, node_fs_1.existsSync)(p))
|
|
99
|
+
return fail(`public key not found: ${p}`);
|
|
100
|
+
try {
|
|
101
|
+
const pem = (0, node_fs_1.readFileSync)(p, 'utf-8');
|
|
102
|
+
keys[(0, signer_js_1.keyIdForPublicKey)(pem)] = pem;
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
return fail(`public key ${p} is not a valid PEM: ${e.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const f of ['scope', 'scope-ref', 'scope-sha']) {
|
|
109
|
+
if (f in flags && !flags[f])
|
|
110
|
+
return fail(`--${f} needs a value`);
|
|
111
|
+
}
|
|
112
|
+
if (!flags.scope)
|
|
113
|
+
return fail('--scope is required: a gate must say which action it authorizes');
|
|
114
|
+
const expected = { scope: flags.scope };
|
|
115
|
+
if (flags['scope-ref'])
|
|
116
|
+
expected.scope_ref = flags['scope-ref'];
|
|
117
|
+
if (flags['scope-sha'])
|
|
118
|
+
expected.scope_sha = flags['scope-sha'];
|
|
119
|
+
const hasExpected = true;
|
|
120
|
+
const result = (0, receipt_verify_js_1.verifyReceipt)(receipt, keys, {
|
|
121
|
+
...(hasExpected ? { expected } : {}),
|
|
122
|
+
allowPolicyApproval: bools.has('allow-policy-approval'),
|
|
123
|
+
});
|
|
124
|
+
const id = receipt?.envelope?.receipt_id ?? '(unknown)';
|
|
125
|
+
if (result.valid) {
|
|
126
|
+
const scope = hasExpected ? ` for ${[expected.scope, expected.scope_ref, expected.scope_sha && `@ ${expected.scope_sha}`].filter(Boolean).join(' ')}` : '';
|
|
127
|
+
console.log(`Receipt ${id} VERIFIED${scope}. Gate green.`);
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
ciError(`No receipt = No deploy. This is intentional. Receipt ${id} REFUSED:\n - ${result.reasons.join('\n - ')}`);
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=verify-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-cli.js","sourceRoot":"","sources":["../../src/verify-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type KeyObject } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* The cryptographic core every verifier in mcp-guard uses: Ed25519 detached-signature
|
|
4
|
+
* checks against PINNED keys, and single-use tracking. Receipt v2 verification and the
|
|
5
|
+
* enforcement module's authorization tokens both build on these two primitives.
|
|
6
|
+
*/
|
|
7
|
+
/** key_id → public key (PEM or KeyObject). Only keys in this map are trusted. */
|
|
8
|
+
export type PinnedKeys = Record<string, string | KeyObject>;
|
|
9
|
+
/**
|
|
10
|
+
* Verify `signatureHex` over `bytes` with the pinned key for `keyId`.
|
|
11
|
+
* Returns a reason string on failure, or null when the signature verifies.
|
|
12
|
+
*/
|
|
13
|
+
export declare function verifyDetached(bytes: string, signatureHex: unknown, keyId: unknown, keys: PinnedKeys): string | null;
|
|
14
|
+
/** Remembers used nonces so an authorization is honored at most once per process. */
|
|
15
|
+
export declare class ReplayCache {
|
|
16
|
+
private readonly max;
|
|
17
|
+
private readonly seen;
|
|
18
|
+
constructor(max?: number);
|
|
19
|
+
/** Record `key`; returns false when it was already used. */
|
|
20
|
+
use(key: string): boolean;
|
|
21
|
+
has(key: string): boolean;
|
|
22
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReplayCache = void 0;
|
|
4
|
+
exports.verifyDetached = verifyDetached;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
/**
|
|
7
|
+
* Verify `signatureHex` over `bytes` with the pinned key for `keyId`.
|
|
8
|
+
* Returns a reason string on failure, or null when the signature verifies.
|
|
9
|
+
*/
|
|
10
|
+
function verifyDetached(bytes, signatureHex, keyId, keys) {
|
|
11
|
+
if (typeof keyId !== 'string' || !Object.prototype.hasOwnProperty.call(keys, keyId)) {
|
|
12
|
+
return `untrusted key_id "${String(keyId)}"`;
|
|
13
|
+
}
|
|
14
|
+
if (typeof signatureHex !== 'string' || !/^[0-9a-f]+$/i.test(signatureHex) || signatureHex.length % 2 !== 0) {
|
|
15
|
+
return 'signature is not hex';
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const key = keys[keyId];
|
|
19
|
+
const ok = (0, node_crypto_1.verify)(null, Buffer.from(bytes), typeof key === 'string' ? (0, node_crypto_1.createPublicKey)(key) : key, Buffer.from(signatureHex, 'hex'));
|
|
20
|
+
return ok ? null : 'signature does not verify against the pinned key';
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
return `signature verification threw (${err instanceof Error ? err.message : String(err)})`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Remembers used nonces so an authorization is honored at most once per process. */
|
|
27
|
+
class ReplayCache {
|
|
28
|
+
max;
|
|
29
|
+
seen = new Set();
|
|
30
|
+
constructor(max = 10_000) {
|
|
31
|
+
this.max = max;
|
|
32
|
+
}
|
|
33
|
+
/** Record `key`; returns false when it was already used. */
|
|
34
|
+
use(key) {
|
|
35
|
+
if (this.seen.has(key))
|
|
36
|
+
return false;
|
|
37
|
+
this.seen.add(key);
|
|
38
|
+
if (this.seen.size > this.max) {
|
|
39
|
+
const oldest = this.seen.values().next().value;
|
|
40
|
+
if (oldest !== undefined)
|
|
41
|
+
this.seen.delete(oldest);
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
has(key) {
|
|
46
|
+
return this.seen.has(key);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.ReplayCache = ReplayCache;
|
|
50
|
+
//# sourceMappingURL=verify-core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-core.js","sourceRoot":"","sources":["../../src/verify-core.ts"],"names":[],"mappings":";;;AAeA,wCAcC;AA7BD,6CAAkF;AAWlF;;;GAGG;AACH,SAAgB,cAAc,CAAC,KAAa,EAAE,YAAqB,EAAE,KAAc,EAAE,IAAgB;IACnG,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QACpF,OAAO,qBAAqB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;IAC/C,CAAC;IACD,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5G,OAAO,sBAAsB,CAAC;IAChC,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,MAAM,EAAE,GAAG,IAAA,oBAAQ,EAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAA,6BAAe,EAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC;QACtI,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,kDAAkD,CAAC;IACxE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,iCAAiC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;IAC9F,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,MAAa,WAAW;IAEO;IADZ,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC1C,YAA6B,MAAM,MAAM;QAAZ,QAAG,GAAH,GAAG,CAAS;IAAG,CAAC;IAE7C,4DAA4D;IAC5D,GAAG,CAAC,GAAW;QACb,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAC/C,IAAI,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,GAAG,CAAC,GAAW;QACb,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;CACF;AAlBD,kCAkBC"}
|