mcp-proof 0.3.12 → 0.3.13

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 ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## 0.3.13
4
+
5
+ Scanner-only release. Runtime authorization functionality is not included.
6
+
7
+ The package contains MCP tool-poisoning, hidden Unicode, typosquat, transport,
8
+ runtime tools/call, Claude Skill, client-config, SARIF, Trust Card, payload-size,
9
+ and live stdio scanning capabilities.
package/bin/mcp-proof.js CHANGED
@@ -9,9 +9,9 @@
9
9
  // mcp-proof --self-test
10
10
  //
11
11
  // Exit codes:
12
- // 0 — verdict allow (and, with --vetokernel-endpoint, VetoKernel authorized it)
12
+ // 0 — verdict allow
13
13
  // 1 — verdict warn
14
- // 2 — verdict block, or VetoKernel denied the action
14
+ // 2 — verdict block
15
15
  // 64 — usage error
16
16
  // 65 — input parse error
17
17
  //
@@ -24,7 +24,6 @@ const os = require('os');
24
24
  const path = require('path');
25
25
  const { spawn } = require('child_process');
26
26
  const { evaluate, evaluateCall, scanSkillManifest, verifyPublisherAsync } = require('../engine.js');
27
- const { authorizeCall } = require('../vetokernel-client.js');
28
27
 
29
28
  const DEFAULT_MAX_INPUT_BYTES = 5 * 1024 * 1024;
30
29
  const DEFAULT_TIMEOUT_MS = 15000;
@@ -54,9 +53,6 @@ Options:
54
53
  --skill <path> scan a Claude SKILL.md (educational/warning context aware)
55
54
  --inspect-call [<path>] inspect a tools/call payload before execution (reads stdin if path omitted)
56
55
  --execute [<path>] deprecated alias for --inspect-call
57
- --vetokernel-endpoint <url> with --inspect-call, ask VetoKernel to authorize the
58
- proposed action before execution and verify the returned receipt
59
- (env: VETOKERNEL_ENDPOINT, VETOKERNEL_API_KEY, VETOKERNEL_TENANT_ID)
60
56
  --markdown emit a shareable Markdown Trust Card
61
57
  --trust-card alias for --markdown
62
58
  --sarif emit SARIF 2.1.0 for GitHub code scanning
@@ -156,23 +152,13 @@ async function main() {
156
152
  let result;
157
153
  let call;
158
154
  try {
159
- ({ result, call } = runExecute(args));
155
+ ({ result } = runExecute(args));
160
156
  } catch (err) {
161
157
  process.stderr.write(`mcp-proof: ${err.message}\n`);
162
158
  process.exit(err.exitCode || 65);
163
159
  }
164
- if (args.vetokernelEndpoint) {
165
- try {
166
- result.authorization = await authorizeInspectedCall(result, call, args);
167
- } catch (err) {
168
- // Fail closed: if the authorization service is unreachable, the action
169
- // is not authorized.
170
- process.stderr.write(`mcp-proof: vetokernel authorization failed: ${err.message}\n`);
171
- process.exit(2);
172
- }
173
- }
174
160
  emitOutput(result, args);
175
- process.exit(exitCodeForInspectedCall(result));
161
+ process.exit(exitCodeForDecision(result.decision));
176
162
  }
177
163
 
178
164
  let raw;
@@ -227,7 +213,6 @@ function parseArgs(argv) {
227
213
  source: '', transport: '', bind: '', json: false, selfTest: false,
228
214
  help: false, version: false, file: '', stdio: '', config: '', skill: '',
229
215
  inspectCall: false, deprecatedExecute: false, format: 'human', live: false, timeoutMs: DEFAULT_TIMEOUT_MS,
230
- vetokernelEndpoint: process.env.VETOKERNEL_ENDPOINT || '',
231
216
  };
