Skip to content

Commit 38c30a4

Browse files
committed
Fix security vulnerabilities, package-lock.json, misc other changes
- Add rate limiting to prevent DoS attacks on vulnerable routes - Update package-lock.json to use public npm registry - Document intentionally permissive CORS for public test server - Remove health endpoints to match original codebase (no health checks) - Fix auth server splash page to list all OAuth endpoints - Update test scripts to check server availability via splash page
1 parent bc3619c commit 38c30a4

File tree

6 files changed

+818
-791
lines changed

6 files changed

+818
-791
lines changed

package-lock.json

Lines changed: 762 additions & 757 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/test-e2e-external.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ AUTH_MODE=auth_server PORT=3001 BASE_URI=$AUTH_SERVER node dist/index.js &
4444
AUTH_PID=$!
4545
sleep 5
4646

47-
# Check auth server
48-
if ! curl -s -f "$AUTH_SERVER/health" > /dev/null; then
47+
# Check auth server by accessing splash page
48+
if ! curl -s -f "$AUTH_SERVER/" > /dev/null 2>&1; then
4949
echo "❌ Auth server failed to start at $AUTH_SERVER"
5050
kill $AUTH_PID 2>/dev/null || true
5151
exit 1
@@ -58,8 +58,8 @@ AUTH_MODE=external AUTH_SERVER_URL=$AUTH_SERVER PORT=8080 BASE_URI=$MCP_SERVER n
5858
MCP_PID=$!
5959
sleep 5
6060

61-
# Check MCP server
62-
if ! curl -s -f "$MCP_SERVER/health" > /dev/null; then
61+
# Check MCP server by accessing splash page
62+
if ! curl -s -f "$MCP_SERVER/" > /dev/null 2>&1; then
6363
echo "❌ MCP server failed to start at $MCP_SERVER"
6464
kill $AUTH_PID 2>/dev/null || true
6565
kill $MCP_PID 2>/dev/null || true

scripts/test-e2e-internal.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ AUTH_MODE=internal PORT=8080 BASE_URI=$SERVER_URL node dist/index.js &
4141
SERVER_PID=$!
4242
sleep 5
4343

44-
# Check server health
45-
if ! curl -s -f "$SERVER_URL/health" > /dev/null; then
44+
# Check server is running by accessing splash page
45+
if ! curl -s -f "$SERVER_URL/" > /dev/null 2>&1; then
4646
echo "❌ Server failed to start at $SERVER_URL"
4747
kill $SERVER_PID 2>/dev/null || true
4848
exit 1

