@mcp-guardian/server 0.4.0 → 0.6.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.
Files changed (50) hide show
  1. package/README.md +116 -8
  2. package/dist/auth/auth-types.d.ts +40 -0
  3. package/dist/auth/auth-types.d.ts.map +1 -0
  4. package/dist/auth/auth-types.js +5 -0
  5. package/dist/auth/auth-types.js.map +1 -0
  6. package/dist/auth/oauth.d.ts +25 -0
  7. package/dist/auth/oauth.d.ts.map +1 -0
  8. package/dist/auth/oauth.js +96 -0
  9. package/dist/auth/oauth.js.map +1 -0
  10. package/dist/auth/session-cache.d.ts +47 -0
  11. package/dist/auth/session-cache.d.ts.map +1 -0
  12. package/dist/auth/session-cache.js +91 -0
  13. package/dist/auth/session-cache.js.map +1 -0
  14. package/dist/cli.js +23 -5
  15. package/dist/cli.js.map +1 -1
  16. package/dist/database/database-interface.d.ts +17 -0
  17. package/dist/database/database-interface.d.ts.map +1 -0
  18. package/dist/database/database-interface.js +2 -0
  19. package/dist/database/database-interface.js.map +1 -0
  20. package/dist/database/history-db.d.ts.map +1 -1
  21. package/dist/database/history-db.js.map +1 -1
  22. package/dist/database/postgres-db.d.ts +18 -0
  23. package/dist/database/postgres-db.d.ts.map +1 -0
  24. package/dist/database/postgres-db.js +118 -0
  25. package/dist/database/postgres-db.js.map +1 -0
  26. package/dist/index.js +1 -1
  27. package/dist/policy/policy-engine.d.ts.map +1 -1
  28. package/dist/policy/policy-engine.js +21 -0
  29. package/dist/policy/policy-engine.js.map +1 -1
  30. package/dist/policy/policy-types.d.ts +9 -0
  31. package/dist/policy/policy-types.d.ts.map +1 -1
  32. package/dist/policy/policy-watcher.d.ts +24 -0
  33. package/dist/policy/policy-watcher.d.ts.map +1 -0
  34. package/dist/policy/policy-watcher.js +68 -0
  35. package/dist/policy/policy-watcher.js.map +1 -0
  36. package/dist/proxy/proxy-manager.d.ts +3 -1
  37. package/dist/proxy/proxy-manager.d.ts.map +1 -1
  38. package/dist/proxy/proxy-manager.js +10 -3
  39. package/dist/proxy/proxy-manager.js.map +1 -1
  40. package/dist/proxy/proxy-server.d.ts +20 -8
  41. package/dist/proxy/proxy-server.d.ts.map +1 -1
  42. package/dist/proxy/proxy-server.js +201 -37
  43. package/dist/proxy/proxy-server.js.map +1 -1
  44. package/dist/utils/circuit-breaker.d.ts +29 -0
  45. package/dist/utils/circuit-breaker.d.ts.map +1 -0
  46. package/dist/utils/circuit-breaker.js +81 -0
  47. package/dist/utils/circuit-breaker.js.map +1 -0
  48. package/dist/utils/structured-logger.d.ts +1 -1
  49. package/dist/utils/structured-logger.d.ts.map +1 -1
  50. package/package.json +6 -1