232
217
  for (let i = 0; i < argv.length; i++) {
233
218
  const a = argv[i];
@@ -255,7 +240,6 @@ function parseArgs(argv) {
255
240
  break;
256
241
  case '--live': out.live = true; break;
257
242
  case '--timeout': out.timeoutMs = parseTimeout(argv[++i] || ''); break;
258
- case '--vetokernel-endpoint': out.vetokernelEndpoint = argv[++i] || ''; break;
259
243
  default:
260
244
  if (a.startsWith('-')) {
261
245
  process.stderr.write(`mcp-proof: unknown flag ${a}\n`);
@@ -671,40 +655,7 @@ function runExecute(args) {
671
655
  const result = evaluateCall(parsed);
672
656
  result.mode = 'inspect-call';
673
657
  result.name = args.file || 'stdin';
674
- return { result, call: parsed };
675
- }
676
-
677
- // authorizeInspectedCall asks VetoKernel to authorize the exact action this
678
- // call proposes. action_type and target_id come from the call file itself,
679
- // falling back to the tool name and arguments.target_id, so a call fixture is
680
- // self-describing and needs no extra flags.
681
- function authorizeInspectedCall(result, call, args) {
682
- const callArgs = call.arguments || (call.params && call.params.arguments) || {};
683
- const parametersJSON = JSON.stringify(callArgs);
684
- return authorizeCall({
685
- endpoint: args.vetokernelEndpoint,
686
- apiKey: process.env.VETOKERNEL_API_KEY || '',
687
- tenantId: process.env.VETOKERNEL_TENANT_ID || 'default',
688
- agentId: call.agent_id || 'mcp-proof',
689
- actionId: call.action_id || `mcp-proof-${Date.now()}`,
690
- toolName: result.tool,
691
- actionType: call.action_type || result.tool,
692
- targetId: call.target_id || callArgs.target_id || '',
693
- parametersJSON,
694
- context: call.context,
695
- scanner: {
696
- name: 'mcp-proof',
697
- decision: result.decision,
698
- severity: result.call_scan.severity,
699
- reason: result.call_scan.reason,
700
- },
701
- timeoutMs: args.timeoutMs,
702
- });
703
- }
704
-
705
- function exitCodeForInspectedCall(result) {
706
- if (result.authorization && result.authorization.decision !== 'allow') return 2;
707
- return exitCodeForDecision(result.decision);
658
+ return { result };
708
659
  }
709
660
 
710
661
  function exitCodeForDecision(d) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-proof",
3
- "version": "0.3.12",
3
+ "version": "0.3.13",
4
4
  "description": "Auditable, privacy-preserving MCP (Model Context Protocol) server verifier with local Trust Cards and SARIF output — test servers before connecting them to an AI system.",
5
5
  "main": "engine.js",
6
6
  "bin": {
@@ -8,9 +8,9 @@
8
8
  },
9
9
  "files": [
10
10
  "engine.js",
11
- "vetokernel-client.js",
12
11
  "bin/mcp-proof.js",
13
12
  "README.md",
13
+ "CHANGELOG.md",
14
14
  "LICENSE"
15
15
  ],
16
16
  "engines": {
@@ -1,168 +0,0 @@
1
- // vetokernel-client.js — post an inspected tools/call to VetoKernel's
2
- // execution-authorization endpoint and independently verify the receipt.
3
- //
4
- // Node stdlib only (http, https, crypto, url) — mcp-proof adds no dependencies.
5
-
6
- 'use strict';
7
-
8
- const crypto = require('crypto');
9
- const http = require('http');
10
- const https = require('https');
11
- const { URL } = require('url');
12
-
13
- const SCHEMA_VERSION = '1.0';
14
-
15
- // goJSONString matches Go's encoding/json string encoding, which HTML-escapes
16
- // <, > and & by default. Everything else Node's JSON.stringify already agrees on.
17
- function goJSONString(value) {
18
- return JSON.stringify(String(value))
19
- .replace(/</g, '\\u003c')
20
- .replace(/>/g, '\\u003e')
21
- .replace(/&/g, '\\u0026');
22
- }
23
-
24
- // canonicalReceiptJSON reproduces, in JavaScript, the exact byte sequence
25
- // VetoKernel hashes into receipt_hash (Go: backend/auth/execution_authorization.go
26
- // CanonicalReceiptBytes). It is deliberately an INDEPENDENT re-implementation:
27
- // agreement between the two is what makes the receipt independently verified
28
- // rather than merely re-read. Field order and the omitempty set are load-bearing
29
- // and are pinned by a golden vector in both test suites.
30
- function canonicalReceiptJSON(receipt) {
31
- const fields = [
32
- ['schema_version', receipt.schema_version, false],
33
- ['decision', receipt.decision, false],
34
- ['action_id', receipt.action_id, false],
35
- ['action_type', receipt.action_type, false],
36
- ['target_id', receipt.target_id, false],
37
- ['tool_name', receipt.tool_name, true],
38
- ['parameters_hash', receipt.parameters_hash, true],
39
- ['tenant_id', receipt.tenant_id, false],
40
- ['payload_hash', receipt.payload_hash, false],
41
- ['context_hash', receipt.context_hash, false],
42
- ['authorized_by_agent_id', receipt.authorized_by_agent_id, true],
43
- ['issued_at', receipt.issued_at, false],
44
- ];
45
- const parts = [];
46
- for (const [key, raw, omitempty] of fields) {
47
- const value = raw === undefined || raw === null ? '' : raw;
48
- if (omitempty && value === '') continue;
49
- parts.push(goJSONString(key) + ':' + goJSONString(value));
50
- }
51
- return '{' + parts.join(',') + '}';
52
- }
53
-
54
- // verifyReceipt re-derives both bindings the receipt claims: that payload_hash
55
- // is sha256 of the exact parameter bytes we sent, and that receipt_hash is
56
- // sha256 of the canonical receipt fields.
57
- function verifyReceipt(receipt, parametersJSON) {
58
- if (!receipt || typeof receipt !== 'object') {
59
- return { verified: false, problems: ['no receipt returned'], payload_hash: '', receipt_hash: '' };
60
- }
61
- const problems = [];
62
- if (receipt.schema_version !== SCHEMA_VERSION) {
63
- problems.push(`unexpected schema_version ${receipt.schema_version}`);
64
- }
65
- if (receipt.decision !== 'allow') {
66
- problems.push(`unexpected receipt decision ${receipt.decision}`);
67
- }
68
- const payloadHash = crypto.createHash('sha256')
69
- .update(Buffer.from(String(parametersJSON), 'utf8')).digest('hex');
70
- if (receipt.payload_hash !== payloadHash) {
71
- problems.push(`payload_hash does not bind the parameters we sent (recomputed ${payloadHash})`);
72
- }
73
- const receiptHash = crypto.createHash('sha256')
74
- .update(canonicalReceiptJSON(receipt), 'utf8').digest('hex');
75
- if (receipt.receipt_hash !== receiptHash) {
76
- problems.push(`receipt_hash mismatch (recomputed ${receiptHash})`);
77
- }
78
- return { verified: problems.length === 0, problems, payload_hash: payloadHash, receipt_hash: receiptHash };
79
- }
80
-
81
- // authorizeCall proposes a consequential action to VetoKernel and returns its
82
- // decision. It fails closed: an allow whose receipt does not independently
83
- // verify is downgraded to a deny.
84
- function authorizeCall(options) {
85
- const {
86
- endpoint,
87
- apiKey = '',
88
- tenantId = 'default',
89
- agentId = 'mcp-proof',
90
- actionId = '',
91
- toolName = '',
92
- actionType = '',
93
- targetId = '',
94
- parametersJSON = '{}',
95
- context,
96
- scanner = { name: 'mcp-proof', decision: 'allow', severity: 'low', reason: 'clean' },
97
- timeoutMs = 5000,
98
- } = options;
99
-
100
- const body = JSON.stringify({
101
- schema_version: SCHEMA_VERSION,
102
- action_id: actionId,
103
- agent_id: agentId,
104
- tool_name: toolName,
105
- action_type: actionType,
106
- target_id: targetId,
107
- parameters_json: parametersJSON,
108
- context: context || undefined,
109
- scanner,
110
- });
111
-
112
- const url = new URL(endpoint);
113
- const transport = url.protocol === 'https:' ? https : http;
114
-
115
- return new Promise((resolve, reject) => {
116
- const req = transport.request({
117
- protocol: url.protocol,
118
- hostname: url.hostname,
119
- port: url.port,
120
- path: url.pathname + url.search,
121
- method: 'POST',
122
- headers: {
123
- 'Content-Type': 'application/json',
124
- 'Content-Length': Buffer.byteLength(body),
125
- 'X-API-Key': apiKey,
126
- 'X-Tenant-ID': tenantId,
127
- },
128
- }, (res) => {
129
- const chunks = [];
130
- res.on('data', (c) => chunks.push(c));
131
- res.on('end', () => {
132
- const raw = Buffer.concat(chunks).toString('utf8');
133
- let parsed;
134
- try {
135
- parsed = JSON.parse(raw);
136
- } catch (_) {
137
- reject(new Error(`vetokernel returned non-JSON (status ${res.statusCode}): ${raw.slice(0, 200)}`));
138
- return;
139
- }
140
- const out = {
141
- endpoint,
142
- status: res.statusCode,
143
- decision: parsed.decision === 'allow' ? 'allow' : 'deny',
144
- reason: parsed.reason || '',
145
- receipt: parsed.receipt || null,
146
- verification: null,
147
- };
148
- out.verification = out.decision === 'allow'
149
- ? verifyReceipt(parsed.receipt, parametersJSON)
150
- : { verified: false, problems: ['denied — no receipt issued'], payload_hash: '', receipt_hash: '' };
151
- if (out.decision === 'allow' && !out.verification.verified) {
152
- out.decision = 'deny';
153
- out.reason = `receipt failed independent verification: ${out.verification.problems.join('; ')}`;
154
- }
155
- resolve(out);
156
- });
157
- });
158
-
159
- req.setTimeout(timeoutMs, () => {
160
- req.destroy(new Error(`vetokernel request timed out after ${timeoutMs}ms`));
161
- });
162
- req.on('error', reject);
163
- req.write(body);
164
- req.end();
165
- });
166
- }
167
-
168
- module.exports = { authorizeCall, verifyReceipt, canonicalReceiptJSON, SCHEMA_VERSION };