mcp-warden 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vikrant Kumar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # mcp-warden
2
+
3
+ ![NPM Version](https://img.shields.io/npm/v/mcp-warden)
4
+ ![License](https://img.shields.io/npm/l/mcp-warden)
5
+ ![Bundle Size](https://img.shields.io/bundlephobia/min/mcp-warden)
6
+
7
+ High-performance security guardrails for MCP-compatible AI agents and tool execution.
8
+
9
+ ## Features
10
+
11
+ - Policy-based tool authorization for MCP tool calls.
12
+ - Prompt-injection scanning for high-risk control phrases.
13
+ - Output redaction for email addresses, API keys, and IP addresses.
14
+ - Built-in rate limiting and circuit-breaker protection.
15
+ - CLI audit workflow for identifying over-permissioned MCP servers.
16
+
17
+ ## Table of Contents
18
+
19
+ - [Why Security Matters for AI Agents](#why-security-matters-for-ai-agents)
20
+ - [Quick Start](#quick-start)
21
+ - [Policy Configuration](#policy-configuration)
22
+ - [CLI Commands](#cli-commands)
23
+ - [Development](#development)
24
+ - [License](#license)
25
+ - [Security Disclaimer](#security-disclaimer)
26
+
27
+ ## Why Security Matters for AI Agents
28
+
29
+ AI agents can execute tools with real-world side effects: reading files, modifying systems, calling external APIs, and handling sensitive data. Without guardrails, a single prompt injection or over-permissioned server can lead to data leakage, privilege escalation, or runaway tool loops.
30
+
31
+ mcp-warden helps enforce a security boundary before and after tool execution:
32
+ - Blocks unauthorized tools using explicit policy rules.
33
+ - Detects prompt-injection signatures in tool arguments.
34
+ - Enforces rate limits to reduce abuse and runaway call storms.
35
+ - Applies circuit-breaker protection for repeated tool failures.
36
+ - Redacts sensitive output data before it reaches downstream systems.
37
+
38
+ ## Quick Start
39
+
40
+ ### 1. Install
41
+
42
+ ```bash
43
+ npm install mcp-warden
44
+ ```
45
+
46
+ ### 2. Define a policy
47
+
48
+ ```json
49
+ {
50
+ "allowedTools": ["list_dir", "read_file", "grep_search"],
51
+ "restrictedPaths": [
52
+ {
53
+ "path": "/",
54
+ "mode": "blocked"
55
+ }
56
+ ],
57
+ "maxCallsPerMinute": 60,
58
+ "approvalRequired": true
59
+ }
60
+ ```
61
+
62
+ ### 3. Protect your transport handler
63
+
64
+ ```ts
65
+ import { McpGuardian, type GuardianPolicy } from "mcp-warden";
66
+
67
+ const policy: GuardianPolicy = {
68
+ allowedTools: ["read_file", /^search_/],
69
+ restrictedPaths: [{ path: "/", mode: "blocked" }],
70
+ maxCallsPerMinute: 60,
71
+ approvalRequired: true
72
+ };
73
+
74
+ const guardian = new McpGuardian(policy, { dryRun: false });
75
+
76
+ const guardedHandler = guardian.wrapHandler(async (request) => {
77
+ // Your MCP transport execution logic
78
+ return {
79
+ jsonrpc: "2.0",
80
+ id: request.id ?? null,
81
+ result: { ok: true }
82
+ };
83
+ });
84
+ ```
85
+
86
+ ### 4. Use the CLI
87
+
88
+ ```bash
89
+ # Audit MCP client config files for excessive permissions
90
+ npx mcp-warden audit ./claude_desktop_config.json
91
+
92
+ # Generate a default policy file
93
+ npx mcp-warden init
94
+ ```
95
+
96
+ If you install globally, you can use the short command directly:
97
+
98
+ ```bash
99
+ mcp-warden audit ./claude_desktop_config.json
100
+ mcp-warden init
101
+ ```
102
+
103
+ ## Policy Configuration
104
+
105
+ | Option | Type | Required | Description | Example |
106
+ |---|---|---|---|---|
107
+ | allowedTools | Array<string \| RegExp> | Yes | Allowed tool names or regex matchers for tools/call requests. | ["read_file", /^search_/] |
108
+ | restrictedPaths | Array<{ path: string; mode: "read-only" \| "blocked" }> | Yes | Directory access constraints used by filesystem-aware middleware. | [{ "path": "/", "mode": "blocked" }] |
109
+ | maxCallsPerMinute | number | Yes | Rolling 60-second budget for tool calls. Requests above this limit are denied. | 60 |
110
+ | approvalRequired | boolean | Yes | Indicates destructive actions should require explicit approval in your orchestration flow. | true |
111
+
112
+ ## CLI Commands
113
+
114
+ - npx mcp-warden audit <config-path>
115
+ - npx mcp-warden init [--output <path>] [--force]
116
+
117
+ CLI output uses color-coded alerts:
118
+ - Green: SAFE
119
+ - Red: CRITICAL
120
+
121
+ ## Development
122
+
123
+ ```bash
124
+ npm install
125
+ npm run typecheck
126
+ npm test
127
+ npm run build
128
+ ```
129
+
130
+ ## License
131
+
132
+ MIT
133
+
134
+ ## Security Disclaimer
135
+
136
+ mcp-warden is a runtime governance layer designed to mitigate risks. It is not a replacement for OS-level permissions, network-level firewalls, or the principle of least privilege. Always run AI agents in isolated environments when possible.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env node
2
+ import { promises as fs } from "node:fs";
3
+ import * as path from "node:path";
4
+ import chalk from "chalk";
5
+ import { Command } from "commander";
6
+ import { GuardianPolicySchema } from "../types/policy.js";
7
+ /**
8
+ * Policy file created by `mcp-warden init`.
9
+ */
10
+ const DEFAULT_POLICY = {
11
+ allowedTools: ["list_dir", "read_file", "grep_search"],
12
+ restrictedPaths: [
13
+ {
14
+ path: "/",
15
+ mode: "blocked"
16
+ }
17
+ ],
18
+ maxCallsPerMinute: 60,
19
+ approvalRequired: true
20
+ };
21
+ /**
22
+ * True if a value is a plain object.
23
+ */
24
+ function isRecord(value) {
25
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
26
+ }
27
+ /**
28
+ * Converts unknown values to a string array.
29
+ */
30
+ function toStringArray(value) {
31
+ if (!Array.isArray(value)) {
32
+ return [];
33
+ }
34
+ return value.filter((entry) => typeof entry === "string");
35
+ }
36
+ /**
37
+ * Best-effort extraction of MCP servers from common config layouts.
38
+ */
39
+ function extractServers(config) {
40
+ if (!isRecord(config)) {
41
+ return [];
42
+ }
43
+ const candidates = [
44
+ config.mcpServers,
45
+ isRecord(config.mcp) ? config.mcp.servers : undefined,
46
+ config.servers
47
+ ];
48
+ for (const candidate of candidates) {
49
+ if (!isRecord(candidate)) {
50
+ continue;
51
+ }
52
+ return Object.entries(candidate)
53
+ .filter(([, server]) => isRecord(server))
54
+ .map(([name, server]) => ({
55
+ name,
56
+ config: server
57
+ }));
58
+ }
59
+ return [];
60
+ }
61
+ /**
62
+ * Returns true when a path represents broad filesystem access.
63
+ */
64
+ function isBroadPath(value) {
65
+ const normalized = value.trim().toLowerCase();
66
+ return (normalized === "/" ||
67
+ normalized === "~" ||
68
+ normalized === "$home" ||
69
+ normalized === "${home}" ||
70
+ normalized === "c:\\" ||
71
+ normalized.startsWith("/users"));
72
+ }
73
+ /**
74
+ * Detects critical full-access permissions in a server config.
75
+ */
76
+ function getCriticalFindings(server) {
77
+ const findings = [];
78
+ const permissions = toStringArray(server.permissions);
79
+ if (permissions.some((entry) => ["*", "all", "full-disk-access", "filesystem:*"].includes(entry.trim().toLowerCase()))) {
80
+ findings.push("Permission set grants full disk access.");
81
+ }
82
+ const roots = toStringArray(server.roots);
83
+ const allowedPaths = toStringArray(server.allowedPaths);
84
+ const broadPath = [...roots, ...allowedPaths].find((entry) => isBroadPath(entry));
85
+ if (broadPath) {
86
+ findings.push(`Broad filesystem path is allowed: ${broadPath}`);
87
+ }
88
+ const args = toStringArray(server.args).map((arg) => arg.toLowerCase());
89
+ if (args.some((arg) => [
90
+ "--allow-all",
91
+ "--dangerously-skip-permissions",
92
+ "--full-disk-access",
93
+ "--allow-read=/",
94
+ "--allow-write=/"
95
+ ].some((flag) => arg.includes(flag)))) {
96
+ findings.push("Command-line flags indicate unrestricted filesystem access.");
97
+ }
98
+ if (isRecord(server.env)) {
99
+ const envPairs = Object.entries(server.env);
100
+ const hasWideEnvAccess = envPairs.some(([key, rawValue]) => {
101
+ if (typeof rawValue !== "string") {
102
+ return false;
103
+ }
104
+ const envKey = key.toLowerCase();
105
+ const envValue = rawValue.trim().toLowerCase();
106
+ return (["full_disk_access", "allow_all", "dangerously_skip_permissions"].includes(envKey) &&
107
+ ["1", "true", "yes", "on"].includes(envValue));
108
+ });
109
+ if (hasWideEnvAccess) {
110
+ findings.push("Environment variables enable unrestricted permissions.");
111
+ }
112
+ }
113
+ return findings;
114
+ }
115
+ /**
116
+ * Reads and parses a JSON configuration file from disk.
117
+ */
118
+ async function readJsonFile(filePath) {
119
+ const content = await fs.readFile(filePath, "utf8");
120
+ return JSON.parse(content);
121
+ }
122
+ /**
123
+ * Runs audit mode and prints color-coded findings.
124
+ */
125
+ async function runAudit(configPath) {
126
+ const absolutePath = path.resolve(process.cwd(), configPath);
127
+ const config = await readJsonFile(absolutePath);
128
+ const servers = extractServers(config);
129
+ console.log(chalk.bold(`Audit file: ${absolutePath}`));
130
+ if (servers.length === 0) {
131
+ console.log(chalk.red("CRITICAL: No MCP servers found. Verify config structure before deployment."));
132
+ process.exitCode = 1;
133
+ return;
134
+ }
135
+ let criticalCount = 0;
136
+ for (const server of servers) {
137
+ const findings = getCriticalFindings(server.config);
138
+ if (findings.length === 0) {
139
+ console.log(chalk.green(`SAFE: ${server.name}`));
140
+ continue;
141
+ }
142
+ criticalCount += 1;
143
+ console.log(chalk.red(`CRITICAL: ${server.name}`));
144
+ for (const finding of findings) {
145
+ console.log(chalk.red(` - ${finding}`));
146
+ }
147
+ }
148
+ if (criticalCount > 0) {
149
+ console.log(chalk.red(`\nCritical servers: ${criticalCount}/${servers.length}`));
150
+ process.exitCode = 1;
151
+ return;
152
+ }
153
+ console.log(chalk.green(`\nAll servers safe: ${servers.length}/${servers.length}`));
154
+ }
155
+ /**
156
+ * Creates a default guardian policy JSON file.
157
+ */
158
+ async function runInit(outputPath, force) {
159
+ const absolutePath = path.resolve(process.cwd(), outputPath);
160
+ if (!force) {
161
+ try {
162
+ await fs.access(absolutePath);
163
+ console.log(chalk.red(`CRITICAL: ${outputPath} already exists. Use --force to overwrite.`));
164
+ process.exitCode = 1;
165
+ return;
166
+ }
167
+ catch {
168
+ // File does not exist and can be created.
169
+ }
170
+ }
171
+ const policy = GuardianPolicySchema.parse(DEFAULT_POLICY);
172
+ const serialized = `${JSON.stringify(policy, null, 2)}\n`;
173
+ await fs.writeFile(absolutePath, serialized, "utf8");
174
+ console.log(chalk.green(`SAFE: Created ${outputPath}`));
175
+ }
176
+ /**
177
+ * Boots the mcp-warden CLI.
178
+ */
179
+ async function main() {
180
+ const program = new Command();
181
+ program
182
+ .name("mcp-warden")
183
+ .description("Security auditing and policy tooling for MCP servers")
184
+ .version("0.1.0");
185
+ program
186
+ .command("audit")
187
+ .description("Audit a claude_desktop_config.json or cursor-settings.json file")
188
+ .argument("<config-path>", "Path to a JSON config file")
189
+ .action(async (configPath) => {
190
+ await runAudit(configPath);
191
+ });
192
+ program
193
+ .command("init")
194
+ .description("Generate a default mcp-policy.json")
195
+ .option("-o, --output <path>", "Output path for generated policy", "mcp-policy.json")
196
+ .option("-f, --force", "Overwrite existing file")
197
+ .action(async (options) => {
198
+ await runInit(options.output, Boolean(options.force));
199
+ });
200
+ await program.parseAsync(process.argv);
201
+ }
202
+ main().catch((error) => {
203
+ const message = error instanceof Error ? error.message : "Unknown CLI error";
204
+ console.error(chalk.red(`CRITICAL: ${message}`));
205
+ process.exitCode = 1;
206
+ });
207
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAuB,MAAM,oBAAoB,CAAC;AAsB/E;;GAEG;AACH,MAAM,cAAc,GAAmB;IACrC,YAAY,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,aAAa,CAAC;IACtD,eAAe,EAAE;QACf;YACE,IAAI,EAAE,GAAG;YACT,IAAI,EAAE,SAAS;SAChB;KACF;IACD,iBAAiB,EAAE,EAAE;IACrB,gBAAgB,EAAE,IAAI;CACvB,CAAC;AAEF;;GAEG;AACH,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC;AAC7E,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,MAAe;IACrC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,UAAU,GAAc;QAC5B,MAAM,CAAC,UAAU;QACjB,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QACrD,MAAM,CAAC,OAAO;KACf,CAAC;IAEF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACzB,SAAS;QACX,CAAC;QAED,OAAO,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;aAC7B,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;aACxC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;YACxB,IAAI;YACJ,MAAM,EAAE,MAAyB;SAClC,CAAC,CAAC,CAAC;IACR,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;GAEG;AACH,SAAS,WAAW,CAAC,KAAa;IAChC,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9C,OAAO,CACL,UAAU,KAAK,GAAG;QAClB,UAAU,KAAK,GAAG;QAClB,UAAU,KAAK,OAAO;QACtB,UAAU,KAAK,SAAS;QACxB,UAAU,KAAK,MAAM;QACrB,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAChC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAC,MAAuB;IAClD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACtD,IACE,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACzB,CAAC,GAAG,EAAE,KAAK,EAAE,kBAAkB,EAAE,cAAc,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CACtF,EACD,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG,aAAa,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACxD,MAAM,SAAS,GAAG,CAAC,GAAG,KAAK,EAAE,GAAG,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;IAClF,IAAI,SAAS,EAAE,CAAC;QACd,QAAQ,CAAC,IAAI,CAAC,qCAAqC,SAAS,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IACxE,IACE,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAChB;QACE,aAAa;QACb,gCAAgC;QAChC,oBAAoB;QACpB,gBAAgB;QAChB,iBAAiB;KAClB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CACrC,EACD,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IAC/E,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5C,MAAM,gBAAgB,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,EAAE;YACzD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACjC,OAAO,KAAK,CAAC;YACf,CAAC;YAED,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;YACjC,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC/C,OAAO,CACL,CAAC,kBAAkB,EAAE,WAAW,EAAE,8BAA8B,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAClF,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAC9C,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,IAAI,gBAAgB,EAAE,CAAC;YACrB,QAAQ,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,YAAY,CAAC,QAAgB;IAC1C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAY,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,QAAQ,CAAC,UAAkB;IACxC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAEvC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,YAAY,EAAE,CAAC,CAAC,CAAC;IAEvD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,4EAA4E,CAAC,CAAC,CAAC;QACrG,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YACjD,SAAS;QACX,CAAC;QAED,aAAa,IAAI,CAAC,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACnD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,uBAAuB,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACjF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,uBAAuB,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACtF,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,OAAO,CAAC,UAAkB,EAAE,KAAc;IACvD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAC;IAE7D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,UAAU,4CAA4C,CAAC,CAAC,CAAC;YAC5F,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAAC,MAAM,CAAC;YACP,0CAA0C;QAC5C,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;IAC1D,MAAM,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IAErD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,iBAAiB,UAAU,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,YAAY,CAAC;SAClB,WAAW,CAAC,sDAAsD,CAAC;SACnE,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpB,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,iEAAiE,CAAC;SAC9E,QAAQ,CAAC,eAAe,EAAE,4BAA4B,CAAC;SACvD,MAAM,CAAC,KAAK,EAAE,UAAkB,EAAE,EAAE;QACnC,MAAM,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,oCAAoC,CAAC;SACjD,MAAM,CAAC,qBAAqB,EAAE,kCAAkC,EAAE,iBAAiB,CAAC;SACpF,MAAM,CAAC,aAAa,EAAE,yBAAyB,CAAC;SAChD,MAAM,CAAC,KAAK,EAAE,OAA4C,EAAE,EAAE;QAC7D,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEL,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC;IAC7E,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\n\nimport { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport { GuardianPolicySchema, type GuardianPolicy } from \"../types/policy.js\";\n\n/**\n * Minimal server configuration shape extracted from MCP client config files.\n */\ninterface McpServerConfig {\n args?: unknown;\n env?: unknown;\n permissions?: unknown;\n roots?: unknown;\n allowedPaths?: unknown;\n [key: string]: unknown;\n}\n\n/**\n * Named server entry used by the audit report.\n */\ninterface ServerEntry {\n name: string;\n config: McpServerConfig;\n}\n\n/**\n * Policy file created by `mcp-warden init`.\n */\nconst DEFAULT_POLICY: GuardianPolicy = {\n allowedTools: [\"list_dir\", \"read_file\", \"grep_search\"],\n restrictedPaths: [\n {\n path: \"/\",\n mode: \"blocked\"\n }\n ],\n maxCallsPerMinute: 60,\n approvalRequired: true\n};\n\n/**\n * True if a value is a plain object.\n */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Converts unknown values to a string array.\n */\nfunction toStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n\n return value.filter((entry): entry is string => typeof entry === \"string\");\n}\n\n/**\n * Best-effort extraction of MCP servers from common config layouts.\n */\nfunction extractServers(config: unknown): ServerEntry[] {\n if (!isRecord(config)) {\n return [];\n }\n\n const candidates: unknown[] = [\n config.mcpServers,\n isRecord(config.mcp) ? config.mcp.servers : undefined,\n config.servers\n ];\n\n for (const candidate of candidates) {\n if (!isRecord(candidate)) {\n continue;\n }\n\n return Object.entries(candidate)\n .filter(([, server]) => isRecord(server))\n .map(([name, server]) => ({\n name,\n config: server as McpServerConfig\n }));\n }\n\n return [];\n}\n\n/**\n * Returns true when a path represents broad filesystem access.\n */\nfunction isBroadPath(value: string): boolean {\n const normalized = value.trim().toLowerCase();\n return (\n normalized === \"/\" ||\n normalized === \"~\" ||\n normalized === \"$home\" ||\n normalized === \"${home}\" ||\n normalized === \"c:\\\\\" ||\n normalized.startsWith(\"/users\")\n );\n}\n\n/**\n * Detects critical full-access permissions in a server config.\n */\nfunction getCriticalFindings(server: McpServerConfig): string[] {\n const findings: string[] = [];\n\n const permissions = toStringArray(server.permissions);\n if (\n permissions.some((entry) =>\n [\"*\", \"all\", \"full-disk-access\", \"filesystem:*\"].includes(entry.trim().toLowerCase())\n )\n ) {\n findings.push(\"Permission set grants full disk access.\");\n }\n\n const roots = toStringArray(server.roots);\n const allowedPaths = toStringArray(server.allowedPaths);\n const broadPath = [...roots, ...allowedPaths].find((entry) => isBroadPath(entry));\n if (broadPath) {\n findings.push(`Broad filesystem path is allowed: ${broadPath}`);\n }\n\n const args = toStringArray(server.args).map((arg) => arg.toLowerCase());\n if (\n args.some((arg) =>\n [\n \"--allow-all\",\n \"--dangerously-skip-permissions\",\n \"--full-disk-access\",\n \"--allow-read=/\",\n \"--allow-write=/\"\n ].some((flag) => arg.includes(flag))\n )\n ) {\n findings.push(\"Command-line flags indicate unrestricted filesystem access.\");\n }\n\n if (isRecord(server.env)) {\n const envPairs = Object.entries(server.env);\n const hasWideEnvAccess = envPairs.some(([key, rawValue]) => {\n if (typeof rawValue !== \"string\") {\n return false;\n }\n\n const envKey = key.toLowerCase();\n const envValue = rawValue.trim().toLowerCase();\n return (\n [\"full_disk_access\", \"allow_all\", \"dangerously_skip_permissions\"].includes(envKey) &&\n [\"1\", \"true\", \"yes\", \"on\"].includes(envValue)\n );\n });\n\n if (hasWideEnvAccess) {\n findings.push(\"Environment variables enable unrestricted permissions.\");\n }\n }\n\n return findings;\n}\n\n/**\n * Reads and parses a JSON configuration file from disk.\n */\nasync function readJsonFile(filePath: string): Promise<unknown> {\n const content = await fs.readFile(filePath, \"utf8\");\n return JSON.parse(content) as unknown;\n}\n\n/**\n * Runs audit mode and prints color-coded findings.\n */\nasync function runAudit(configPath: string): Promise<void> {\n const absolutePath = path.resolve(process.cwd(), configPath);\n const config = await readJsonFile(absolutePath);\n const servers = extractServers(config);\n\n console.log(chalk.bold(`Audit file: ${absolutePath}`));\n\n if (servers.length === 0) {\n console.log(chalk.red(\"CRITICAL: No MCP servers found. Verify config structure before deployment.\"));\n process.exitCode = 1;\n return;\n }\n\n let criticalCount = 0;\n\n for (const server of servers) {\n const findings = getCriticalFindings(server.config);\n if (findings.length === 0) {\n console.log(chalk.green(`SAFE: ${server.name}`));\n continue;\n }\n\n criticalCount += 1;\n console.log(chalk.red(`CRITICAL: ${server.name}`));\n for (const finding of findings) {\n console.log(chalk.red(` - ${finding}`));\n }\n }\n\n if (criticalCount > 0) {\n console.log(chalk.red(`\\nCritical servers: ${criticalCount}/${servers.length}`));\n process.exitCode = 1;\n return;\n }\n\n console.log(chalk.green(`\\nAll servers safe: ${servers.length}/${servers.length}`));\n}\n\n/**\n * Creates a default guardian policy JSON file.\n */\nasync function runInit(outputPath: string, force: boolean): Promise<void> {\n const absolutePath = path.resolve(process.cwd(), outputPath);\n\n if (!force) {\n try {\n await fs.access(absolutePath);\n console.log(chalk.red(`CRITICAL: ${outputPath} already exists. Use --force to overwrite.`));\n process.exitCode = 1;\n return;\n } catch {\n // File does not exist and can be created.\n }\n }\n\n const policy = GuardianPolicySchema.parse(DEFAULT_POLICY);\n const serialized = `${JSON.stringify(policy, null, 2)}\\n`;\n await fs.writeFile(absolutePath, serialized, \"utf8\");\n\n console.log(chalk.green(`SAFE: Created ${outputPath}`));\n}\n\n/**\n * Boots the mcp-warden CLI.\n */\nasync function main(): Promise<void> {\n const program = new Command();\n\n program\n .name(\"mcp-warden\")\n .description(\"Security auditing and policy tooling for MCP servers\")\n .version(\"0.1.0\");\n\n program\n .command(\"audit\")\n .description(\"Audit a claude_desktop_config.json or cursor-settings.json file\")\n .argument(\"<config-path>\", \"Path to a JSON config file\")\n .action(async (configPath: string) => {\n await runAudit(configPath);\n });\n\n program\n .command(\"init\")\n .description(\"Generate a default mcp-policy.json\")\n .option(\"-o, --output <path>\", \"Output path for generated policy\", \"mcp-policy.json\")\n .option(\"-f, --force\", \"Overwrite existing file\")\n .action(async (options: { output: string; force?: boolean }) => {\n await runInit(options.output, Boolean(options.force));\n });\n\n await program.parseAsync(process.argv);\n}\n\nmain().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : \"Unknown CLI error\";\n console.error(chalk.red(`CRITICAL: ${message}`));\n process.exitCode = 1;\n});\n"]}
@@ -0,0 +1,122 @@
1
+ import { type GuardianPolicy } from "../types/policy.js";
2
+ import { type CircuitBreakerOptions } from "../security/circuit-breaker.js";
3
+ /**
4
+ * JSON-RPC request identifier shape.
5
+ */
6
+ export type JsonRpcId = string | number | null;
7
+ /**
8
+ * Minimal JSON-RPC 2.0 request payload used by MCP transports.
9
+ */
10
+ export interface JsonRpcRequest {
11
+ jsonrpc: "2.0";
12
+ id?: JsonRpcId;
13
+ method: string;
14
+ params?: unknown;
15
+ }
16
+ /**
17
+ * Standardized JSON-RPC 2.0 error object.
18
+ */
19
+ export interface JsonRpcError {
20
+ code: number;
21
+ message: string;
22
+ data?: Record<string, unknown>;
23
+ }
24
+ /**
25
+ * Standardized JSON-RPC 2.0 error response payload.
26
+ */
27
+ export interface JsonRpcErrorResponse {
28
+ jsonrpc: "2.0";
29
+ id: JsonRpcId;
30
+ error: JsonRpcError;
31
+ }
32
+ /**
33
+ * Security violation metadata emitted by the guardian engine.
34
+ */
35
+ export interface GuardianViolation {
36
+ code: "PERMISSION_DENIED";
37
+ reason: string;
38
+ method: string;
39
+ toolName?: string;
40
+ }
41
+ /**
42
+ * Validation result produced by the guardian engine.
43
+ */
44
+ export interface ValidationResult {
45
+ isAllowed: boolean;
46
+ error?: JsonRpcErrorResponse;
47
+ violation?: GuardianViolation;
48
+ }
49
+ /**
50
+ * Decision object returned by guardian middleware.
51
+ */
52
+ export interface MiddlewareDecision {
53
+ allowed: boolean;
54
+ reason?: string;
55
+ }
56
+ /**
57
+ * Execution context provided to each middleware stage.
58
+ */
59
+ export interface GuardianContext {
60
+ readonly request: JsonRpcRequest;
61
+ readonly policy: GuardianPolicy;
62
+ readonly isDryRun: boolean;
63
+ readonly toolName?: string;
64
+ readonly toolArgs?: unknown;
65
+ }
66
+ /**
67
+ * Async middleware function signature used by the guardian engine.
68
+ */
69
+ export type GuardianMiddleware = (context: GuardianContext, next: () => Promise<MiddlewareDecision>) => Promise<MiddlewareDecision>;
70
+ /**
71
+ * Logging function for policy violations.
72
+ */
73
+ export type GuardianLogger = (violation: GuardianViolation) => void;
74
+ /**
75
+ * Configuration object for McpGuardian construction.
76
+ */
77
+ export interface McpGuardianOptions {
78
+ dryRun?: boolean;
79
+ logger?: GuardianLogger;
80
+ injectionKeywords?: readonly string[];
81
+ circuitBreaker?: CircuitBreakerOptions;
82
+ redactToolOutputs?: boolean;
83
+ nowProvider?: () => number;
84
+ }
85
+ /**
86
+ * Core policy guard for intercepting JSON-RPC tool calls.
87
+ *
88
+ * This class can be used as a standalone validator (`validateRequest`) or as a
89
+ * transport wrapper through `wrapHandler` to gate downstream request handlers.
90
+ */
91
+ export declare class McpGuardian {
92
+ private readonly policy;
93
+ private readonly isDryRun;
94
+ private readonly logger;
95
+ private readonly middlewares;
96
+ private readonly circuitBreaker;
97
+ private readonly injectionKeywords;
98
+ private readonly redactToolOutputs;
99
+ private readonly rateLimiter;
100
+ private readonly nowProvider;
101
+ /**
102
+ * Creates a guardian instance with policy and optional runtime controls.
103
+ */
104
+ constructor(policy: GuardianPolicy, options?: McpGuardianOptions);
105
+ /**
106
+ * Registers additional middleware checks that run after the built-in policy check.
107
+ */
108
+ use(middleware: GuardianMiddleware): this;
109
+ /**
110
+ * Validates an incoming JSON-RPC request against the configured guardrail chain.
111
+ */
112
+ validateRequest(request: JsonRpcRequest): Promise<ValidationResult>;
113
+ /**
114
+ * Wraps a JSON-RPC handler and blocks requests that violate policy.
115
+ */
116
+ wrapHandler<TResponse>(handler: (request: JsonRpcRequest) => Promise<TResponse | JsonRpcErrorResponse>): (request: JsonRpcRequest) => Promise<TResponse | JsonRpcErrorResponse>;
117
+ /**
118
+ * Runs middleware chain using a deterministic Koa-style composition model.
119
+ */
120
+ private runMiddlewares;
121
+ }
122
+ //# sourceMappingURL=interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../../src/core/interceptor.ts"],"names":[],"mappings":"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"}