Skip to content

Commit bdd69c2

Browse files
committed
Fix path traversal, Dockerfile build, and per-session transport
- Validate resolved path stays within SKILLS_DIR to prevent traversal - Remove --prod from Dockerfile install so tsc is available for build - Remove pnpm-lock.yaml from .gitignore so Dockerfile --frozen-lockfile works - Create per-session transport instances instead of sharing one globally - Add sync warning comment on forked renderer CSS
1 parent 17058d1 commit bdd69c2

File tree

5 files changed

+32
-7
lines changed

5 files changed

+32
-7
lines changed

apps/mcp/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,3 @@ yarn-error.log*
99
.DS_Store
1010
.turbo/
1111
.venv/
12-
pnpm-lock.yaml

apps/mcp/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ COPY package.json pnpm-lock.yaml ./
1313
RUN npm install -g pnpm@9
1414

1515
# Install dependencies
16-
RUN pnpm install --prod --frozen-lockfile
16+
RUN pnpm install --frozen-lockfile
1717

1818
# Copy source
1919
COPY tsconfig.json ./

apps/mcp/src/index.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const PORT = Number(process.env.MCP_PORT) || 3100;
88
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(",") || ["*"];
99

1010
const server = createMcpServer();
11-
const transport = new WebStandardStreamableHTTPServerTransport();
11+
const sessions = new Map<string, WebStandardStreamableHTTPServerTransport>();
1212

1313
const app = new Hono();
1414

@@ -31,8 +31,29 @@ app.use(
3131
);
3232

3333
app.get("/health", (c) => c.json({ status: "ok" }));
34-
app.all("/mcp", (c) => transport.handleRequest(c.req.raw));
3534

36-
await server.connect(transport);
35+
app.all("/mcp", async (c) => {
36+
const sessionId = c.req.header("mcp-session-id");
37+
38+
if (sessionId && sessions.has(sessionId)) {
39+
return sessions.get(sessionId)!.handleRequest(c.req.raw);
40+
}
41+
42+
const transport = new WebStandardStreamableHTTPServerTransport();
43+
await server.connect(transport);
44+
45+
const response = await transport.handleRequest(c.req.raw);
46+
47+
const newSessionId = response.headers.get("mcp-session-id");
48+
if (newSessionId) {
49+
sessions.set(newSessionId, transport);
50+
transport.onclose = () => {
51+
sessions.delete(newSessionId);
52+
};
53+
}
54+
55+
return response;
56+
});
57+
3758
serve({ fetch: app.fetch, port: PORT });
38-
console.error(`MCP server running on http://localhost:${PORT}/mcp`);
59+
console.log(`MCP server running on http://localhost:${PORT}/mcp`);

apps/mcp/src/renderer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// OpenGenerativeUI Design System CSS and Bridge JS
22
// Forked from apps/app/src/components/generative-ui/widget-renderer.tsx
3+
// WARNING: Keep in sync with the source widget-renderer.tsx when the design system changes.
34

45
const THEME_CSS = `
56
:root {

apps/mcp/src/skills.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,9 @@ export function listSkills(): string[] {
1414
}
1515

1616
export function loadSkill(name: string): string {
17-
return readFileSync(resolve(SKILLS_DIR, `${name}.txt`), "utf-8");
17+
const resolved = resolve(SKILLS_DIR, `${name}.txt`);
18+
if (!resolved.startsWith(resolve(SKILLS_DIR) + "/")) {
19+
throw new Error(`Invalid skill name: ${name}`);
20+
}
21+
return readFileSync(resolved, "utf-8");
1822
}

0 commit comments

Comments
 (0)