@mcp-guardian/server 0.3.0 → 0.5.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 (101) hide show
  1. package/README.md +362 -136
  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/dashboard-auth.d.ts +97 -0
  7. package/dist/auth/dashboard-auth.d.ts.map +1 -0
  8. package/dist/auth/dashboard-auth.js +319 -0
  9. package/dist/auth/dashboard-auth.js.map +1 -0
  10. package/dist/auth/dpop.d.ts +38 -0
  11. package/dist/auth/dpop.d.ts.map +1 -0
  12. package/dist/auth/dpop.js +72 -0
  13. package/dist/auth/dpop.js.map +1 -0
  14. package/dist/auth/oauth.d.ts +25 -0
  15. package/dist/auth/oauth.d.ts.map +1 -0
  16. package/dist/auth/oauth.js +96 -0
  17. package/dist/auth/oauth.js.map +1 -0
  18. package/dist/auth/redis-session-cache.d.ts +21 -0
  19. package/dist/auth/redis-session-cache.d.ts.map +1 -0
  20. package/dist/auth/redis-session-cache.js +74 -0
  21. package/dist/auth/redis-session-cache.js.map +1 -0
  22. package/dist/auth/session-cache.d.ts +47 -0
  23. package/dist/auth/session-cache.d.ts.map +1 -0
  24. package/dist/auth/session-cache.js +91 -0
  25. package/dist/auth/session-cache.js.map +1 -0
  26. package/dist/cli.js +48 -3
  27. package/dist/cli.js.map +1 -1
  28. package/dist/database/database-interface.d.ts +17 -0
  29. package/dist/database/database-interface.d.ts.map +1 -0
  30. package/dist/database/database-interface.js +2 -0
  31. package/dist/database/database-interface.js.map +1 -0
  32. package/dist/database/postgres-db.d.ts +18 -0
  33. package/dist/database/postgres-db.d.ts.map +1 -0
  34. package/dist/database/postgres-db.js +118 -0
  35. package/dist/database/postgres-db.js.map +1 -0
  36. package/dist/index.js +1 -1
  37. package/dist/policy/policy-engine.d.ts +19 -0
  38. package/dist/policy/policy-engine.d.ts.map +1 -0
  39. package/dist/policy/policy-engine.js +87 -0
  40. package/dist/policy/policy-engine.js.map +1 -0
  41. package/dist/policy/policy-types.d.ts +42 -0
  42. package/dist/policy/policy-types.d.ts.map +1 -0
  43. package/dist/policy/policy-types.js +5 -0
  44. package/dist/policy/policy-types.js.map +1 -0
  45. package/dist/policy/policy-watcher.d.ts +24 -0
  46. package/dist/policy/policy-watcher.d.ts.map +1 -0
  47. package/dist/policy/policy-watcher.js +68 -0
  48. package/dist/policy/policy-watcher.js.map +1 -0
  49. package/dist/policy/shell-tokenizer.d.ts +92 -0
  50. package/dist/policy/shell-tokenizer.d.ts.map +1 -0
  51. package/dist/policy/shell-tokenizer.js +300 -0
  52. package/dist/policy/shell-tokenizer.js.map +1 -0
  53. package/dist/proxy/http-proxy-server.d.ts +26 -0
  54. package/dist/proxy/http-proxy-server.d.ts.map +1 -0
  55. package/dist/proxy/http-proxy-server.js +172 -0
  56. package/dist/proxy/http-proxy-server.js.map +1 -0
  57. package/dist/proxy/proxy-manager.d.ts +5 -1
  58. package/dist/proxy/proxy-manager.d.ts.map +1 -1
  59. package/dist/proxy/proxy-manager.js +12 -3
  60. package/dist/proxy/proxy-manager.js.map +1 -1
  61. package/dist/proxy/proxy-server.d.ts +20 -5
  62. package/dist/proxy/proxy-server.d.ts.map +1 -1
  63. package/dist/proxy/proxy-server.js +126 -9
  64. package/dist/proxy/proxy-server.js.map +1 -1
  65. package/dist/utils/circuit-breaker.d.ts +29 -0
  66. package/dist/utils/circuit-breaker.d.ts.map +1 -0
  67. package/dist/utils/circuit-breaker.js +81 -0
  68. package/dist/utils/circuit-breaker.js.map +1 -0
  69. package/dist/utils/dashboard-server.d.ts +19 -0
  70. package/dist/utils/dashboard-server.d.ts.map +1 -0
  71. package/dist/utils/dashboard-server.js +258 -0
  72. package/dist/utils/dashboard-server.js.map +1 -0
  73. package/dist/utils/metrics.d.ts +17 -0
  74. package/dist/utils/metrics.d.ts.map +1 -0
  75. package/dist/utils/metrics.js +79 -0
  76. package/dist/utils/metrics.js.map +1 -0
  77. package/dist/utils/mtls-config.d.ts +27 -0
  78. package/dist/utils/mtls-config.d.ts.map +1 -0
  79. package/dist/utils/mtls-config.js +82 -0
  80. package/dist/utils/mtls-config.js.map +1 -0
  81. package/dist/utils/payload-normalizer.d.ts +62 -0
  82. package/dist/utils/payload-normalizer.d.ts.map +1 -0
  83. package/dist/utils/payload-normalizer.js +240 -0
  84. package/dist/utils/payload-normalizer.js.map +1 -0
  85. package/dist/utils/policy-auditor.d.ts +24 -0
  86. package/dist/utils/policy-auditor.d.ts.map +1 -0
  87. package/dist/utils/policy-auditor.js +58 -0
  88. package/dist/utils/policy-auditor.js.map +1 -0
  89. package/dist/utils/redis-rate-limiter.d.ts +22 -0
  90. package/dist/utils/redis-rate-limiter.d.ts.map +1 -0
  91. package/dist/utils/redis-rate-limiter.js +61 -0
  92. package/dist/utils/redis-rate-limiter.js.map +1 -0
  93. package/dist/utils/structured-logger.d.ts +47 -0
  94. package/dist/utils/structured-logger.d.ts.map +1 -0
  95. package/dist/utils/structured-logger.js +48 -0
  96. package/dist/utils/structured-logger.js.map +1 -0
  97. package/dist/utils/tracing.d.ts +7 -0
  98. package/dist/utils/tracing.d.ts.map +1 -0
  99. package/dist/utils/tracing.js +34 -0
  100. package/dist/utils/tracing.js.map +1 -0
  101. package/package.json +14 -8
