@enactprotocol/mcp-server 2.2.1 → 2.2.4
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.js +327 -304
- package/package.json +4 -4
- package/src/index.ts +150 -15
- package/tests/secrets.test.ts +465 -0
- package/tests/trust-policy.test.ts +280 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for MCP server trust policy enforcement
|
|
3
|
+
*
|
|
4
|
+
* These tests verify that the enact_run handler properly enforces
|
|
5
|
+
* trust policies based on attestation verification.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
// Type for trust policy
|
|
11
|
+
type TrustPolicy = "require_attestation" | "prompt" | "allow";
|
|
12
|
+
|
|
13
|
+
// Mock the shared config functions
|
|
14
|
+
const mockGetTrustPolicy = mock((): TrustPolicy => "require_attestation");
|
|
15
|
+
const mockGetMinimumAttestations = mock(() => 1);
|
|
16
|
+
const mockIsIdentityTrusted = mock((_identity: string) => false);
|
|
17
|
+
|
|
18
|
+
// Mock the API functions
|
|
19
|
+
const mockVerifyAllAttestations = mock(async () => []);
|
|
20
|
+
|
|
21
|
+
describe("MCP Server Trust Policy Enforcement", () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
// Reset mocks before each test
|
|
24
|
+
mockGetTrustPolicy.mockReset();
|
|
25
|
+
mockGetMinimumAttestations.mockReset();
|
|
26
|
+
mockIsIdentityTrusted.mockReset();
|
|
27
|
+
mockVerifyAllAttestations.mockReset();
|
|
28
|
+
|
|
29
|
+
// Set default mock implementations
|
|
30
|
+
mockGetTrustPolicy.mockImplementation((): TrustPolicy => "require_attestation");
|
|
31
|
+
mockGetMinimumAttestations.mockImplementation(() => 1);
|
|
32
|
+
mockIsIdentityTrusted.mockImplementation(() => false);
|
|
33
|
+
mockVerifyAllAttestations.mockImplementation(async () => []);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("Trust Policy Logic", () => {
|
|
37
|
+
test("should block execution when policy is 'require_attestation' and no attestations", () => {
|
|
38
|
+
const trustPolicy = mockGetTrustPolicy();
|
|
39
|
+
const minimumAttestations = mockGetMinimumAttestations();
|
|
40
|
+
const verifiedCount = 0;
|
|
41
|
+
|
|
42
|
+
// Simulate the trust check logic from enact_run
|
|
43
|
+
const shouldBlock =
|
|
44
|
+
verifiedCount < minimumAttestations && trustPolicy === "require_attestation";
|
|
45
|
+
|
|
46
|
+
expect(shouldBlock).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("should block execution when policy is 'prompt' and no attestations", () => {
|
|
50
|
+
mockGetTrustPolicy.mockImplementation((): TrustPolicy => "prompt");
|
|
51
|
+
|
|
52
|
+
const trustPolicy = mockGetTrustPolicy();
|
|
53
|
+
const minimumAttestations = mockGetMinimumAttestations();
|
|
54
|
+
const verifiedCount = 0;
|
|
55
|
+
|
|
56
|
+
// Simulate the trust check logic from enact_run
|
|
57
|
+
const shouldBlock = verifiedCount < minimumAttestations && trustPolicy === "prompt";
|
|
58
|
+
|
|
59
|
+
expect(shouldBlock).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("should allow execution when policy is 'allow' regardless of attestations", () => {
|
|
63
|
+
mockGetTrustPolicy.mockImplementation((): TrustPolicy => "allow");
|
|
64
|
+
|
|
65
|
+
const trustPolicy = mockGetTrustPolicy();
|
|
66
|
+
const minimumAttestations = mockGetMinimumAttestations();
|
|
67
|
+
const verifiedCount = 0;
|
|
68
|
+
|
|
69
|
+
// Simulate the trust check logic from enact_run
|
|
70
|
+
const shouldBlock =
|
|
71
|
+
verifiedCount < minimumAttestations &&
|
|
72
|
+
(trustPolicy === "require_attestation" || trustPolicy === "prompt");
|
|
73
|
+
|
|
74
|
+
expect(shouldBlock).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("should allow execution when attestations meet minimum requirement", () => {
|
|
78
|
+
mockIsIdentityTrusted.mockImplementation(() => true);
|
|
79
|
+
|
|
80
|
+
const trustPolicy = mockGetTrustPolicy();
|
|
81
|
+
const minimumAttestations = mockGetMinimumAttestations();
|
|
82
|
+
const verifiedCount = 1; // Meets minimum
|
|
83
|
+
|
|
84
|
+
const shouldBlock =
|
|
85
|
+
verifiedCount < minimumAttestations &&
|
|
86
|
+
(trustPolicy === "require_attestation" || trustPolicy === "prompt");
|
|
87
|
+
|
|
88
|
+
expect(shouldBlock).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("should require higher attestation count when minimum_attestations is increased", () => {
|
|
92
|
+
mockGetMinimumAttestations.mockImplementation(() => 3);
|
|
93
|
+
|
|
94
|
+
const trustPolicy = mockGetTrustPolicy();
|
|
95
|
+
const minimumAttestations = mockGetMinimumAttestations();
|
|
96
|
+
const verifiedCount = 2; // Below new minimum of 3
|
|
97
|
+
|
|
98
|
+
const shouldBlock =
|
|
99
|
+
verifiedCount < minimumAttestations && trustPolicy === "require_attestation";
|
|
100
|
+
|
|
101
|
+
expect(shouldBlock).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("Identity Trust Filtering", () => {
|
|
106
|
+
test("should only count attestations from trusted identities", () => {
|
|
107
|
+
// Mock attestations with various identities
|
|
108
|
+
const attestations = [
|
|
109
|
+
{ providerIdentity: "github:trusted-user" },
|
|
110
|
+
{ providerIdentity: "github:untrusted-user" },
|
|
111
|
+
{ providerIdentity: "google:trusted@company.com" },
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
// Only trust specific identities
|
|
115
|
+
mockIsIdentityTrusted.mockImplementation((identity: string) => {
|
|
116
|
+
return identity === "github:trusted-user" || identity === "google:trusted@company.com";
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const verifiedCount = attestations.filter((v) =>
|
|
120
|
+
mockIsIdentityTrusted(v.providerIdentity)
|
|
121
|
+
).length;
|
|
122
|
+
|
|
123
|
+
expect(verifiedCount).toBe(2);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("should return zero when no attestations are from trusted identities", () => {
|
|
127
|
+
const attestations = [
|
|
128
|
+
{ providerIdentity: "github:unknown-user" },
|
|
129
|
+
{ providerIdentity: "github:another-unknown" },
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
mockIsIdentityTrusted.mockImplementation(() => false);
|
|
133
|
+
|
|
134
|
+
const verifiedCount = attestations.filter((v) =>
|
|
135
|
+
mockIsIdentityTrusted(v.providerIdentity)
|
|
136
|
+
).length;
|
|
137
|
+
|
|
138
|
+
expect(verifiedCount).toBe(0);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("Error Message Generation", () => {
|
|
143
|
+
test("should generate correct error message for require_attestation policy", () => {
|
|
144
|
+
const trustPolicy = "require_attestation";
|
|
145
|
+
const minimumAttestations = 1;
|
|
146
|
+
const verifiedCount = 0;
|
|
147
|
+
|
|
148
|
+
const errorMessage = `Trust policy violation: Tool requires ${minimumAttestations} attestation(s) from trusted auditors, but only ${verifiedCount} found.\n\nConfigured trust policy: ${trustPolicy}\nTo run unverified tools, update your ~/.enact/config.yaml trust policy to 'allow' or 'prompt'.`;
|
|
149
|
+
|
|
150
|
+
expect(errorMessage).toContain("Trust policy violation");
|
|
151
|
+
expect(errorMessage).toContain("require_attestation");
|
|
152
|
+
expect(errorMessage).toContain("~/.enact/config.yaml");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("should generate correct error message for prompt policy", () => {
|
|
156
|
+
const trustPolicy = "prompt";
|
|
157
|
+
const minimumAttestations = 1;
|
|
158
|
+
const verifiedCount = 0;
|
|
159
|
+
|
|
160
|
+
const errorMessage = `Trust policy violation: Tool requires ${minimumAttestations} attestation(s) from trusted auditors, but only ${verifiedCount} found.\n\nConfigured trust policy: ${trustPolicy}\nMCP server cannot prompt interactively. To run unverified tools via MCP, update your ~/.enact/config.yaml trust policy to 'allow'.`;
|
|
161
|
+
|
|
162
|
+
expect(errorMessage).toContain("Trust policy violation");
|
|
163
|
+
expect(errorMessage).toContain("prompt");
|
|
164
|
+
expect(errorMessage).toContain("cannot prompt interactively");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("Edge Cases", () => {
|
|
169
|
+
test("should handle minimum_attestations of 0 (always allow)", () => {
|
|
170
|
+
mockGetMinimumAttestations.mockImplementation(() => 0);
|
|
171
|
+
|
|
172
|
+
const trustPolicy = mockGetTrustPolicy();
|
|
173
|
+
const minimumAttestations = mockGetMinimumAttestations();
|
|
174
|
+
const verifiedCount = 0;
|
|
175
|
+
|
|
176
|
+
// 0 < 0 is false, so should not block
|
|
177
|
+
const shouldBlock =
|
|
178
|
+
verifiedCount < minimumAttestations && trustPolicy === "require_attestation";
|
|
179
|
+
|
|
180
|
+
expect(shouldBlock).toBe(false);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("should handle empty attestation list", async () => {
|
|
184
|
+
mockVerifyAllAttestations.mockImplementation(async () => []);
|
|
185
|
+
|
|
186
|
+
const attestations = await mockVerifyAllAttestations();
|
|
187
|
+
const verifiedCount = attestations.filter((v: { providerIdentity: string }) =>
|
|
188
|
+
mockIsIdentityTrusted(v.providerIdentity)
|
|
189
|
+
).length;
|
|
190
|
+
|
|
191
|
+
expect(verifiedCount).toBe(0);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("should handle attestation verification errors gracefully", async () => {
|
|
195
|
+
mockVerifyAllAttestations.mockImplementation(async () => {
|
|
196
|
+
throw new Error("Network error");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
let verifiedCount = 0;
|
|
200
|
+
try {
|
|
201
|
+
const attestations = await mockVerifyAllAttestations();
|
|
202
|
+
verifiedCount = attestations.length;
|
|
203
|
+
} catch {
|
|
204
|
+
// Error is caught, verifiedCount stays 0
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
expect(verifiedCount).toBe(0);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("Trust Policy Integration", () => {
|
|
213
|
+
test("complete flow: unverified tool with require_attestation policy should be blocked", () => {
|
|
214
|
+
// Setup: require_attestation policy, minimum 1 attestation, no trusted attestations
|
|
215
|
+
mockGetTrustPolicy.mockImplementation(() => "require_attestation");
|
|
216
|
+
mockGetMinimumAttestations.mockImplementation(() => 1);
|
|
217
|
+
mockIsIdentityTrusted.mockImplementation(() => false);
|
|
218
|
+
|
|
219
|
+
const attestations = [{ providerIdentity: "github:unknown" }];
|
|
220
|
+
|
|
221
|
+
const trustPolicy = mockGetTrustPolicy();
|
|
222
|
+
const minimumAttestations = mockGetMinimumAttestations();
|
|
223
|
+
const verifiedCount = attestations.filter((v) =>
|
|
224
|
+
mockIsIdentityTrusted(v.providerIdentity)
|
|
225
|
+
).length;
|
|
226
|
+
|
|
227
|
+
expect(verifiedCount).toBe(0);
|
|
228
|
+
expect(verifiedCount < minimumAttestations).toBe(true);
|
|
229
|
+
expect(trustPolicy).toBe("require_attestation");
|
|
230
|
+
|
|
231
|
+
// Should block
|
|
232
|
+
const shouldBlock =
|
|
233
|
+
verifiedCount < minimumAttestations &&
|
|
234
|
+
(trustPolicy === "require_attestation" || trustPolicy === "prompt");
|
|
235
|
+
expect(shouldBlock).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("complete flow: verified tool with trusted attestation should be allowed", () => {
|
|
239
|
+
// Setup: require_attestation policy, minimum 1 attestation, one trusted attestation
|
|
240
|
+
mockGetTrustPolicy.mockImplementation(() => "require_attestation");
|
|
241
|
+
mockGetMinimumAttestations.mockImplementation(() => 1);
|
|
242
|
+
mockIsIdentityTrusted.mockImplementation(
|
|
243
|
+
(identity: string) => identity === "github:EnactProtocol"
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const attestations = [{ providerIdentity: "github:EnactProtocol" }];
|
|
247
|
+
|
|
248
|
+
const trustPolicy = mockGetTrustPolicy();
|
|
249
|
+
const minimumAttestations = mockGetMinimumAttestations();
|
|
250
|
+
const verifiedCount = attestations.filter((v) =>
|
|
251
|
+
mockIsIdentityTrusted(v.providerIdentity)
|
|
252
|
+
).length;
|
|
253
|
+
|
|
254
|
+
expect(verifiedCount).toBe(1);
|
|
255
|
+
expect(verifiedCount >= minimumAttestations).toBe(true);
|
|
256
|
+
|
|
257
|
+
// Should not block
|
|
258
|
+
const shouldBlock =
|
|
259
|
+
verifiedCount < minimumAttestations &&
|
|
260
|
+
(trustPolicy === "require_attestation" || trustPolicy === "prompt");
|
|
261
|
+
expect(shouldBlock).toBe(false);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("complete flow: allow policy bypasses attestation check", () => {
|
|
265
|
+
// Setup: allow policy, no attestations
|
|
266
|
+
mockGetTrustPolicy.mockImplementation((): TrustPolicy => "allow");
|
|
267
|
+
mockGetMinimumAttestations.mockImplementation(() => 1);
|
|
268
|
+
mockIsIdentityTrusted.mockImplementation(() => false);
|
|
269
|
+
|
|
270
|
+
const trustPolicy = mockGetTrustPolicy();
|
|
271
|
+
const minimumAttestations = mockGetMinimumAttestations();
|
|
272
|
+
const verifiedCount = 0;
|
|
273
|
+
|
|
274
|
+
// Should not block because policy is 'allow'
|
|
275
|
+
const shouldBlock =
|
|
276
|
+
verifiedCount < minimumAttestations &&
|
|
277
|
+
(trustPolicy === "require_attestation" || trustPolicy === "prompt");
|
|
278
|
+
expect(shouldBlock).toBe(false);
|
|
279
|
+
});
|
|
280
|
+
});
|