@cyanheads/mcp-ts-core 0.11.0 → 0.11.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/AGENTS.md +9 -5
- package/CLAUDE.md +9 -5
- package/README.md +4 -2
- package/biome.json +2 -2
- package/changelog/0.11.x/0.11.0.md +0 -2
- package/changelog/0.11.x/0.11.1.md +74 -0
- package/changelog/0.11.x/0.11.2.md +98 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +27 -13
- package/dist/config/index.js.map +1 -1
- package/dist/core/app.d.ts +37 -14
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +56 -23
- package/dist/core/app.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/worker.d.ts.map +1 -1
- package/dist/core/worker.js +35 -18
- package/dist/core/worker.js.map +1 -1
- package/dist/mcp-server/elicitation.d.ts +64 -0
- package/dist/mcp-server/elicitation.d.ts.map +1 -0
- package/dist/mcp-server/elicitation.js +81 -0
- package/dist/mcp-server/elicitation.js.map +1 -0
- package/dist/mcp-server/protocolSession.d.ts +34 -0
- package/dist/mcp-server/protocolSession.d.ts.map +1 -0
- package/dist/mcp-server/protocolSession.js +13 -0
- package/dist/mcp-server/protocolSession.js.map +1 -0
- package/dist/mcp-server/resources/resource-registration.d.ts +2 -1
- package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
- package/dist/mcp-server/resources/resource-registration.js +6 -2
- package/dist/mcp-server/resources/resource-registration.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +7 -12
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +24 -31
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/server.d.ts +3 -0
- package/dist/mcp-server/server.d.ts.map +1 -1
- package/dist/mcp-server/server.js +12 -2
- package/dist/mcp-server/server.js.map +1 -1
- package/dist/mcp-server/tools/tool-registration.d.ts +2 -1
- package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-registration.js +6 -2
- package/dist/mcp-server/tools/tool-registration.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +7 -13
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +11 -44
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.js +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.js.map +1 -1
- package/dist/mcp-server/transports/http/httpServer.d.ts +2 -2
- package/dist/mcp-server/transports/http/httpServer.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpServer.js.map +1 -1
- package/dist/mcp-server/transports/http/httpTransport.d.ts +2 -2
- package/dist/mcp-server/transports/http/httpTransport.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpTransport.js +113 -55
- package/dist/mcp-server/transports/http/httpTransport.js.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.d.ts +17 -0
- package/dist/mcp-server/transports/http/sessionStore.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.js +71 -0
- package/dist/mcp-server/transports/http/sessionStore.js.map +1 -1
- package/dist/mcp-server/transports/manager.d.ts +2 -6
- package/dist/mcp-server/transports/manager.d.ts.map +1 -1
- package/dist/mcp-server/transports/manager.js +4 -0
- package/dist/mcp-server/transports/manager.js.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.d.ts +2 -2
- package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.js +14 -3
- package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
- package/dist/services/canvas/core/DataCanvas.d.ts +1 -1
- package/dist/services/canvas/core/DataCanvas.d.ts.map +1 -1
- package/dist/services/canvas/core/DataCanvas.js +2 -2
- package/dist/services/canvas/core/DataCanvas.js.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +11 -3
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
- package/dist/services/canvas/providers/duckdb/exportWriter.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/exportWriter.js +23 -2
- package/dist/services/canvas/providers/duckdb/exportWriter.js.map +1 -1
- package/dist/services/mirror/core/defineMirror.d.ts.map +1 -1
- package/dist/services/mirror/core/defineMirror.js +18 -7
- package/dist/services/mirror/core/defineMirror.js.map +1 -1
- package/dist/services/mirror/core/runner.d.ts.map +1 -1
- package/dist/services/mirror/core/runner.js +3 -0
- package/dist/services/mirror/core/runner.js.map +1 -1
- package/dist/services/mirror/index.d.ts +1 -1
- package/dist/services/mirror/index.d.ts.map +1 -1
- package/dist/services/mirror/index.js.map +1 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.d.ts +20 -0
- package/dist/services/mirror/sqlite/sqliteMirrorStore.d.ts.map +1 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js +87 -8
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js.map +1 -1
- package/dist/storage/core/StorageService.d.ts.map +1 -1
- package/dist/storage/core/StorageService.js +4 -1
- package/dist/storage/core/StorageService.js.map +1 -1
- package/dist/storage/core/storageValidation.d.ts +8 -0
- package/dist/storage/core/storageValidation.d.ts.map +1 -1
- package/dist/storage/core/storageValidation.js +38 -0
- package/dist/storage/core/storageValidation.js.map +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.d.ts +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.d.ts.map +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.js +67 -25
- package/dist/storage/providers/inMemory/inMemoryProvider.js.map +1 -1
- package/dist/testing/fuzz.d.ts +5 -1
- package/dist/testing/fuzz.d.ts.map +1 -1
- package/dist/testing/fuzz.js +147 -37
- package/dist/testing/fuzz.js.map +1 -1
- package/dist/testing/index.d.ts +111 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +139 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/vitest.d.ts +86 -12
- package/dist/testing/vitest.d.ts.map +1 -1
- package/dist/testing/vitest.js +91 -11
- package/dist/testing/vitest.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/network/fetchWithTimeout.d.ts +7 -6
- package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
- package/dist/utils/network/fetchWithTimeout.js +141 -51
- package/dist/utils/network/fetchWithTimeout.js.map +1 -1
- package/dist/utils/network/httpError.d.ts.map +1 -1
- package/dist/utils/network/httpError.js +3 -2
- package/dist/utils/network/httpError.js.map +1 -1
- package/dist/utils/network/responseBody.d.ts +15 -0
- package/dist/utils/network/responseBody.d.ts.map +1 -0
- package/dist/utils/network/responseBody.js +63 -0
- package/dist/utils/network/responseBody.js.map +1 -0
- package/dist/utils/parsing/frontmatterParser.d.ts +3 -1
- package/dist/utils/parsing/frontmatterParser.d.ts.map +1 -1
- package/dist/utils/parsing/frontmatterParser.js +6 -7
- package/dist/utils/parsing/frontmatterParser.js.map +1 -1
- package/dist/utils/parsing/htmlExtractor.d.ts +4 -0
- package/dist/utils/parsing/htmlExtractor.d.ts.map +1 -1
- package/dist/utils/parsing/htmlExtractor.js +4 -5
- package/dist/utils/parsing/htmlExtractor.js.map +1 -1
- package/dist/utils/parsing/index.d.ts +1 -0
- package/dist/utils/parsing/index.d.ts.map +1 -1
- package/dist/utils/parsing/index.js +1 -0
- package/dist/utils/parsing/index.js.map +1 -1
- package/dist/utils/parsing/inputBudget.d.ts +28 -0
- package/dist/utils/parsing/inputBudget.d.ts.map +1 -0
- package/dist/utils/parsing/inputBudget.js +76 -0
- package/dist/utils/parsing/inputBudget.js.map +1 -0
- package/dist/utils/parsing/jsonParser.d.ts +3 -1
- package/dist/utils/parsing/jsonParser.d.ts.map +1 -1
- package/dist/utils/parsing/jsonParser.js +5 -6
- package/dist/utils/parsing/jsonParser.js.map +1 -1
- package/dist/utils/parsing/pdfParser.d.ts +11 -3
- package/dist/utils/parsing/pdfParser.d.ts.map +1 -1
- package/dist/utils/parsing/pdfParser.js +65 -50
- package/dist/utils/parsing/pdfParser.js.map +1 -1
- package/dist/utils/parsing/xmlParser.d.ts +3 -1
- package/dist/utils/parsing/xmlParser.d.ts.map +1 -1
- package/dist/utils/parsing/xmlParser.js +8 -6
- package/dist/utils/parsing/xmlParser.js.map +1 -1
- package/dist/utils/parsing/yamlParser.d.ts +3 -1
- package/dist/utils/parsing/yamlParser.d.ts.map +1 -1
- package/dist/utils/parsing/yamlParser.js +5 -6
- package/dist/utils/parsing/yamlParser.js.map +1 -1
- package/package.json +28 -26
- package/skills/api-testing/SKILL.md +65 -4
- package/skills/git-wrapup/SKILL.md +22 -1
- package/skills/security-pass/SKILL.md +2 -2
- package/templates/devcheck.config.json +1 -1
- package/templates/package.json +2 -0
- package/templates/tests/fuzz/echo-tool.fuzz.test.ts +20 -0
- package/templates/tests/integration/echo-contract.int.test.ts +25 -0
- package/templates/tests/smoke/definitions.smoke.test.ts +46 -0
- package/templates/vitest.config.ts +24 -27
- package/vitest.config.base.mjs +6 -0
- package/dist/logs/combined.log +0 -8
- package/dist/logs/error.log +0 -4
- package/dist/logs/interactions.log +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { decodeCursor, encodeCursor } from '../../../storage/core/storageValidation.js';
|
|
2
|
-
import { JsonRpcErrorCode, McpError } from '../../../types-global/errors.js';
|
|
2
|
+
import { configurationError, JsonRpcErrorCode, McpError } from '../../../types-global/errors.js';
|
|
3
3
|
import { logger } from '../../../utils/internal/logger.js';
|
|
4
4
|
const DEFAULT_LIST_LIMIT = 1000;
|
|
5
5
|
const DEFAULT_MAX_ENTRIES = 10_000;
|
|
@@ -8,19 +8,23 @@ export class InMemoryProvider {
|
|
|
8
8
|
maxEntries;
|
|
9
9
|
entryCount = 0;
|
|
10
10
|
constructor(options) {
|
|
11
|
-
|
|
11
|
+
const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
12
|
+
if (!Number.isSafeInteger(maxEntries) || maxEntries < 0) {
|
|
13
|
+
throw configurationError('InMemoryProvider maxEntries must be a non-negative safe integer.');
|
|
14
|
+
}
|
|
15
|
+
this.maxEntries = maxEntries;
|
|
12
16
|
}
|
|
13
17
|
/** Returns the total number of entries across all tenants. */
|
|
14
18
|
get size() {
|
|
15
19
|
return this.entryCount;
|
|
16
20
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return
|
|
21
|
+
getOrCreateTenantStore(tenantId) {
|
|
22
|
+
const existing = this.store.get(tenantId);
|
|
23
|
+
if (existing)
|
|
24
|
+
return existing;
|
|
25
|
+
const created = new Map();
|
|
26
|
+
this.store.set(tenantId, created);
|
|
27
|
+
return created;
|
|
24
28
|
}
|
|
25
29
|
/** Sweeps all tenant stores and removes expired entries, returning the count reclaimed. */
|
|
26
30
|
sweepExpired() {
|
|
@@ -44,21 +48,24 @@ export class InMemoryProvider {
|
|
|
44
48
|
* Ensures capacity for a new entry. If at limit, runs a TTL sweep first.
|
|
45
49
|
* If still at capacity after sweep, throws.
|
|
46
50
|
*/
|
|
47
|
-
ensureCapacity() {
|
|
48
|
-
if (this.entryCount
|
|
51
|
+
ensureCapacity(additionalEntries = 1) {
|
|
52
|
+
if (this.entryCount + additionalEntries <= this.maxEntries)
|
|
49
53
|
return;
|
|
50
54
|
const reclaimed = this.sweepExpired();
|
|
51
55
|
if (reclaimed > 0) {
|
|
52
56
|
logger.debug(`[InMemoryProvider] TTL sweep reclaimed ${reclaimed} expired entries`);
|
|
53
57
|
}
|
|
54
|
-
if (this.entryCount
|
|
58
|
+
if (this.entryCount + additionalEntries > this.maxEntries) {
|
|
55
59
|
throw new McpError(JsonRpcErrorCode.InternalError, `In-memory storage capacity exceeded (max: ${this.maxEntries}). ` +
|
|
56
60
|
'Consider increasing maxEntries, adding TTLs to entries, or switching to a persistent provider.');
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
get(tenantId, key, context) {
|
|
60
64
|
logger.debug(`[InMemoryProvider] Getting key: ${key} for tenant: ${tenantId}`, context);
|
|
61
|
-
const tenantStore = this.
|
|
65
|
+
const tenantStore = this.store.get(tenantId);
|
|
66
|
+
if (!tenantStore) {
|
|
67
|
+
return Promise.resolve(null);
|
|
68
|
+
}
|
|
62
69
|
const entry = tenantStore.get(key);
|
|
63
70
|
if (!entry) {
|
|
64
71
|
return Promise.resolve(null);
|
|
@@ -66,6 +73,8 @@ export class InMemoryProvider {
|
|
|
66
73
|
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
67
74
|
tenantStore.delete(key);
|
|
68
75
|
this.entryCount--;
|
|
76
|
+
if (tenantStore.size === 0)
|
|
77
|
+
this.store.delete(tenantId);
|
|
69
78
|
logger.debug(`[InMemoryProvider] Key expired and removed: ${key} for tenant: ${tenantId}`, context);
|
|
70
79
|
return Promise.resolve(null);
|
|
71
80
|
}
|
|
@@ -73,11 +82,12 @@ export class InMemoryProvider {
|
|
|
73
82
|
}
|
|
74
83
|
set(tenantId, key, value, context, options) {
|
|
75
84
|
logger.debug(`[InMemoryProvider] Setting key: ${key} for tenant: ${tenantId}`, context);
|
|
76
|
-
|
|
77
|
-
const isNew = !tenantStore
|
|
85
|
+
let tenantStore = this.store.get(tenantId);
|
|
86
|
+
const isNew = !tenantStore?.has(key);
|
|
78
87
|
if (isNew) {
|
|
79
88
|
this.ensureCapacity();
|
|
80
89
|
}
|
|
90
|
+
tenantStore ??= this.getOrCreateTenantStore(tenantId);
|
|
81
91
|
// Fix: Check for undefined instead of truthy to handle ttl=0 correctly
|
|
82
92
|
const expiresAt = options?.ttl !== undefined ? Date.now() + options.ttl * 1000 : undefined;
|
|
83
93
|
tenantStore.set(key, {
|
|
@@ -91,10 +101,14 @@ export class InMemoryProvider {
|
|
|
91
101
|
}
|
|
92
102
|
delete(tenantId, key, context) {
|
|
93
103
|
logger.debug(`[InMemoryProvider] Deleting key: ${key} for tenant: ${tenantId}`, context);
|
|
94
|
-
const tenantStore = this.
|
|
104
|
+
const tenantStore = this.store.get(tenantId);
|
|
105
|
+
if (!tenantStore)
|
|
106
|
+
return Promise.resolve(false);
|
|
95
107
|
const deleted = tenantStore.delete(key);
|
|
96
108
|
if (deleted) {
|
|
97
109
|
this.entryCount--;
|
|
110
|
+
if (tenantStore.size === 0)
|
|
111
|
+
this.store.delete(tenantId);
|
|
98
112
|
}
|
|
99
113
|
return Promise.resolve(deleted);
|
|
100
114
|
}
|
|
@@ -103,7 +117,14 @@ export class InMemoryProvider {
|
|
|
103
117
|
...context,
|
|
104
118
|
options,
|
|
105
119
|
});
|
|
106
|
-
|
|
120
|
+
// Authenticate tenant-bound cursors before an empty-namespace fast path.
|
|
121
|
+
// Otherwise a cursor issued to another tenant is silently accepted whenever
|
|
122
|
+
// the requested tenant has no entries.
|
|
123
|
+
const lastKey = options?.cursor ? decodeCursor(options.cursor, tenantId, context) : undefined;
|
|
124
|
+
const tenantStore = this.store.get(tenantId);
|
|
125
|
+
if (!tenantStore) {
|
|
126
|
+
return Promise.resolve({ keys: [], nextCursor: undefined });
|
|
127
|
+
}
|
|
107
128
|
const now = Date.now();
|
|
108
129
|
const allKeys = [];
|
|
109
130
|
// Collect all matching non-expired keys
|
|
@@ -118,14 +139,14 @@ export class InMemoryProvider {
|
|
|
118
139
|
}
|
|
119
140
|
}
|
|
120
141
|
}
|
|
142
|
+
if (tenantStore.size === 0)
|
|
143
|
+
this.store.delete(tenantId);
|
|
121
144
|
// Sort for consistent pagination
|
|
122
145
|
allKeys.sort();
|
|
123
146
|
// Apply pagination with opaque cursors
|
|
124
147
|
const limit = options?.limit ?? DEFAULT_LIST_LIMIT;
|
|
125
148
|
let startIndex = 0;
|
|
126
|
-
if (
|
|
127
|
-
// Decode and validate cursor
|
|
128
|
-
const lastKey = decodeCursor(options.cursor, tenantId, context);
|
|
149
|
+
if (lastKey) {
|
|
129
150
|
const cursorIndex = allKeys.indexOf(lastKey);
|
|
130
151
|
if (cursorIndex !== -1) {
|
|
131
152
|
startIndex = cursorIndex + 1;
|
|
@@ -163,14 +184,33 @@ export class InMemoryProvider {
|
|
|
163
184
|
logger.debug(`[InMemoryProvider] Retrieved ${results.size}/${keys.length} keys for tenant: ${tenantId}`, context);
|
|
164
185
|
return results;
|
|
165
186
|
}
|
|
187
|
+
// biome-ignore lint/suspicious/useAwait: async is required by IStorageProvider; the in-memory batch write is synchronous.
|
|
166
188
|
async setMany(tenantId, entries, context, options) {
|
|
167
189
|
if (entries.size === 0) {
|
|
168
190
|
return;
|
|
169
191
|
}
|
|
170
192
|
logger.debug(`[InMemoryProvider] Setting ${entries.size} keys for tenant: ${tenantId}`, context);
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
193
|
+
// Expired entries must be removed before computing the batch delta. If an
|
|
194
|
+
// expired key is present in this batch, counting it as an overwrite and
|
|
195
|
+
// sweeping it later can undercount additions and exceed maxEntries.
|
|
196
|
+
this.sweepExpired();
|
|
197
|
+
let tenantStore = this.store.get(tenantId);
|
|
198
|
+
let newEntryCount = 0;
|
|
199
|
+
for (const key of entries.keys()) {
|
|
200
|
+
if (!tenantStore?.has(key))
|
|
201
|
+
newEntryCount++;
|
|
202
|
+
}
|
|
203
|
+
// Preflight the complete batch so capacity failures cannot partially commit.
|
|
204
|
+
this.ensureCapacity(newEntryCount);
|
|
205
|
+
tenantStore ??= this.getOrCreateTenantStore(tenantId);
|
|
206
|
+
const expiresAt = options?.ttl !== undefined ? Date.now() + options.ttl * 1000 : undefined;
|
|
207
|
+
for (const [key, value] of entries) {
|
|
208
|
+
tenantStore.set(key, {
|
|
209
|
+
value,
|
|
210
|
+
...(expiresAt !== undefined && { expiresAt }),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
this.entryCount += newEntryCount;
|
|
174
214
|
logger.debug(`[InMemoryProvider] Successfully set ${entries.size} keys for tenant: ${tenantId}`, context);
|
|
175
215
|
}
|
|
176
216
|
async deleteMany(tenantId, keys, context) {
|
|
@@ -187,9 +227,11 @@ export class InMemoryProvider {
|
|
|
187
227
|
}
|
|
188
228
|
clear(tenantId, context) {
|
|
189
229
|
logger.debug(`[InMemoryProvider] Clearing all keys for tenant: ${tenantId}`, context);
|
|
190
|
-
const tenantStore = this.
|
|
230
|
+
const tenantStore = this.store.get(tenantId);
|
|
231
|
+
if (!tenantStore)
|
|
232
|
+
return Promise.resolve(0);
|
|
191
233
|
const count = tenantStore.size;
|
|
192
|
-
|
|
234
|
+
this.store.delete(tenantId);
|
|
193
235
|
this.entryCount -= count;
|
|
194
236
|
logger.info(`[InMemoryProvider] Cleared ${count} keys for tenant: ${tenantId}`, context);
|
|
195
237
|
return Promise.resolve(count);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inMemoryProvider.js","sourceRoot":"","sources":["../../../../src/storage/providers/inMemory/inMemoryProvider.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"inMemoryProvider.js","sourceRoot":"","sources":["../../../../src/storage/providers/inMemory/inMemoryProvider.ts"],"names":[],"mappings":"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"}
|
package/dist/testing/fuzz.d.ts
CHANGED
|
@@ -33,6 +33,10 @@ export interface FuzzOptions {
|
|
|
33
33
|
* Converts a Zod schema to a fast-check `Arbitrary` that produces valid values.
|
|
34
34
|
* Supports the JSON-Schema-serializable subset used by MCP tool/resource schemas.
|
|
35
35
|
*
|
|
36
|
+
* Finite nesting is expanded in full, however deep. A self-referential schema —
|
|
37
|
+
* Zod 4's getter pattern, where reading `.shape` re-enters the same node —
|
|
38
|
+
* terminates at the point of recursion with an absence the schema accepts.
|
|
39
|
+
*
|
|
36
40
|
* Requires `fast-check` to be loaded first — call from within a `fuzzTool()`/
|
|
37
41
|
* `fuzzResource()`/`fuzzPrompt()` run, or call `loadFc()` before standalone use.
|
|
38
42
|
*/
|
|
@@ -68,7 +72,7 @@ export interface FuzzReport {
|
|
|
68
72
|
* Designed to be called inside a `describe()` / `it()` block.
|
|
69
73
|
*
|
|
70
74
|
* Checks:
|
|
71
|
-
* 1. Valid inputs -> handler
|
|
75
|
+
* 1. Valid inputs -> handler returns schema-valid output or a well-formed MCP error
|
|
72
76
|
* 2. Adversarial inputs -> Zod rejects or handler errors gracefully
|
|
73
77
|
* 3. No prototype pollution on Object.prototype
|
|
74
78
|
* 4. No stack trace / path leaks in error messages
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fuzz.d.ts","sourceRoot":"","sources":["../../src/testing/fuzz.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAalD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gDAAgD,CAAC;AAC1F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oDAAoD,CAAC;AAChG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;
|
|
1
|
+
{"version":3,"file":"fuzz.d.ts","sourceRoot":"","sources":["../../src/testing/fuzz.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAalD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gDAAgD,CAAC;AAC1F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oDAAoD,CAAC;AAChG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAEpF,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAQxE,wMAAwM;AACxM,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAGjD;AAiBD,qCAAqC;AACrC,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,GAAG,CAAC,EAAE,kBAAkB,CAAC;IACzB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAmCD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAErE;AA6LD,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAwDvC,CAAC;AAEX,6EAA6E;AAC7E,wBAAgB,oBAAoB,IAAI,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CA6B5D;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,CAAC,WAAW,CAAC,GAC7B,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAUvC;AAiHD,0DAA0D;AAC1D,MAAM,WAAW,UAAU;IACzB,gFAAgF;IAChF,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACnD,4DAA4D;IAC5D,KAAK,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,sDAAsD;IACtD,kBAAkB,EAAE,OAAO,CAAC;IAC5B,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,iBAAiB,EACtB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,CAAC,CA8FrB;AAMD;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,qBAAqB,EAC1B,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,CAAC,CA2ErB;AAMD;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,mBAAmB,EACxB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,CAAC,CAsErB"}
|
package/dist/testing/fuzz.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* @module src/testing/fuzz
|
|
10
10
|
*/
|
|
11
11
|
import { ZodArray, ZodBoolean, ZodDefault, ZodEnum, ZodLiteral, ZodNullable, ZodNumber, ZodOptional, ZodString, ZodUnion, } from 'zod';
|
|
12
|
+
import { McpError } from '../types-global/errors.js';
|
|
12
13
|
import { createMockContext } from './index.js';
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// Lazy-loaded peer dependency
|
|
@@ -46,32 +47,69 @@ function zodTypeName(schema) {
|
|
|
46
47
|
// ---------------------------------------------------------------------------
|
|
47
48
|
// Zod → fast-check arbitrary generation
|
|
48
49
|
// ---------------------------------------------------------------------------
|
|
50
|
+
/** Elements generated for an array with no declared upper bound. */
|
|
51
|
+
const DEFAULT_ARRAY_MAX = 5;
|
|
52
|
+
/**
|
|
53
|
+
* Nesting levels expanded before a node is treated as non-terminating. Cycle
|
|
54
|
+
* detection catches self-referential schemas by identity; this is the backstop
|
|
55
|
+
* for a schema whose getters mint a fresh node on every access, where there is
|
|
56
|
+
* no repeated identity to detect.
|
|
57
|
+
*/
|
|
58
|
+
const MAX_SCHEMA_DEPTH = 32;
|
|
49
59
|
/**
|
|
50
60
|
* Converts a Zod schema to a fast-check `Arbitrary` that produces valid values.
|
|
51
61
|
* Supports the JSON-Schema-serializable subset used by MCP tool/resource schemas.
|
|
52
62
|
*
|
|
63
|
+
* Finite nesting is expanded in full, however deep. A self-referential schema —
|
|
64
|
+
* Zod 4's getter pattern, where reading `.shape` re-enters the same node —
|
|
65
|
+
* terminates at the point of recursion with an absence the schema accepts.
|
|
66
|
+
*
|
|
53
67
|
* Requires `fast-check` to be loaded first — call from within a `fuzzTool()`/
|
|
54
68
|
* `fuzzResource()`/`fuzzPrompt()` run, or call `loadFc()` before standalone use.
|
|
55
69
|
*/
|
|
56
70
|
export function zodToArbitrary(schema) {
|
|
57
|
-
return zodNodeToArbitrary(schema, 0);
|
|
71
|
+
return zodNodeToArbitrary(schema, new Set(), 0) ?? getFc().constant(null);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Expands one schema node, tracking the nodes on the current path so recursion
|
|
75
|
+
* is distinguished from finite nesting.
|
|
76
|
+
*
|
|
77
|
+
* Returns `undefined` when the node cannot yield a finite value — it re-enters
|
|
78
|
+
* itself, or nesting passed `MAX_SCHEMA_DEPTH`. Callers turn that into a
|
|
79
|
+
* schema-valid absence where one exists (`[]`, omitted optional, `null`), and
|
|
80
|
+
* propagate it otherwise, so a generated sample never carries an invalid value
|
|
81
|
+
* at an arbitrary position.
|
|
82
|
+
*/
|
|
83
|
+
function zodNodeToArbitrary(schema, path, depth) {
|
|
84
|
+
if (depth > MAX_SCHEMA_DEPTH)
|
|
85
|
+
return;
|
|
86
|
+
if (typeof schema !== 'object' || schema === null)
|
|
87
|
+
return expandNode(schema, path, depth);
|
|
88
|
+
if (path.has(schema))
|
|
89
|
+
return;
|
|
90
|
+
path.add(schema);
|
|
91
|
+
try {
|
|
92
|
+
return expandNode(schema, path, depth);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
path.delete(schema);
|
|
96
|
+
}
|
|
58
97
|
}
|
|
59
|
-
function
|
|
98
|
+
function expandNode(schema, path, depth) {
|
|
60
99
|
const f = getFc();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// Unwrap wrappers — cast through any to avoid Zod 4 $ZodType vs ZodType mismatch
|
|
100
|
+
// Unwrap wrappers — cast through any to avoid Zod 4 $ZodType vs ZodType mismatch.
|
|
101
|
+
// A wrapper whose inner node cannot terminate collapses to the absence it permits.
|
|
64
102
|
if (schema instanceof ZodOptional) {
|
|
65
|
-
|
|
103
|
+
const inner = zodNodeToArbitrary(schema.unwrap(), path, depth);
|
|
104
|
+
return inner ? f.option(inner, { nil: undefined }) : f.constant(undefined);
|
|
66
105
|
}
|
|
67
106
|
if (schema instanceof ZodNullable) {
|
|
68
|
-
|
|
107
|
+
const inner = zodNodeToArbitrary(schema.unwrap(), path, depth);
|
|
108
|
+
return inner ? f.option(inner, { nil: null }) : f.constant(null);
|
|
69
109
|
}
|
|
70
110
|
if (schema instanceof ZodDefault) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
freq: 5,
|
|
74
|
-
});
|
|
111
|
+
const inner = zodNodeToArbitrary(schema.removeDefault(), path, depth);
|
|
112
|
+
return inner ? f.option(inner, { nil: undefined, freq: 5 }) : f.constant(undefined);
|
|
75
113
|
}
|
|
76
114
|
// Primitives
|
|
77
115
|
if (schema instanceof ZodString || zodTypeName(schema) === 'string') {
|
|
@@ -93,17 +131,20 @@ function zodNodeToArbitrary(schema, depth) {
|
|
|
93
131
|
}
|
|
94
132
|
// Array
|
|
95
133
|
if (schema instanceof ZodArray) {
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
});
|
|
134
|
+
const { minLength, maxLength } = arrayLengthBounds(schema);
|
|
135
|
+
const element = zodNodeToArbitrary(schema.element, path, depth + 1);
|
|
136
|
+
// A recursive element type terminates here — an empty array is the finite value.
|
|
137
|
+
if (!element)
|
|
138
|
+
return f.constant([]);
|
|
139
|
+
return f.array(element, { minLength, maxLength });
|
|
102
140
|
}
|
|
103
141
|
// Union
|
|
104
142
|
if (schema instanceof ZodUnion) {
|
|
105
143
|
const options = schema._def.options;
|
|
106
|
-
|
|
144
|
+
const arbs = options
|
|
145
|
+
.map((o) => zodNodeToArbitrary(o, path, depth + 1))
|
|
146
|
+
.filter((arb) => arb !== undefined);
|
|
147
|
+
return arbs.length > 0 ? f.oneof(...arbs) : undefined;
|
|
107
148
|
}
|
|
108
149
|
// Object — check by _def.type since instanceof ZodObject may have type issues
|
|
109
150
|
if (zodTypeName(schema) === 'object') {
|
|
@@ -115,13 +156,46 @@ function zodNodeToArbitrary(schema, depth) {
|
|
|
115
156
|
return f.constant({});
|
|
116
157
|
const arbs = {};
|
|
117
158
|
for (const [key, fieldSchema] of entries) {
|
|
118
|
-
|
|
159
|
+
const field = zodNodeToArbitrary(fieldSchema, path, depth + 1);
|
|
160
|
+
// An optional or nullable field already collapsed to its permitted absence,
|
|
161
|
+
// so reaching here means a required field cannot terminate.
|
|
162
|
+
if (!field)
|
|
163
|
+
return;
|
|
164
|
+
arbs[key] = field;
|
|
119
165
|
}
|
|
120
166
|
return f.record(arbs);
|
|
121
167
|
}
|
|
122
168
|
// Fallback: generate JSON-safe primitives
|
|
123
169
|
return f.oneof(f.string(), f.integer(), f.boolean(), f.constant(null));
|
|
124
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Resolves an array's declared length bounds.
|
|
173
|
+
*
|
|
174
|
+
* Unlike ZodString, Zod 4's ZodArray exposes no `.minLength`/`.maxLength`
|
|
175
|
+
* accessors — `.min()`, `.max()`, `.length()`, and `.nonempty()` all land in
|
|
176
|
+
* `_def.checks`. Reading the absent accessor silently drops every one of them.
|
|
177
|
+
*/
|
|
178
|
+
function arrayLengthBounds(schema) {
|
|
179
|
+
const checks = (schema?._def?.checks ?? []);
|
|
180
|
+
let min = 0;
|
|
181
|
+
let max;
|
|
182
|
+
for (const check of checks) {
|
|
183
|
+
const def = check?._zod?.def;
|
|
184
|
+
if (def?.check === 'min_length' && typeof def.minimum === 'number') {
|
|
185
|
+
min = Math.max(min, def.minimum);
|
|
186
|
+
}
|
|
187
|
+
else if (def?.check === 'max_length' && typeof def.maximum === 'number') {
|
|
188
|
+
max = max === undefined ? def.maximum : Math.min(max, def.maximum);
|
|
189
|
+
}
|
|
190
|
+
else if (def?.check === 'length_equals' && typeof def.length === 'number') {
|
|
191
|
+
min = Math.max(min, def.length);
|
|
192
|
+
max = max === undefined ? def.length : Math.min(max, def.length);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// An unsatisfiable schema (min above max) still has to yield an arbitrary
|
|
196
|
+
// rather than throw out of the generator; the sample is rejected at parse.
|
|
197
|
+
return { minLength: min, maxLength: Math.max(min, max ?? DEFAULT_ARRAY_MAX) };
|
|
198
|
+
}
|
|
125
199
|
/**
|
|
126
200
|
* Zod 4 exposes `.minLength`, `.maxLength`, `.format` as direct accessors on ZodString.
|
|
127
201
|
*/
|
|
@@ -275,6 +349,50 @@ function checkErrorLeaks(errorText) {
|
|
|
275
349
|
/[A-Za-z]:\\/.test(errorText);
|
|
276
350
|
return { leakedStack, leakedInternals };
|
|
277
351
|
}
|
|
352
|
+
/** Serialize the fields an MCP client can observe, excluding the local Error stack itself. */
|
|
353
|
+
function publicErrorText(error) {
|
|
354
|
+
if (!(error instanceof McpError)) {
|
|
355
|
+
return error instanceof Error ? error.message : String(error);
|
|
356
|
+
}
|
|
357
|
+
const seen = new WeakSet();
|
|
358
|
+
try {
|
|
359
|
+
return JSON.stringify({ code: error.code, message: error.message, data: error.data }, (_key, value) => {
|
|
360
|
+
if (typeof value === 'bigint')
|
|
361
|
+
return value.toString();
|
|
362
|
+
if (typeof value === 'object' && value !== null) {
|
|
363
|
+
if (seen.has(value))
|
|
364
|
+
return '[Circular]';
|
|
365
|
+
seen.add(value);
|
|
366
|
+
}
|
|
367
|
+
return value;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return error.message;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/** Records a leak entry when an error's client-visible text exposes a stack or internal path. */
|
|
375
|
+
function recordLeak(report, input, error) {
|
|
376
|
+
const errorText = publicErrorText(error);
|
|
377
|
+
const { leakedStack, leakedInternals } = checkErrorLeaks(errorText);
|
|
378
|
+
if (leakedStack || leakedInternals) {
|
|
379
|
+
report.leaks.push({ input, errorText });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function recordHandlerError(report, input, error) {
|
|
383
|
+
if (!(error instanceof McpError)) {
|
|
384
|
+
report.crashes.push({ input, error });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
recordLeak(report, input, error);
|
|
388
|
+
}
|
|
389
|
+
function createToolFuzzContext(def, options, overrides = {}) {
|
|
390
|
+
return createMockContext({
|
|
391
|
+
...options.ctx,
|
|
392
|
+
...(def.errors !== undefined && { errors: def.errors }),
|
|
393
|
+
...overrides,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
278
396
|
// ---------------------------------------------------------------------------
|
|
279
397
|
// Prototype pollution detection
|
|
280
398
|
// ---------------------------------------------------------------------------
|
|
@@ -301,7 +419,7 @@ function createProtoPollutionGuard() {
|
|
|
301
419
|
* Designed to be called inside a `describe()` / `it()` block.
|
|
302
420
|
*
|
|
303
421
|
* Checks:
|
|
304
|
-
* 1. Valid inputs -> handler
|
|
422
|
+
* 1. Valid inputs -> handler returns schema-valid output or a well-formed MCP error
|
|
305
423
|
* 2. Adversarial inputs -> Zod rejects or handler errors gracefully
|
|
306
424
|
* 3. No prototype pollution on Object.prototype
|
|
307
425
|
* 4. No stack trace / path leaks in error messages
|
|
@@ -346,20 +464,20 @@ export async function fuzzTool(def, options = {}) {
|
|
|
346
464
|
const parsed = def.input.safeParse(raw);
|
|
347
465
|
if (!parsed.success)
|
|
348
466
|
return;
|
|
349
|
-
const ctx =
|
|
467
|
+
const ctx = createToolFuzzContext(def, options);
|
|
350
468
|
try {
|
|
351
469
|
const result = await withTimeout(def.handler(parsed.data, ctx), timeout);
|
|
352
470
|
def.output.parse(result);
|
|
353
471
|
}
|
|
354
472
|
catch (err) {
|
|
355
|
-
report
|
|
473
|
+
recordHandlerError(report, parsed.data, err);
|
|
356
474
|
}
|
|
357
475
|
}), fcParams);
|
|
358
476
|
// Phase 2: Adversarial inputs (should be caught by Zod or handler, never crash)
|
|
359
477
|
const advArb = adversarialObjectArbitrary(def.input);
|
|
360
478
|
await f.assert(f.asyncProperty(advArb, async (input) => {
|
|
361
479
|
report.totalRuns++;
|
|
362
|
-
const ctx =
|
|
480
|
+
const ctx = createToolFuzzContext(def, options);
|
|
363
481
|
try {
|
|
364
482
|
const validated = def.input.safeParse(input);
|
|
365
483
|
if (!validated.success)
|
|
@@ -368,11 +486,7 @@ export async function fuzzTool(def, options = {}) {
|
|
|
368
486
|
def.output.parse(result);
|
|
369
487
|
}
|
|
370
488
|
catch (err) {
|
|
371
|
-
|
|
372
|
-
const leakCheck = checkErrorLeaks(msg);
|
|
373
|
-
if (leakCheck.leakedStack || leakCheck.leakedInternals) {
|
|
374
|
-
report.leaks.push({ input, errorText: msg });
|
|
375
|
-
}
|
|
489
|
+
recordLeak(report, input, err);
|
|
376
490
|
}
|
|
377
491
|
}), { ...fcParams, numRuns: numAdversarial });
|
|
378
492
|
// Phase 3: Raw adversarial (completely wrong types at the top level)
|
|
@@ -392,7 +506,7 @@ export async function fuzzTool(def, options = {}) {
|
|
|
392
506
|
const validated = def.input.safeParse(input);
|
|
393
507
|
if (!validated.success)
|
|
394
508
|
continue;
|
|
395
|
-
const ctx =
|
|
509
|
+
const ctx = createToolFuzzContext(def, options);
|
|
396
510
|
await withTimeout(def.handler(validated.data, ctx), timeout);
|
|
397
511
|
}
|
|
398
512
|
catch {
|
|
@@ -404,7 +518,7 @@ export async function fuzzTool(def, options = {}) {
|
|
|
404
518
|
try {
|
|
405
519
|
const controller = new AbortController();
|
|
406
520
|
controller.abort();
|
|
407
|
-
const ctx =
|
|
521
|
+
const ctx = createToolFuzzContext(def, options, { signal: controller.signal });
|
|
408
522
|
const rawSample = generateOne(validArb);
|
|
409
523
|
const parsedSample = def.input.parse(rawSample);
|
|
410
524
|
await withTimeout(def.handler(parsedSample, ctx), timeout);
|
|
@@ -460,7 +574,7 @@ export async function fuzzResource(def, options = {}) {
|
|
|
460
574
|
await withTimeout(def.handler(parsed.data, ctx), timeout);
|
|
461
575
|
}
|
|
462
576
|
catch (err) {
|
|
463
|
-
report
|
|
577
|
+
recordHandlerError(report, parsed.data, err);
|
|
464
578
|
}
|
|
465
579
|
}), fcParams);
|
|
466
580
|
// Phase 2: Adversarial params
|
|
@@ -478,11 +592,7 @@ export async function fuzzResource(def, options = {}) {
|
|
|
478
592
|
await withTimeout(def.handler(validated.data, ctx), timeout);
|
|
479
593
|
}
|
|
480
594
|
catch (err) {
|
|
481
|
-
|
|
482
|
-
const leakCheck = checkErrorLeaks(msg);
|
|
483
|
-
if (leakCheck.leakedStack || leakCheck.leakedInternals) {
|
|
484
|
-
report.leaks.push({ input: params, errorText: msg });
|
|
485
|
-
}
|
|
595
|
+
recordLeak(report, params, err);
|
|
486
596
|
}
|
|
487
597
|
}), { ...fcParams, numRuns: numAdversarial });
|
|
488
598
|
}
|
|
@@ -496,7 +606,7 @@ export async function fuzzResource(def, options = {}) {
|
|
|
496
606
|
await withTimeout(def.handler({}, ctx), timeout);
|
|
497
607
|
}
|
|
498
608
|
catch (err) {
|
|
499
|
-
report
|
|
609
|
+
recordHandlerError(report, {}, err);
|
|
500
610
|
}
|
|
501
611
|
}
|
|
502
612
|
protoGuard.check(report);
|