package/README.md CHANGED
@@ -22,6 +22,7 @@ MCP Guardian scans your [Model Context Protocol](https://modelcontextprotocol.io
22
22
  - [One-Off Scan](#one-off-scan)
23
23
  - [CLI Reference](#cli-reference)
24
24
  - [`mcp-guardian proxy`](#mcp-guardian-proxy)
25
+ - [Policy Engine (v0.4+)](#policy-engine-v04)
25
26
  - [`mcp-guardian scan`](#mcp-guardian-scan)
26
27
  - [`mcp-guardian audit`](#mcp-guardian-audit)
27
28
  - [`mcp-guardian health`](#mcp-guardian-health)
@@ -30,6 +31,7 @@ MCP Guardian scans your [Model Context Protocol](https://modelcontextprotocol.io
30
31
  - [Available Tools](#available-tools)
31
32
  - [Available Resources & Prompts](#available-resources--prompts)
32
33
  - [CI/CD Integration](#cicd-integration)
34
+ - [Production Deployment (K8s + Helm)](#production-deployment-k8s--helm)
33
35
  - [Docker](#docker)
34
36
  - [Architecture](#architecture)
35
37
  - [Data Flow (Proxy → DB → Audit)](#data-flow-proxy--db--audit)
@@ -38,6 +40,7 @@ MCP Guardian scans your [Model Context Protocol](https://modelcontextprotocol.io
38
40
  - [Pricing Models](#pricing-models)
39
41
  - [Environment Variables](#environment-variables)
40
42
  - [Development](#development)
43
+ - [SECURITY.md](#securitymd)
41
44
  - [FAQ](#faq)
42
45
  - [Roadmap](#roadmap)
43
46
  - [License](#license)
@@ -50,10 +53,17 @@ As MCP adoption grows, so does the attack surface. MCP servers run arbitrary com
50
53
 
51
54
  MCP Guardian provides:
52
55
 
56
+ - **Active policy enforcement (v0.4+)** — YAML-configurable policy engine that blocks, flags, or passes every `tools/call` in real time based on tool allowlists/denylists, regex patterns, rate limits, and token budgets
53
57
  - **Security auditing** — CVE scanning (OSV.dev + NVD), hardcoded secret detection, typo-squatting detection, command injection detection, and TLS validation
54
58
  - **Real cost tracking** — Proxy interceptor that captures actual `tools/call` traffic and counts tokens via `tiktoken` (o200k_base encoding) — no estimates, no mocks
55
59
  - **Health monitoring** — Live JSON-RPC 2.0 handshake probes with latency, success rate, tool count, and context pressure analysis
56
60
  - **Agent-native** — Runs as an MCP server so your AI assistant can self-audit its own infrastructure
61
+ - **Enterprise SIEM logging (v0.4+)** — Structured JSON logs via pino with request-ID tracing, policy decision audit trails, and block events at WARN level
62
+ - **Session-based replay protection (v0.6.0)** — Short-lived 5-min session tokens prevent JWT replay attacks. Nonce tracking detects token reuse
63
+ - **Hot-reload policies (v0.6.0)** — File watcher atomically swaps policy engine on YAML changes — no restart needed
64
+ - **Circuit breaker (v0.5.2)** — 3-state circuit breaker protects upstream MCP servers from cascading failures
65
+ - **OAuth 2.1 / OIDC (v0.5.0)** — JWT validation with OIDC Discovery, bearer token extraction, agent identity mapping
66
+ - **RBAC (v0.5.1)** — Scope-based and client-ID-based access control in policy engine
57
67
 
58
68
  ---
59
69
 
@@ -69,6 +79,7 @@ MCP Guardian provides:
69
79
  | **Typo-Squat Detection** | Levenshtein distance matching against 24 known official MCP packages |
70
80
  | **Secret Scanning** | 6 regex patterns for hardcoded API keys, tokens, private keys, passwords, GitHub tokens, OpenAI keys |
71
81
  | **Command Validation** | Flags dangerous patterns (path traversal, shell chaining, `rm -rf`, `curl`/`wget` in commands, and more) |
82
+ | **🔴 Active Policy Engine (v0.4+)** | YAML-configurable rules: tool allowlist/denylist, regex pattern blocking, rate limiting, token budgets. Operates in `audit` (passive), `warn` (flag only), or `block` (active enforcement) modes |
72
83
  | **Scoring** | Weighted 0–100 security score with actionable recommendations |
73
84
 
74
85
  ### 💰 Cost Audit (`audit_costs`)
@@ -99,7 +110,7 @@ MCP Guardian provides:
99
110
  - **Graceful Shutdown** — SIGINT/SIGTERM handlers flush DB and close connections
100
111
  - **Batched DB Writes** — 1s debounced flush reduces I/O by 10x
101
112
  - **Alert Thresholds** — 6 CLI flags with exit codes 1/2 for CI/CD integration
102
- - **GitHub Actions CI** — Node 18/20/22 matrix, 63 tests across 10 suites
113
+ - **GitHub Actions CI** — Node 18/20/22 matrix, 74 tests across 11 suites
103
114
 
104
115
  ---
105
116
 
@@ -192,15 +203,64 @@ mcp-guardian report --format markdown --output audit-report.md
192
203
 
193
204
  ### `mcp-guardian proxy`
194
205
 
195
- Start the MCP proxy interceptor to capture real token usage data. The proxy spawns all stdio MCP servers from config, then bridges stdin/stdout.
206
+ Start the MCP proxy interceptor with optional active policy enforcement.
196
207
 
197
208
  ```bash
209
+ # Audit-only (passive)
198
210
  mcp-guardian proxy --config ./cline_mcp_settings.json
211
+
212
+ # Active blocking with default policy
213
+ mcp-guardian proxy --config ./cline_mcp_settings.json --policy ./default-policy.yaml
214
+
215
+ # Active blocking with custom policy + mode override
216
+ mcp-guardian proxy --config ./cline_mcp_settings.json --policy ./my-policy.yaml --blocking-mode block
199
217
  ```
200
218
 
201
219
  | Option | Description |
202
220
  |---|---|
203
221
  | `-c, --config <path>` | Path to MCP config file |
222
+ | `--policy <path>` | Path to policy YAML file (enables active blocking) |
223
+ | `--blocking-mode <mode>` | Override policy mode: `audit` (passive), `warn` (flag), `block` (enforce) |
224
+
225
+ ### Policy Engine (v0.4+)
226
+
227
+ The policy engine evaluates every intercepted `tools/call` before it reaches the MCP server. Define rules in YAML:
228
+
229
+ ```yaml
230
+ # my-policy.yaml
231
+ version: "1.0"
232
+ policy:
233
+ mode: block
234
+ rules:
235
+ - name: "deny-shell-tools"
236
+ action: block
237
+ tools: { deny: ["execute_command", "bash", "sh", "eval", "exec"] }
238
+ - name: "block-injection"
239
+ action: block
240
+ patterns:
241
+ - "rm\\s+-rf"
242
+ - "curl\\s|wget\\s"
243
+ - ";\\s*\\w"
244
+ - "&&|\\|\\|"
245
+ - name: "rate-limit"
246
+ action: flag
247
+ maxCallsPerMinute: 60
248
+ - name: "token-budget"
249
+ action: flag
250
+ maxTokens: 50000
251
+ ```
252
+
253
+ **Blocked calls** return a JSON-RPC 2.0 error to the client:
254
+ ```json
255
+ {"jsonrpc":"2.0","id":"abc-123","error":{"code":-32001,"message":"Blocked by MCP Guardian policy: Tool 'execute_command' is explicitly denied"}}
256
+ ```
257
+
258
+ **Policy modes:**
259
+ | Mode | Behavior |
260
+ |---|---|
261
+ | `audit` | Pass all calls; log decisions only (passive) |
262
+ | `warn` | Downgrade `block` actions to `flag`; log warnings |
263
+ | `block` | Full active enforcement — blocked calls never reach the MCP server |
204
264
 
205
265
  ### `mcp-guardian scan`
206
266
 
@@ -349,6 +409,41 @@ Run MCP Guardian in CI to catch issues before deployment:
349
409
 
350
410
  ---
351
411
 
412
+ ## Production Deployment (K8s + Helm)
413
+
414
+ See the full guide at **[deploy/PRODUCTION.md](deploy/PRODUCTION.md)**.
415
+
416
+ ### Quick Helm Install
417
+
418
+ ```bash
419
+ # Install from local chart
420
+ helm install mcp-guardian ./deploy/helm/mcp-guardian \
421
+ --set config.policy.mode=block \
422
+ --set config.mcpConfigPath=/etc/mcp-guardian/cline_mcp_settings.json
423
+
424
+ # Or from the repo (future)
425
+ helm repo add mcp-guardian https://rudraneel93.github.io/mcp-guardian
426
+ helm install mcp-guardian mcp-guardian/mcp-guardian
427
+ ```
428
+
429
+ ### Key Features
430
+ - **Helm chart** with ConfigMap-backed policies, PVC persistence, and safe defaults
431
+ - **Fail-closed** by default (block traffic if proxy crashes) — configurable to fail-open
432
+ - **Sidecar injection pattern** documented for stdio MCP servers
433
+ - **Scaling guide** with CPU/memory recommendations per traffic level
434
+ - **Pod Disruption Budget** for HA, anti-affinity for multi-AZ
435
+ - **SIEM integration** via pino structured JSON logs (Splunk, Datadog, Elasticsearch)
436
+
437
+ ### Performance Overhead
438
+
439
+ | Scenario | p50 | p99 | Overhead |
440
+ |----------|-----|-----|----------|
441
+ | Direct MCP (no proxy) | 5ms | 7ms | — |
442
+ | Proxy (no policy) | 27ms | 77ms | +25.78ms |
443
+ | Proxy (blocking policy) | 27ms | 74ms | +25.93ms |
444
+
445
+ Policy engine adds **~0.15ms** — negligible. The ~26ms is Node.js child process stdio overhead.
446
+
352
447
  ## Docker
353
448
 
354
449
  A Docker image is available for running the proxy in containerized environments.
@@ -414,7 +509,7 @@ mcp-guardian/
414
509
  │ ├── scoring.ts # Shared scoring utility
415
510
  │ └── logger.ts # Colored console logger with log levels
416
511
 
417
- tests/ # 63 tests across 10 suites (Vitest)
512
+ tests/ # 74 tests across 11 suites (Vitest)
418
513
  ├── config-parser.test.ts
419
514
  ├── secret-scanner.test.ts
420
515
  ├── auth-prober.test.ts
@@ -541,7 +636,7 @@ npm install
541
636
  npm run dev # Watch mode with tsx
542
637
  npm run build # Compile TypeScript
543
638
  npm run lint # Type check (tsc --noEmit)
544
- npm test # 63 tests across 10 suites (Vitest)
639
+ npm test # 74 tests across 11 suites (Vitest)
545
640
  npm run test:watch # Watch mode
546
641
 
547
642
  # Contributing
@@ -617,13 +712,26 @@ Token counting uses `tiktoken` with the `o200k_base` encoding (used by GPT-4o an
617
712
  - [x] Token-bucket rate limiter (OSV + NVD)
618
713
  - [x] TLS certificate validation
619
714
  - [x] Command injection validation (10 suspicious patterns)
620
- - [x] 63 unit tests (10 test suites)
715
+ - [x] Active policy engine YAML-based pass/block/flag with allowlists, regex, rate limiting, token budgets
716
+ - [x] Structured JSON logging (pino) for SIEM ingestion
717
+ - [x] STRIDE threat model (SECURITY.md)
718
+ - [x] 74 tests (11 suites)
621
719
  - [x] GitHub Actions CI (Node 18/20/22 matrix)
720
+ - [x] Performance benchmarks (p50: 5ms baseline, +25.78ms proxy overhead, +0.15ms policy)
721
+ - [x] Helm chart + production deployment guide (K8s, fail-open/closed, sidecar pattern, scaling)
622
722
  - [x] Published to npm as [`@mcp-guardian/server`](https://www.npmjs.com/package/@mcp-guardian/server)
723
+ - [x] OAuth 2.1 / OIDC proxy authentication (v0.5.0)
724
+ - [x] RBAC — scope & client-ID-based access control (v0.5.1)
725
+ - [x] Circuit breaker — 3-state protection for upstream servers (v0.5.2)
726
+ - [x] Per‑client rate limiting (v0.5.2)
727
+ - [x] Consistent SIEM fields — requestId, authnSuccess, authzAllowed (v0.5.2)
728
+ - [x] Session binding — replay protection via 5‑min session tokens (v0.6.0)
729
+ - [x] Hot‑reload policies — chokidar file watcher (v0.6.0)
730
+ - [ ] OPA integration for Rego policies
623
731
  - [ ] Web dashboard for historical trends
624
- - [ ] Slack/Discord alerting integration
625
- - [ ] Custom CVE feed support
626
- - [ ] Multi-user proxy mode
732
+ - [ ] Slack/Discord alerting
733
+ - [ ] Prometheus metrics endpoint
734
+ - [ ] Multi-user proxy
627
735
 
628
736
  ---
629
737
 
@@ -0,0 +1,40 @@
1
+ /**
2
+ * OAuth 2.1 / OIDC authentication types for MCP Guardian proxy.
3
+ */
4
+ export interface AuthConfig {
5
+ /** OIDC issuer URL (e.g., https://accounts.google.com) */
6
+ issuer: string;
7
+ /** Expected audience claim in JWT */
8
+ audience: string;
9
+ /** Whether authentication is required (fail-closed) or optional (fail-open) */
10
+ required: boolean;
11
+ /** JWKS URI override (default: auto-discovered from issuer) */
12
+ jwksUri?: string;
13
+ /** Clock tolerance in seconds for JWT validation */
14
+ clockTolerance?: number;
15
+ }
16
+ export interface AgentIdentity {
17
+ /** Subject claim (sub) — unique agent identifier */
18
+ sub: string;
19
+ /** Client ID from the token */
20
+ clientId?: string;
21
+ /** Scopes granted to this agent */
22
+ scopes?: string[];
23
+ /** Issuer of the token */
24
+ issuer: string;
25
+ /** Token expiry timestamp */
26
+ expiresAt?: number;
27
+ }
28
+ export interface AuthValidationResult {
29
+ valid: boolean;
30
+ identity?: AgentIdentity;
31
+ error?: string;
32
+ }
33
+ export interface OIDCDiscovery {
34
+ issuer: string;
35
+ jwks_uri: string;
36
+ authorization_endpoint?: string;
37
+ token_endpoint?: string;
38
+ scopes_supported?: string[];
39
+ }
40
+ //# sourceMappingURL=auth-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-types.d.ts","sourceRoot":"","sources":["../../src/auth/auth-types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,UAAU;IACzB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,QAAQ,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * OAuth 2.1 / OIDC authentication types for MCP Guardian proxy.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=auth-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-types.js","sourceRoot":"","sources":["../../src/auth/auth-types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,25 @@
1
+ import { AuthConfig, AuthValidationResult, OIDCDiscovery } from './auth-types.js';
2
+ export declare class OAuthValidator {
3
+ private config;
4
+ private jwks;
5
+ private cachedDiscovery;
6
+ constructor(config: AuthConfig);
7
+ /**
8
+ * Perform OIDC discovery to fetch JWKS URI from issuer.
9
+ */
10
+ discover(): Promise<OIDCDiscovery>;
11
+ /**
12
+ * Initialize JWKS from discovery or explicit URI.
13
+ */
14
+ init(): Promise<void>;
15
+ /**
16
+ * Validate a JWT bearer token and extract agent identity.
17
+ */
18
+ validate(token: string): Promise<AuthValidationResult>;
19
+ /**
20
+ * Extract Bearer token from Authorization header.
21
+ */
22
+ static extractToken(authorizationHeader?: string): string | null;
23
+ getConfig(): AuthConfig;
24
+ }
25
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAiB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGjG,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,IAAI,CAA2D;IACvE,OAAO,CAAC,eAAe,CAA8B;gBAEzC,MAAM,EAAE,UAAU;IAI9B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC;IAiBxC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAkC5D;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAMhE,SAAS,IAAI,UAAU;CAGxB"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * OAuth 2.1 / OIDC JWT Validator for MCP Guardian proxy.
3
+ *
4
+ * Validates bearer tokens from MCP requests against an OIDC provider.
5
+ * Uses OIDC Discovery (RFC 8414) to auto-configure JWKS endpoint.
6
+ * Supports Client Credentials flow (most common for server-to-agent MCP).
7
+ */
8
+ import * as jose from 'jose';
9
+ import { StructuredLogger } from '../utils/structured-logger.js';
10
+ export class OAuthValidator {
11
+ config;
12
+ jwks = null;
13
+ cachedDiscovery = null;
14
+ constructor(config) {
15
+ this.config = config;
16
+ }
17
+ /**
18
+ * Perform OIDC discovery to fetch JWKS URI from issuer.
19
+ */
20
+ async discover() {
21
+ if (this.cachedDiscovery)
22
+ return this.cachedDiscovery;
23
+ const discoveryUrl = `${this.config.issuer}/.well-known/openid-configuration`;
24
+ try {
25
+ const res = await fetch(discoveryUrl);
26
+ if (!res.ok)
27
+ throw new Error(`OIDC discovery failed: HTTP ${res.status}`);
28
+ const meta = (await res.json());
29
+ this.cachedDiscovery = meta;
30
+ StructuredLogger.info({ event: 'oidc_discovery', issuer: this.config.issuer, jwks_uri: meta.jwks_uri });
31
+ return meta;
32
+ }
33
+ catch (err) {
34
+ StructuredLogger.logError({ event: 'oidc_discovery_error', serverName: 'oauth', error: `Failed to discover OIDC config: ${err?.message}` });
35
+ throw err;
36
+ }
37
+ }
38
+ /**
39
+ * Initialize JWKS from discovery or explicit URI.
40
+ */
41
+ async init() {
42
+ let jwksUri = this.config.jwksUri;
43
+ if (!jwksUri) {
44
+ const discovery = await this.discover();
45
+ jwksUri = discovery.jwks_uri;
46
+ }
47
+ this.jwks = jose.createRemoteJWKSet(new URL(jwksUri));
48
+ }
49
+ /**
50
+ * Validate a JWT bearer token and extract agent identity.
51
+ */
52
+ async validate(token) {
53
+ if (!this.jwks) {
54
+ try {
55
+ await this.init();
56
+ }
57
+ catch (err) {
58
+ return { valid: false, error: `Auth provider unreachable: ${err?.message}` };
59
+ }
60
+ }
61
+ if (!this.jwks) {
62
+ return { valid: false, error: 'JWKS not initialized' };
63
+ }
64
+ try {
65
+ const { payload } = await jose.jwtVerify(token, this.jwks, {
66
+ issuer: this.config.issuer,
67
+ audience: this.config.audience,
68
+ clockTolerance: this.config.clockTolerance || 30,
69
+ });
70
+ const identity = {
71
+ sub: payload.sub || 'unknown',
72
+ clientId: payload.client_id || payload.azp,
73
+ scopes: payload.scope ? String(payload.scope).split(' ') : undefined,
74
+ issuer: payload.iss || this.config.issuer,
75
+ expiresAt: payload.exp ? payload.exp * 1000 : undefined,
76
+ };
77
+ return { valid: true, identity };
78
+ }
79
+ catch (err) {
80
+ return { valid: false, error: `JWT validation failed: ${err?.message}` };
81
+ }
82
+ }
83
+ /**
84
+ * Extract Bearer token from Authorization header.
85
+ */
86
+ static extractToken(authorizationHeader) {
87
+ if (!authorizationHeader)
88
+ return null;
89
+ const match = authorizationHeader.match(/^Bearer\s+(.+)$/i);
90
+ return match ? match[1] : null;
91
+ }
92
+ getConfig() {
93
+ return this.config;
94
+ }
95
+ }
96
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { AgentIdentity } from './auth-types.js';
2
+ /**
3
+ * Session cache for replay protection.
4
+ * After a JWT is validated once, a short-lived session token is issued.
5
+ * Subsequent calls must include this session token, not the raw JWT.
6
+ * This prevents replay of captured JWTs within their expiry window.
7
+ *
8
+ * In production, replace with Redis for multi-replica HA.
9
+ */
10
+ export interface SessionEntry {
11
+ token: string;
12
+ identity: AgentIdentity;
13
+ nonce: string;
14
+ createdAt: number;
15
+ expiresAt: number;
16
+ }
17
+ export declare class SessionCache {
18
+ private sessions;
19
+ private usedNonces;
20
+ private readonly sessionTtlMs;
21
+ private readonly nonceTtlMs;
22
+ constructor(sessionTtlMs?: number, nonceTtlMs?: number);
23
+ /**
24
+ * Create a session after successful JWT validation.
25
+ * Returns a session token the client must use for subsequent calls.
26
+ * The JWT cannot be replayed because:
27
+ * 1. We track used nonces (jti or sub+iat)
28
+ * 2. We issue a session token with a short (5min) TTL
29
+ */
30
+ createSession(identity: AgentIdentity, jwtNonce?: string): SessionEntry;
31
+ /**
32
+ * Validate a session token.
33
+ * Returns the agent identity if valid, null if expired/not found.
34
+ */
35
+ validateSession(token: string): AgentIdentity | null;
36
+ /**
37
+ * Check if a JWT nonce has been used (replay detection).
38
+ */
39
+ isNonceUsed(nonce: string): boolean;
40
+ /**
41
+ * Revoke a session (e.g., on logout or suspicious activity).
42
+ */
43
+ revokeSession(token: string): void;
44
+ private cleanup;
45
+ get size(): number;
46
+ }
47
+ //# sourceMappingURL=session-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-cache.d.ts","sourceRoot":"","sources":["../../src/auth/session-cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD;;;;;;;GAOG;AAEH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,YAAY,GAAE,MAAsB,EAAE,UAAU,GAAE,MAAuB;IAOrF;;;;;;OAMG;IACH,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY;IAuBvE;;;OAGG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAUpD;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAInC;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIlC,OAAO,CAAC,OAAO;IAqBf,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -0,0 +1,91 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { Logger } from '../utils/logger.js';
3
+ export class SessionCache {
4
+ sessions = new Map();
5
+ usedNonces = new Set();
6
+ sessionTtlMs;
7
+ nonceTtlMs;
8
+ constructor(sessionTtlMs = 5 * 60 * 1000, nonceTtlMs = 10 * 60 * 1000) {
9
+ this.sessionTtlMs = sessionTtlMs;
10
+ this.nonceTtlMs = nonceTtlMs;
11
+ // Cleanup expired entries every 60s
12
+ setInterval(() => this.cleanup(), 60000);
13
+ }
14
+ /**
15
+ * Create a session after successful JWT validation.
16
+ * Returns a session token the client must use for subsequent calls.
17
+ * The JWT cannot be replayed because:
18
+ * 1. We track used nonces (jti or sub+iat)
19
+ * 2. We issue a session token with a short (5min) TTL
20
+ */
21
+ createSession(identity, jwtNonce) {
22
+ const nonce = jwtNonce || `${identity.sub}:${Date.now()}:${randomUUID()}`;
23
+ // Prevent nonce replay
24
+ if (this.usedNonces.has(nonce)) {
25
+ Logger.warn(`[session-cache] Replay detected: nonce ${nonce}`);
26
+ }
27
+ this.usedNonces.add(nonce);
28
+ const token = `mcp_guardian_session_${randomUUID()}`;
29
+ const now = Date.now();
30
+ const entry = {
31
+ token,
32
+ identity,
33
+ nonce,
34
+ createdAt: now,
35
+ expiresAt: now + this.sessionTtlMs,
36
+ };
37
+ this.sessions.set(token, entry);
38
+ return entry;
39
+ }
40
+ /**
41
+ * Validate a session token.
42
+ * Returns the agent identity if valid, null if expired/not found.
43
+ */
44
+ validateSession(token) {
45
+ const entry = this.sessions.get(token);
46
+ if (!entry)
47
+ return null;
48
+ if (Date.now() > entry.expiresAt) {
49
+ this.sessions.delete(token);
50
+ return null;
51
+ }
52
+ return entry.identity;
53
+ }
54
+ /**
55
+ * Check if a JWT nonce has been used (replay detection).
56
+ */
57
+ isNonceUsed(nonce) {
58
+ return this.usedNonces.has(nonce);
59
+ }
60
+ /**
61
+ * Revoke a session (e.g., on logout or suspicious activity).
62
+ */
63
+ revokeSession(token) {
64
+ this.sessions.delete(token);
65
+ }
66
+ cleanup() {
67
+ const now = Date.now();
68
+ // Clean expired sessions
69
+ for (const [token, entry] of this.sessions) {
70
+ if (now > entry.expiresAt) {
71
+ this.sessions.delete(token);
72
+ }
73
+ }
74
+ // Clean expired nonces (keep for nonceTtlMs to detect replays)
75
+ // This is simplified — in production, use a time-sorted structure
76
+ if (this.usedNonces.size > 10000) {
77
+ // Full sweep
78
+ const entries = Array.from(this.sessions.values());
79
+ const validTokens = new Set(entries.map(e => e.token));
80
+ for (const token of this.sessions.keys()) {
81
+ if (!validTokens.has(token))
82
+ this.sessions.delete(token);
83
+ }
84
+ this.usedNonces.clear();
85
+ }
86
+ }
87
+ get size() {
88
+ return this.sessions.size;
89
+ }
90
+ }
91
+ //# sourceMappingURL=session-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-cache.js","sourceRoot":"","sources":["../../src/auth/session-cache.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { ReportGenerator } from './reporter/report-generator.js';
7
7
  import { calculateOverallScore } from './utils/scoring.js';
8
8
  import { ProxyManager } from './proxy/proxy-manager.js';
9
9
  import { PolicyEngine } from './policy/policy-engine.js';
10
+ import { OAuthValidator } from './auth/oauth.js';
10
11
  import { createContainer } from './container.js';
11
12
  // ── Shared helpers ────────────────────────────────────────────────────
12
13
  function loadConfigs(options) {
@@ -40,7 +41,7 @@ const program = new Command();
40
41
  program
41
42
  .name('mcp-guardian')
42
43
  .description('Security, cost, and health audit for MCP infrastructure')
43
- .version('0.4.0');
44
+ .version('0.6.0');
44
45
  program
45
46
  .command('scan')
46
47
  .description('Run security scan on MCP servers')
@@ -184,10 +185,13 @@ program
184
185
  });
185
186
  program
186
187
  .command('proxy')
187
- .description('Start MCP Guardian proxy to capture real token usage data with active policy enforcement')
188
+ .description('Start MCP Guardian proxy with optional OAuth 2.1 authentication and active policy enforcement')
188
189
  .option('-c, --config <path>', 'Path to MCP config file')
189
190
  .option('--policy <path>', 'Path to policy YAML file (enables active blocking)')
190
191
  .option('--blocking-mode <mode>', 'Override policy mode: audit (passive), warn (flag), block (enforce)', 'block')
192
+ .option('--auth-issuer <url>', 'OIDC issuer URL for JWT validation (e.g., https://accounts.google.com)')
193
+ .option('--auth-audience <aud>', 'Expected audience claim in JWT')
194
+ .option('--auth-required', 'Require authentication for all tool calls (fail-closed)', false)
191
195
  .action(async (opts) => {
192
196
  const paths = opts.config ? [opts.config] : ConfigParser.findConfigPaths();
193
197
  if (paths.length === 0) {
@@ -199,6 +203,21 @@ program
199
203
  console.error(chalk.yellow('No servers found in config.'));
200
204
  process.exit(0);
201
205
  }
206
+ // Configure OAuth 2.1 if --auth-issuer provided
207
+ let authValidator;
208
+ if (opts.authIssuer) {
209
+ if (!opts.authAudience) {
210
+ console.error(chalk.red('--auth-audience is required when --auth-issuer is set'));
211
+ process.exit(1);
212
+ }
213
+ const authConfig = {
214
+ issuer: opts.authIssuer,
215
+ audience: opts.authAudience,
216
+ required: opts.authRequired || false,
217
+ };
218
+ authValidator = new OAuthValidator(authConfig);
219
+ console.error(chalk.green(`OAuth 2.1 enabled: ${authConfig.issuer} (audience: ${authConfig.audience})${authConfig.required ? ' [REQUIRED]' : ' [OPTIONAL]'}`));
220
+ }
202
221
  // Load policy config if --policy flag provided
203
222
  let policyEngine;
204
223
  if (opts.policy) {
@@ -207,7 +226,6 @@ program
207
226
  const { load } = await import('js-yaml');
208
227
  const policyYaml = readFileSync(opts.policy, 'utf-8');
209
228
  const policyConfig = load(policyYaml);
210
- // Override mode from CLI flag if specified
211
229
  if (opts.blockingMode && ['audit', 'warn', 'block'].includes(opts.blockingMode)) {
212
230
  policyConfig.policy.mode = opts.blockingMode;
213
231
  }
@@ -221,10 +239,10 @@ program
221
239
  }
222
240
  }
223
241
  else {
224
- console.error(chalk.dim('No policy file specified — running in audit-only mode (no blocking)'));
242
+ console.error(chalk.dim('No policy file specified — running in audit-only mode'));
225
243
  }
226
244
  const db = new HistoryDatabase();
227
- const manager = new ProxyManager(db, policyEngine);
245
+ const manager = new ProxyManager(db, policyEngine, authValidator);
228
246
  await manager.startAll(servers);
229
247
  console.error(chalk.green('MCP Guardian proxy running. Press Ctrl+C to stop.'));
230
248
  const cleanup = () => { manager.stopAll(); db.close(); process.exit(0); };