fix(server): return 404 for missing well-known metadata (#8622)

* fix(server): return 404 for unserved well-known paths

The daemon web UI middleware fell back to the SPA's index.html for every
unknown GET, including /.well-known/* URIs. MCP clients that run OAuth
discovery (RFC 9728 protected resource metadata, RFC 8414 authorization
server metadata) received a 200 text/html body, treated it as malformed
metadata and retried instead of concluding the server has no OAuth.

Well-known paths now serve a real file from the dist directory if one
exists and otherwise return a JSON 404. Other paths keep the SPA fallback.

* fix(server): verify metadata routing through the daemon

Retain the contributed well-known routing fix with named resolver options and a full daemon regression that fails when metadata requests receive app HTML.

Co-authored-by: HomicideZero <219062565+HomicideZero@users.noreply.github.com>

---------

Co-authored-by: HomicideZero <219062565+HomicideZero@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
paseo-bot[bot]
2026-11-03 07:53:13 +00:00
committed by GitHub
co-authored by HomicideZero
parent e546497a21
commit b507fc1986
3 changed files with 68 additions and 2 deletions
@@ -84,6 +84,23 @@ describe("daemon web UI bootstrap", () => {
}
});
test("returns 404 for missing metadata while serving web UI deep links", async () => {
const distDir = await createWebUiDist();
daemonHandle = await createTestPaseoDaemon({
mcpEnabled: true,
webUi: { enabled: true, distDir },
});
const origin = `http://127.0.0.1:${daemonHandle.port}`;
const metadata = await fetch(`${origin}/.well-known/oauth-protected-resource/mcp/agents`);
expect(metadata.status).toBe(404);
expect(metadata.headers.get("content-type")).toBe("application/json; charset=utf-8");
expect(await metadata.json()).toEqual({ error: "Not found" });
const deepLink = await fetch(`${origin}/h/example/agent/123`);
expect(deepLink.status).toBe(200);
expect(await deepLink.text()).toContain("<body>app</body>");
});
test("advertises ComputerName as the hostname over HTTP and WebSocket", async () => {
daemonHandle = await createTestPaseoDaemon({ mcpEnabled: false });
const response = await fetch(`http://127.0.0.1:${daemonHandle.port}/api/status`);
+27
View File
@@ -187,6 +187,33 @@ describe("daemon web UI route module", () => {
expect(res.body).toContain("app");
});
test.each([
"/.well-known/oauth-protected-resource",
"/.well-known/oauth-protected-resource/mcp/agents",
"/.well-known/oauth-authorization-server",
"/.well-known/openid-configuration",
])("returns a JSON 404 instead of the SPA for missing %s", async (requestPath) => {
const app = createApp({ enabled: true, distDir, publicDir });
const res = await request(app, "GET", requestPath);
expect(res.status).toBe(404);
expect(res.headers["content-type"]).toBe("application/json; charset=utf-8");
expect(JSON.parse(res.body)).toEqual({ error: "Not found" });
});
test("serves well-known files that exist in the dist directory", async () => {
await mkdir(path.join(distDir, ".well-known"), { recursive: true });
await writeFile(path.join(distDir, ".well-known", "security.json"), '{"ok":true}');
const app = createApp({ enabled: true, distDir, publicDir });
const res = await request(app, "GET", "/.well-known/security.json");
expect(res.status).toBe(200);
expect(res.headers["content-type"]).toBe("application/json; charset=utf-8");
expect(res.body).toBe('{"ok":true}');
});
test("serves static assets", async () => {
const app = createApp({ enabled: true, distDir, publicDir });
+24 -2
View File
@@ -15,6 +15,13 @@ function isExcludedPath(requestPath: string): boolean {
return EXCLUDED_PATHS.has(requestPath);
}
// RFC 8615 well-known URIs are machine-read metadata. Clients such as MCP OAuth
// discovery (RFC 9728, RFC 8414) need a 404 for documents the daemon does not
// serve; the SPA's index.html with a 200 reads as malformed metadata.
function isWellKnownPath(requestPath: string): boolean {
return requestPath === "/.well-known" || requestPath.startsWith("/.well-known/");
}
const CONTENT_TYPES: Record<string, string> = {
".html": "text/html; charset=utf-8",
".js": "application/javascript; charset=utf-8",
@@ -71,7 +78,14 @@ interface ResolvedTarget {
isIndexHtml: boolean;
}
function resolveTargetFile(distDir: string, requestPath: string): ResolvedTarget | null {
interface ResolveTargetFileOptions {
distDir: string;
requestPath: string;
spaFallback: boolean;
}
function resolveTargetFile(options: ResolveTargetFileOptions): ResolvedTarget | null {
const { distDir, requestPath, spaFallback } = options;
const safePath = path.normalize(requestPath).replace(/^(\.\.[/\\])+/, "");
let filePath = path.join(distDir, safePath);
@@ -82,6 +96,9 @@ function resolveTargetFile(distDir: string, requestPath: string): ResolvedTarget
const finalStat = safeStat(filePath);
if (!finalStat?.isFile()) {
if (!spaFallback) {
return null;
}
filePath = path.join(distDir, "index.html");
const fallbackStat = safeStat(filePath);
if (!fallbackStat?.isFile()) {
@@ -190,8 +207,13 @@ interface ServeWebUiFileOptions {
function serveWebUiFile(options: ServeWebUiFileOptions): void {
const { distDir, requestPath, label, req, res } = options;
const target = resolveTargetFile(distDir, requestPath);
const wellKnown = isWellKnownPath(requestPath);
const target = resolveTargetFile({ distDir, requestPath, spaFallback: !wellKnown });
if (!target) {
if (wellKnown) {
res.status(404).json({ error: "Not found" });
return;
}
res.status(404).end();
return;
}