@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.
@@ -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
+ });