src/index.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import cors from 'cors';
1515
import fs from 'fs';
1616
import path from 'path';
1717
import { fileURLToPath } from 'url';
18+
import rateLimit from 'express-rate-limit';
1819
import { config } from './config.js';
1920
import { AuthModule } from './modules/auth/index.js';
2021
import { MCPModule } from './modules/mcp/index.js';
@@ -38,6 +39,8 @@ async function main() {
3839
const app = express();
3940

4041
// Basic middleware
42+
// Intentionally permissive CORS for public MCP reference server
43+
// This allows any MCP client to test against this reference implementation
4144
app.use(cors({ origin: true, credentials: true }));
4245
app.use(express.json());
4346
app.use(express.urlencoded({ extended: false }));
@@ -125,6 +128,7 @@ async function main() {
125128
console.log(` Authorize: GET ${config.baseUri}/authorize`);
126129
console.log(` Get Token: POST ${config.baseUri}/token`);
127130
console.log(` Introspect: POST ${config.baseUri}/introspect`);
131+
console.log(` Revoke: POST ${config.baseUri}/revoke`);
128132

129133
} else if (config.auth.mode === 'external') {
130134
// ========================================
@@ -161,12 +165,20 @@ async function main() {
161165
console.log('MCP Endpoints:');
162166
console.log(` Streamable HTTP: ${config.baseUri}/mcp`);
163167
console.log(` SSE (legacy): ${config.baseUri}/sse`);
164-
console.log(` Health Check: ${config.baseUri}/health`);
165168
console.log(` OAuth Metadata: ${config.baseUri}/.well-known/oauth-authorization-server`);
166169
}
167170

171+
// Rate limiter for splash page (moderate limit)
172+
const splashPageLimiter = rateLimit({
173+
windowMs: 60 * 1000, // 1 minute
174+
max: 50, // 50 requests per minute
175+
message: 'Too many requests to splash page',
176+
standardHeaders: true,
177+
legacyHeaders: false,
178+
});
179+
168180
// Splash page (customize based on mode)
169-
app.get('/', (req, res) => {
181+
app.get('/', splashPageLimiter, (req, res) => {
170182
if (config.auth.mode === 'auth_server') {
171183
// Simple splash page for standalone auth server
172184
res.send(`
@@ -199,13 +211,14 @@ async function main() {
199211
<div class="endpoint">GET ${config.baseUri}/authorize - Authorization endpoint</div>
200212
<div class="endpoint">POST ${config.baseUri}/token - Token endpoint</div>
201213
<div class="endpoint">POST ${config.baseUri}/introspect - Token introspection</div>
214+
<div class="endpoint">POST ${config.baseUri}/revoke - Token revocation</div>
202215
</body>
203216
</html>
204217
`);
205218
} else {
206219
const srcStaticDir = path.join(__dirname, 'static');
207220
const splashPath = path.join(srcStaticDir, 'index.html');
208-
let html = fs.readFileSync(splashPath, 'utf8');
221+
const html = fs.readFileSync(splashPath, 'utf8');
209222
res.send(html);
210223
}
211224
});

src/modules/auth/index.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Router } from 'express';
1313
import express from 'express';
1414
import path from 'path';
1515
import { fileURLToPath } from 'url';
16+
import rateLimit from 'express-rate-limit';
1617
import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';
1718
import { FeatureReferenceAuthProvider } from './auth/provider.js';
1819
import { handleMockUpstreamAuthorize, handleMockUpstreamCallback } from './handlers/mock-upstream-idp.js';
@@ -73,6 +74,23 @@ export class AuthModule {
7374
private setupRouter(): Router {
7475
const router = Router();
7576

77+
// Rate limiters for different route types
78+
const authLimiter = rateLimit({
79+
windowMs: 60 * 1000, // 1 minute
80+
max: 20, // 20 requests per minute for auth endpoints
81+
message: 'Too many authentication attempts',
82+
standardHeaders: true,
83+
legacyHeaders: false,
84+
});
85+
86+
const staticAssetLimiter = rateLimit({
87+
windowMs: 60 * 1000, // 1 minute
88+
max: 100, // 100 requests per minute for static assets
89+
message: 'Too many requests for static assets',
90+
standardHeaders: true,
91+
legacyHeaders: false,
92+
});
93+
7694
// OAuth endpoints via SDK's mcpAuthRouter
7795
router.use(mcpAuthRouter({
7896
provider: this.provider,
@@ -108,24 +126,15 @@ export class AuthModule {
108126
});
109127

110128
// Mock upstream IDP endpoints (for demo purposes)
111-
router.get('/mock-upstream-idp/authorize', handleMockUpstreamAuthorize);
112-
router.get('/mock-upstream-idp/callback', handleMockUpstreamCallback);
129+
router.get('/mock-upstream-idp/authorize', authLimiter, handleMockUpstreamAuthorize);
130+
router.get('/mock-upstream-idp/callback', authLimiter, handleMockUpstreamCallback);
113131

114132
// Static assets for auth pages
115-
router.get('/mcp-logo.png', (req, res) => {
133+
router.get('/mcp-logo.png', staticAssetLimiter, (req, res) => {
116134
const logoPath = path.join(__dirname, 'static', 'mcp.png');
117135
res.sendFile(logoPath);
118136
});
119137

120-
// Health check
121-
router.get('/auth/health', (req, res) => {
122-
res.json({
123-
status: 'healthy',
124-
module: 'auth',
125-
mode: 'internal'
126-
});
127-
});
128-
129138
return router;
130139
}
131140
}

src/modules/mcp/index.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Router, Request, Response, NextFunction } from 'express';
1313
import cors from 'cors';
1414
import path from 'path';
1515
import { fileURLToPath } from 'url';
16+
import rateLimit from 'express-rate-limit';
1617
import { BearerAuthMiddlewareOptions, requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
1718
import { getOAuthProtectedResourceMetadataUrl } from '@modelcontextprotocol/sdk/server/auth/router.js';
1819
import { ITokenValidator } from '../../interfaces/auth-validator.js';
@@ -47,7 +48,18 @@ export class MCPModule {
4748
private setupRouter(): Router {
4849
const router = Router();
4950

51+
// Rate limiter for static assets
52+
const staticAssetLimiter = rateLimit({
53+
windowMs: 60 * 1000, // 1 minute
54+
max: 100, // 100 requests per minute for static assets
55+
message: 'Too many requests for static assets',
56+
standardHeaders: true,
57+
legacyHeaders: false,
58+
});
59+
5060
// CORS configuration for MCP endpoints
61+
// Intentionally permissive CORS for public MCP reference server
62+
// This allows any MCP client to test against this reference implementation
5163
const corsOptions = {
5264
origin: true, // Allow any origin
5365
methods: ['GET', 'POST', 'DELETE'],
@@ -88,20 +100,8 @@ export class MCPModule {
88100
router.get('/sse', cors(corsOptions), bearerAuth, sseHeaders, handleSSEConnection);
89101
router.post('/message', cors(corsOptions), bearerAuth, securityHeaders, handleMessage);
90102

91-
// Health check
92-
router.get('/health', (req, res) => {
93-
res.json({
94-
status: 'healthy',
95-
service: 'mcp',
96-
endpoints: {
97-
streamable: '/mcp',
98-
sse: '/sse'
99-
}
100-
});
101-
});
102-
103103
// Static files for MCP
104-
router.get('/styles.css', (req, res) => {
104+
router.get('/styles.css', staticAssetLimiter, (req, res) => {
105105
const cssPath = path.join(__dirname, '../../static', 'styles.css');
106106
res.setHeader('Content-Type', 'text/css');
107107
res.sendFile(cssPath);

0 commit comments

Comments
 (0)