@@ -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,97 @@
1
+ export interface AuthResult {
2
+ authenticated: boolean;
3
+ reason?: string;
4
+ identity?: string;
5
+ }
6
+ export interface DashboardAuthConfig {
7
+ /** Enable authentication on dashboard API */
8
+ enabled: boolean;
9
+ /** Pre-shared API key (simplest auth) */
10
+ apiKey?: string;
11
+ /** JWT HMAC secret for session tokens */
12
+ jwtSecret?: string;
13
+ /** Session token expiry in seconds */
14
+ sessionTtlSeconds: number;
15
+ /** Allowed origins for CORS/CSRF validation */
16
+ allowedOrigins: string[];
17
+ /** Maximum login attempts per minute per IP */
18
+ maxLoginAttemptsPerMinute: number;
19
+ }
20
+ /**
21
+ * DashboardAuth provides authentication for the dashboard HTTP server.
22
+ *
23
+ * Two modes:
24
+ * 1. API Key: Set DASHBOARD_API_KEY, pass as ?api_key=<key> or Authorization: Bearer <key>
25
+ * 2. JWT Sessions: Set DASHBOARD_JWT_SECRET, POST /api/login with credentials
26
+ */
27
+ export declare class DashboardAuth {
28
+ private config;
29
+ private loginRateMap;
30
+ private activeTokens;
31
+ private cleanupInterval;
32
+ constructor(config?: Partial<DashboardAuthConfig>);
33
+ /**
34
+ * Authenticate a dashboard HTTP request.
35
+ * Checks multiple sources:
36
+ * 1. ?api_key=<key> query parameter
37
+ * 2. Authorization: Bearer <token> header
38
+ * 3. X-API-Key: <key> header
39
+ */
40
+ authenticate(req: {
41
+ url?: string;
42
+ headers?: Record<string, string | string[] | undefined>;
43
+ method?: string;
44
+ }): AuthResult;
45
+ /**
46
+ * Handle a login attempt. Creates a session token if credentials are valid.
47
+ * Credentials are validated against DASHBOARD_USERNAME / DASHBOARD_PASSWORD env vars.
48
+ */
49
+ login(req: {
50
+ url?: string;
51
+ headers?: Record<string, string | string[] | undefined>;
52
+ body?: {
53
+ username?: string;
54
+ password?: string;
55
+ api_key?: string;
56
+ };
57
+ ip?: string;
58
+ }): {
59
+ success: boolean;
60
+ token?: string;
61
+ error?: string;
62
+ };
63
+ /**
64
+ * Revoke a session token (logout).
65
+ */
66
+ logout(token: string): void;
67
+ /**
68
+ * Generate login page HTML (serves at /login when JWT auth is enabled).
69
+ */
70
+ getLoginPageHtml(error?: string): string;
71
+ /**
72
+ * Check if auth is enabled and required.
73
+ */
74
+ isEnabled(): boolean;
75
+ /**
76
+ * Check if JWT session-based auth is configured (vs API key only).
77
+ */
78
+ hasJwtSessionAuth(): boolean;
79
+ /**
80
+ * Create a signed HMAC session token.
81
+ */
82
+ private createSessionToken;
83
+ /**
84
+ * Timing-safe string comparison to prevent timing attacks on API keys.
85
+ */
86
+ private timingSafeCompare;
87
+ /**
88
+ * Validate CSRF protection via Origin/Referer headers.
89
+ */
90
+ private validateCsrf;
91
+ private isAllowedOrigin;
92
+ private checkLoginRate;
93
+ private normalizeHeaders;
94
+ private cleanupRateMap;
95
+ dispose(): void;
96
+ }
97
+ //# sourceMappingURL=dashboard-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-auth.d.ts","sourceRoot":"","sources":["../../src/auth/dashboard-auth.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,6CAA6C;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+CAA+C;IAC/C,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,+CAA+C;IAC/C,yBAAyB,EAAE,MAAM,CAAC;CACnC;AAUD;;;;;;GAMG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,YAAY,CAA0C;IAC9D,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,eAAe,CAA+C;gBAE1D,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC;IA4BjD;;;;;;OAMG;IACH,YAAY,CAAC,GAAG,EAAE;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;QACxD,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,UAAU;IAyDd;;;OAGG;IACH,KAAK,CAAC,GAAG,EAAE;QACT,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;QACxD,IAAI,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAClE,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAuDxD;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI3B;;OAEG;IACH,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAyCxC;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,iBAAiB,IAAI,OAAO;IAI5B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;OAEG;IACH,OAAO,CAAC,YAAY;IA0BpB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,cAAc;IAOtB,OAAO,IAAI,IAAI;CAKhB"}
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Dashboard Authentication Middleware
3
+ *
4
+ * Provides JWT-based authentication for the dashboard HTTP API.
5
+ * Supports:
6
+ * - API key authentication (simple, internal deployments)
7
+ * - JWT session tokens (for multi-user deployments)
8
+ * - CSRF protection via Origin/Referer validation
9
+ * - Rate limiting on auth endpoints
10
+ * - Login endpoint with configurable credential source
11
+ *
12
+ * Enable with: DASHBOARD_AUTH_ENABLED=true
13
+ * Configure API key: DASHBOARD_API_KEY=<key>
14
+ * Configure JWT secret: DASHBOARD_JWT_SECRET=<secret>
15
+ */
16
+ import { createHmac, randomBytes, timingSafeEqual } from 'crypto';
17
+ import { Logger } from '../utils/logger.js';
18
+ import { StructuredLogger } from '../utils/structured-logger.js';
19
+ /**
20
+ * DashboardAuth provides authentication for the dashboard HTTP server.
21
+ *
22
+ * Two modes:
23
+ * 1. API Key: Set DASHBOARD_API_KEY, pass as ?api_key=<key> or Authorization: Bearer <key>
24
+ * 2. JWT Sessions: Set DASHBOARD_JWT_SECRET, POST /api/login with credentials
25
+ */
26
+ export class DashboardAuth {
27
+ config;
28
+ loginRateMap = new Map();
29
+ activeTokens = new Set();
30
+ cleanupInterval = null;
31
+ constructor(config) {
32
+ const enabled = process.env['DASHBOARD_AUTH_ENABLED'] === 'true';
33
+ this.config = {
34
+ enabled: config?.enabled ?? enabled,
35
+ apiKey: config?.apiKey ?? process.env['DASHBOARD_API_KEY'] ?? undefined,
36
+ jwtSecret: config?.jwtSecret ?? process.env['DASHBOARD_JWT_SECRET'] ?? undefined,
37
+ sessionTtlSeconds: config?.sessionTtlSeconds ?? 3600,
38
+ allowedOrigins: config?.allowedOrigins ?? (process.env['DASHBOARD_ALLOWED_ORIGINS']
39
+ ? process.env['DASHBOARD_ALLOWED_ORIGINS'].split(',').map(s => s.trim())
40
+ : ['http://localhost:4000', 'http://localhost:3000', 'http://127.0.0.1:4000']),
41
+ maxLoginAttemptsPerMinute: config?.maxLoginAttemptsPerMinute ?? 5,
42
+ };
43
+ if (this.config.enabled && this.config.apiKey) {
44
+ Logger.info('[dashboard-auth] API key authentication enabled');
45
+ }
46
+ else if (this.config.enabled && this.config.jwtSecret) {
47
+ Logger.info('[dashboard-auth] JWT session authentication enabled');
48
+ }
49
+ else if (this.config.enabled) {
50
+ Logger.warn('[dashboard-auth] Auth enabled but no API key or JWT secret configured');
51
+ }
52
+ // Periodic cleanup of rate limit entries
53
+ if (this.config.enabled) {
54
+ this.cleanupInterval = setInterval(() => this.cleanupRateMap(), 60000);
55
+ }
56
+ }
57
+ /**
58
+ * Authenticate a dashboard HTTP request.
59
+ * Checks multiple sources:
60
+ * 1. ?api_key=<key> query parameter
61
+ * 2. Authorization: Bearer <token> header
62
+ * 3. X-API-Key: <key> header
63
+ */
64
+ authenticate(req) {
65
+ if (!this.config.enabled) {
66
+ return { authenticated: true, identity: 'anonymous' };
67
+ }
68
+ const url = req.url || '/';
69
+ const headers = this.normalizeHeaders(req.headers || {});
70
+ // ── CSRF check for mutating requests ──
71
+ if (req.method && ['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
72
+ const csrfResult = this.validateCsrf(headers);
73
+ if (!csrfResult.authenticated)
74
+ return csrfResult;
75
+ }
76
+ // ── Check query param API key ──
77
+ try {
78
+ const urlObj = new URL(url, 'http://localhost');
79
+ const queryKey = urlObj.searchParams.get('api_key');
80
+ if (queryKey && this.config.apiKey) {
81
+ if (this.timingSafeCompare(queryKey, this.config.apiKey)) {
82
+ return { authenticated: true, identity: 'api_key' };
83
+ }
84
+ }
85
+ }
86
+ catch {
87
+ // Malformed URL — continue to other auth methods
88
+ }
89
+ // ── Check Authorization header ──
90
+ const authHeader = headers['authorization'];
91
+ if (authHeader) {
92
+ const bearerMatch = authHeader.match(/^Bearer\s+(.+)$/i);
93
+ if (bearerMatch) {
94
+ const token = bearerMatch[1];
95
+ // Check if it's the API key
96
+ if (this.config.apiKey && this.timingSafeCompare(token, this.config.apiKey)) {
97
+ return { authenticated: true, identity: 'api_key' };
98
+ }
99
+ // Check if it's a valid session token
100
+ if (this.activeTokens.has(token)) {
101
+ return { authenticated: true, identity: 'session' };
102
+ }
103
+ }
104
+ }
105
+ // ── Check X-API-Key header ──
106
+ const apiKeyHeader = headers['x-api-key'];
107
+ if (apiKeyHeader && this.config.apiKey) {
108
+ if (this.timingSafeCompare(apiKeyHeader, this.config.apiKey)) {
109
+ return { authenticated: true, identity: 'api_key' };
110
+ }
111
+ }
112
+ return { authenticated: false, reason: 'No valid authentication provided' };
113
+ }
114
+ /**
115
+ * Handle a login attempt. Creates a session token if credentials are valid.
116
+ * Credentials are validated against DASHBOARD_USERNAME / DASHBOARD_PASSWORD env vars.
117
+ */
118
+ login(req) {
119
+ if (!this.config.enabled || !this.config.jwtSecret) {
120
+ return { success: false, error: 'JWT auth not configured. Set DASHBOARD_JWT_SECRET.' };
121
+ }
122
+ // ── Rate limit login attempts ──
123
+ const ip = req.ip || 'unknown';
124
+ if (!this.checkLoginRate(ip)) {
125
+ StructuredLogger.info({
126
+ event: 'dashboard_login_rate_limited',
127
+ ip,
128
+ });
129
+ return { success: false, error: 'Too many login attempts. Try again later.' };
130
+ }
131
+ const body = req.body || {};
132
+ // Check API key shortcut
133
+ if (body.api_key && this.config.apiKey && this.timingSafeCompare(body.api_key, this.config.apiKey)) {
134
+ const token = this.createSessionToken();
135
+ Logger.info(`[dashboard-auth] Login via API key from ${ip}`);
136
+ return { success: true, token };
137
+ }
138
+ // Check username/password
139
+ const expectedUsername = process.env['DASHBOARD_USERNAME'];
140
+ const expectedPassword = process.env['DASHBOARD_PASSWORD'];
141
+ if (!expectedUsername || !expectedPassword) {
142
+ return { success: false, error: 'Login credentials not configured on server. Set DASHBOARD_USERNAME and DASHBOARD_PASSWORD.' };
143
+ }
144
+ if (body.username === expectedUsername &&
145
+ body.password &&
146
+ this.timingSafeCompare(body.password, expectedPassword)) {
147
+ const token = this.createSessionToken();
148
+ StructuredLogger.info({
149
+ event: 'dashboard_login',
150
+ ip,
151
+ identity: body.username,
152
+ });
153
+ return { success: true, token };
154
+ }
155
+ StructuredLogger.info({
156
+ event: 'dashboard_login_failed',
157
+ ip,
158
+ identity: body.username || 'unknown',
159
+ });
160
+ return { success: false, error: 'Invalid credentials' };
161
+ }
162
+ /**
163
+ * Revoke a session token (logout).
164
+ */
165
+ logout(token) {
166
+ this.activeTokens.delete(token);
167
+ }
168
+ /**
169
+ * Generate login page HTML (serves at /login when JWT auth is enabled).
170
+ */
171
+ getLoginPageHtml(error) {
172
+ const errorHtml = error ? `<div style="color:#f85149;margin-bottom:16px;padding:8px;background:#3d1f1f;border-radius:6px;">${error}</div>` : '';
173
+ return `<!DOCTYPE html>
174
+ <html lang="en">
175
+ <head>
176
+ <meta charset="UTF-8">
177
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
178
+ <title>MCP Guardian — Login</title>
179
+ <style>
180
+ * { margin: 0; padding: 0; box-sizing: border-box; }
181
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: #0d1117; color: #c9d1d9; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
182
+ .container { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 32px; width: 100%; max-width: 400px; }
183
+ h1 { font-size: 20px; color: #58a6ff; margin-bottom: 8px; }
184
+ h2 { font-size: 14px; color: #8b949e; margin-bottom: 24px; }
185
+ label { display: block; font-size: 13px; color: #8b949e; margin-bottom: 4px; margin-top: 12px; }
186
+ input { width: 100%; padding: 8px 12px; background: #0d1117; border: 1px solid #30363d; border-radius: 6px; color: #c9d1d9; font-size: 14px; }
187
+ input:focus { outline: none; border-color: #58a6ff; }
188
+ button { width: 100%; padding: 10px; background: #238636; color: #fff; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; margin-top: 20px; }
189
+ button:hover { background: #2ea043; }
190
+ .footer { font-size: 12px; color: #8b949e; margin-top: 16px; text-align: center; }
191
+ </style>
192
+ </head>
193
+ <body>
194
+ <div class="container">
195
+ <h1>🛡️ MCP Guardian</h1>
196
+ <h2>Dashboard Authentication</h2>
197
+ ${errorHtml}
198
+ <form method="POST" action="/api/login">
199
+ <label for="username">Username</label>
200
+ <input type="text" id="username" name="username" required autofocus>
201
+ <label for="password">Password</label>
202
+ <input type="password" id="password" name="password" required>
203
+ <button type="submit">Sign In</button>
204
+ </form>
205
+ <div class="footer">Internal deployment — authorized access only</div>
206
+ </div>
207
+ </body>
208
+ </html>`;
209
+ }
210
+ /**
211
+ * Check if auth is enabled and required.
212
+ */
213
+ isEnabled() {
214
+ return this.config.enabled && !!(this.config.apiKey || this.config.jwtSecret);
215
+ }
216
+ /**
217
+ * Check if JWT session-based auth is configured (vs API key only).
218
+ */
219
+ hasJwtSessionAuth() {
220
+ return this.config.enabled && !!this.config.jwtSecret;
221
+ }
222
+ /**
223
+ * Create a signed HMAC session token.
224
+ */
225
+ createSessionToken() {
226
+ const payload = Buffer.from(JSON.stringify({
227
+ iat: Math.floor(Date.now() / 1000),
228
+ jti: randomBytes(16).toString('hex'),
229
+ })).toString('base64url');
230
+ const signature = createHmac('sha256', this.config.jwtSecret || randomBytes(32).toString('hex'))
231
+ .update(payload)
232
+ .digest('base64url');
233
+ const token = `${payload}.${signature}`;
234
+ this.activeTokens.add(token);
235
+ // Auto-expire after TTL
236
+ setTimeout(() => this.activeTokens.delete(token), this.config.sessionTtlSeconds * 1000);
237
+ return token;
238
+ }
239
+ /**
240
+ * Timing-safe string comparison to prevent timing attacks on API keys.
241
+ */
242
+ timingSafeCompare(a, b) {
243
+ if (a.length !== b.length)
244
+ return false;
245
+ const bufA = Buffer.from(a, 'utf-8');
246
+ const bufB = Buffer.from(b, 'utf-8');
247
+ return timingSafeEqual(bufA, bufB);
248
+ }
249
+ /**
250
+ * Validate CSRF protection via Origin/Referer headers.
251
+ */
252
+ validateCsrf(headers) {
253
+ const origin = headers['origin'];
254
+ const referer = headers['referer'];
255
+ // If both are missing and we're strict, could block
256
+ // For now, only validate when present
257
+ if (origin) {
258
+ if (!this.isAllowedOrigin(origin)) {
259
+ return { authenticated: false, reason: `Origin '${origin}' not allowed` };
260
+ }
261
+ }
262
+ if (referer) {
263
+ try {
264
+ const refererOrigin = new URL(referer).origin;
265
+ if (!this.isAllowedOrigin(refererOrigin)) {
266
+ return { authenticated: false, reason: `Referer origin '${refererOrigin}' not allowed` };
267
+ }
268
+ }
269
+ catch {
270
+ // Malformed referer — allow through
271
+ }
272
+ }
273
+ return { authenticated: true };
274
+ }
275
+ isAllowedOrigin(origin) {
276
+ return this.config.allowedOrigins.some(allowed => {
277
+ if (allowed === '*')
278
+ return true;
279
+ if (allowed === origin)
280
+ return true;
281
+ return false;
282
+ });
283
+ }
284
+ checkLoginRate(ip) {
285
+ const now = Date.now();
286
+ let entry = this.loginRateMap.get(ip);
287
+ if (!entry || now > entry.resetAt) {
288
+ entry = { count: 1, resetAt: now + 60000 };
289
+ this.loginRateMap.set(ip, entry);
290
+ return true;
291
+ }
292
+ entry.count++;
293
+ return entry.count <= this.config.maxLoginAttemptsPerMinute;
294
+ }
295
+ normalizeHeaders(headers) {
296
+ const result = {};
297
+ for (const [key, value] of Object.entries(headers)) {
298
+ if (Array.isArray(value))
299
+ result[key.toLowerCase()] = value[0] || '';
300
+ else if (value !== undefined)
301
+ result[key.toLowerCase()] = value;
302
+ }
303
+ return result;
304
+ }
305
+ cleanupRateMap() {
306
+ const now = Date.now();
307
+ for (const [ip, entry] of this.loginRateMap) {
308
+ if (now > entry.resetAt)
309
+ this.loginRateMap.delete(ip);
310
+ }
311
+ }
312
+ dispose() {
313
+ if (this.cleanupInterval)
314
+ clearInterval(this.cleanupInterval);
315
+ this.activeTokens.clear();
316
+ this.loginRateMap.clear();
317
+ }
318
+ }
319
+ //# sourceMappingURL=dashboard-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-auth.js","sourceRoot":"","sources":["../../src/auth/dashboard-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import * as jose from 'jose';
2
+ /**
3
+ * DPoP (Demonstrating Proof of Possession) — RFC 9449.
4
+ * Validates sender-constrained tokens to prevent token replay.
5
+ * The client must include a DPoP proof JWT in the DPoP header.
6
+ */
7
+ export interface DPoPProof {
8
+ /** The access token hash (ath) claim */
9
+ ath?: string;
10
+ /** The HTTP method of the request */
11
+ htm: string;
12
+ /** The HTTP URI of the request */
13
+ htu: string;
14
+ /** Issued at (Unix timestamp) */
15
+ iat: number;
16
+ /** Unique JWT ID for replay detection */
17
+ jti: string;
18
+ }
19
+ export declare class DPoPValidator {
20
+ private usedNonces;
21
+ private readonly nonceTtlMs;
22
+ private lastCleanup;
23
+ constructor(nonceTtlMs?: number);
24
+ /**
25
+ * Validate a DPoP proof JWT.
26
+ * Checks: signature (JWK), htm, htu, iat freshness (60s window), ath (if access token provided), nonce replay.
27
+ */
28
+ validate(proofToken: string, jwk: jose.JWK, httpMethod: string, httpUri: string, accessToken?: string): Promise<{
29
+ valid: boolean;
30
+ error?: string;
31
+ }>;
32
+ /**
33
+ * Compute the access token hash (ath) as per RFC 9449 §4.2.
34
+ * ath = base64url(sha256(access_token))
35
+ */
36
+ private computeAth;
37
+ }
38
+ //# sourceMappingURL=dpop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dpop.d.ts","sourceRoot":"","sources":["../../src/auth/dpop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAG7B;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,iCAAiC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,WAAW,CAAsB;gBAE7B,UAAU,GAAE,MAAuB;IAI/C;;;OAGG;IACG,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAyDzJ;;;OAGG;YACW,UAAU;CAIzB"}
@@ -0,0 +1,72 @@
1
+ import * as jose from 'jose';
2
+ import { Logger } from '../utils/logger.js';
3
+ export class DPoPValidator {
4
+ usedNonces = new Set();
5
+ nonceTtlMs;
6
+ lastCleanup = Date.now();
7
+ constructor(nonceTtlMs = 10 * 60 * 1000) {
8
+ this.nonceTtlMs = nonceTtlMs;
9
+ }
10
+ /**
11
+ * Validate a DPoP proof JWT.
12
+ * Checks: signature (JWK), htm, htu, iat freshness (60s window), ath (if access token provided), nonce replay.
13
+ */
14
+ async validate(proofToken, jwk, httpMethod, httpUri, accessToken) {
15
+ try {
16
+ // Verify the proof JWT is signed by the client's private key matching the JWK
17
+ const publicKey = await jose.importJWK(jwk, 'ES256');
18
+ const { payload } = await jose.jwtVerify(proofToken, publicKey, {
19
+ algorithms: ['ES256', 'RS256', 'EdDSA'],
20
+ clockTolerance: 10,
21
+ });
22
+ const proof = payload;
23
+ // Validate htm (HTTP method)
24
+ if (proof.htm !== httpMethod.toUpperCase()) {
25
+ return { valid: false, error: `DPoP: htm mismatch (expected ${httpMethod.toUpperCase()}, got ${proof.htm})` };
26
+ }
27
+ // Validate htu (HTTP URI) — must match the request URI
28
+ if (proof.htu !== httpUri) {
29
+ return { valid: false, error: `DPoP: htu mismatch (expected ${httpUri}, got ${proof.htu})` };
30
+ }
31
+ // Validate iat freshness (must be within last 60 seconds)
32
+ const now = Math.floor(Date.now() / 1000);
33
+ if (proof.iat < now - 60) {
34
+ return { valid: false, error: 'DPoP: proof too old (iat > 60s ago)' };
35
+ }
36
+ if (proof.iat > now + 10) {
37
+ return { valid: false, error: 'DPoP: proof from the future' };
38
+ }
39
+ // Validate nonce (jti) for replay detection
40
+ if (this.usedNonces.has(proof.jti)) {
41
+ Logger.warn(`[dpop] Replay detected: jti ${proof.jti}`);
42
+ return { valid: false, error: 'DPoP: nonce already used (replay detected)' };
43
+ }
44
+ this.usedNonces.add(proof.jti);
45
+ // Validate ath (access token hash) if access token provided
46
+ if (accessToken && proof.ath) {
47
+ const expectedAth = await this.computeAth(accessToken);
48
+ if (proof.ath !== expectedAth) {
49
+ return { valid: false, error: 'DPoP: ath mismatch (access token hash does not match)' };
50
+ }
51
+ }
52
+ // Periodic cleanup of old nonces
53
+ if (Date.now() - this.lastCleanup > 60000) {
54
+ this.usedNonces.clear();
55
+ this.lastCleanup = Date.now();
56
+ }
57
+ return { valid: true };
58
+ }
59
+ catch (err) {
60
+ return { valid: false, error: `DPoP validation failed: ${err?.message}` };
61
+ }
62
+ }
63
+ /**
64
+ * Compute the access token hash (ath) as per RFC 9449 §4.2.
65
+ * ath = base64url(sha256(access_token))
66
+ */
67
+ async computeAth(accessToken) {
68
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(accessToken));
69
+ return Buffer.from(digest).toString('base64url');
70
+ }
71
+ }
72
+ //# sourceMappingURL=dpop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dpop.js","sourceRoot":"","sources":["../../src/auth/dpop.ts"],"names":[],"mappings":"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"}
@@ -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"}