Fix: enforce MCP token length too

This commit is contained in:
shamoon
2026-08-07 15:09:57 -07:00
parent 2fae37b004
commit 7904d4adc9
5 changed files with 95 additions and 19 deletions
+12 -1
View File
@@ -12,6 +12,8 @@ const SERVER_INFO = {
version: "1.0.0",
};
const MIN_TOKEN_LENGTH = 32;
const CONFIG_FILES = [
"settings.yaml",
"services.yaml",
@@ -442,9 +444,18 @@ export function mcpEnabled() {
return enabled();
}
export function mcpTokenConfigError() {
if (!enabled()) return null;
const token = requiredToken();
if (token && token.length < MIN_TOKEN_LENGTH) {
return `HOMEPAGE_MCP_TOKEN must be at least ${MIN_TOKEN_LENGTH} characters. Generate one with: openssl rand -base64 32`;
}
return null;
}
export function mcpTokenAuthorized(req) {
const token = requiredToken();
if (!token) return false;
if (!token || token.length < MIN_TOKEN_LENGTH) return false;
const authHeader = req.headers.authorization;
const bearerToken = typeof authHeader === "string" && authHeader.startsWith("Bearer ") ? authHeader.slice(7) : null;
+36 -5
View File
@@ -622,13 +622,44 @@ describe("utils/mcp/homepage-mcp", () => {
expect(mod.mcpTokenAuthorized({ headers: {} })).toBe(false);
process.env.HOMEPAGE_MCP_TOKEN = "secret";
expect(mod.mcpTokenAuthorized({ headers: { authorization: "Bearer secret" } })).toBe(true);
expect(mod.mcpTokenAuthorized({ headers: { "x-homepage-mcp-token": "secret" } })).toBe(true);
const token = "mcp-tok-0123456789abcdefghijklmnopqrstuv"; // 40 chars
process.env.HOMEPAGE_MCP_TOKEN = token;
expect(mod.mcpTokenAuthorized({ headers: { authorization: `Bearer ${token}` } })).toBe(true);
expect(mod.mcpTokenAuthorized({ headers: { "x-homepage-mcp-token": token } })).toBe(true);
expect(mod.mcpTokenAuthorized({ headers: { authorization: "Bearer wrong" } })).toBe(false);
process.env.HOMEPAGE_MCP_TOKEN = "é";
// multibyte token at the minimum length still authorizes and doesn't throw on byte-length mismatch
const multibyteToken = "é".repeat(32);
process.env.HOMEPAGE_MCP_TOKEN = multibyteToken;
expect(mod.mcpTokenAuthorized({ headers: { authorization: "Bearer a" } })).toBe(false);
expect(mod.mcpTokenAuthorized({ headers: { authorization: "Bearer é" } })).toBe(true);
expect(mod.mcpTokenAuthorized({ headers: { authorization: `Bearer ${multibyteToken}` } })).toBe(true);
});
it("never authorizes a token below the minimum length, even when presented verbatim", async () => {
const mod = await loadMcpWithConfigDir(mkdtempSync(path.join(tmpdir(), "homepage-mcp-test-")));
process.env.HOMEPAGE_MCP_TOKEN = "change-me";
expect(mod.mcpTokenAuthorized({ headers: { authorization: "Bearer change-me" } })).toBe(false);
expect(mod.mcpTokenAuthorized({ headers: { "x-homepage-mcp-token": "change-me" } })).toBe(false);
});
it("reports a config error only when MCP is enabled with a too-short token", async () => {
const mod = await loadMcpWithConfigDir(mkdtempSync(path.join(tmpdir(), "homepage-mcp-test-")));
process.env.HOMEPAGE_MCP_ENABLED = "true";
process.env.HOMEPAGE_MCP_TOKEN = "change-me";
expect(mod.mcpTokenConfigError()).toMatch(/at least 32 characters/i);
process.env.HOMEPAGE_MCP_TOKEN = "mcp-tok-0123456789abcdefghijklmnopqrstuv";
expect(mod.mcpTokenConfigError()).toBeNull();
// no token configured is valid (session-only mode), so it is not an error
delete process.env.HOMEPAGE_MCP_TOKEN;
expect(mod.mcpTokenConfigError()).toBeNull();
// a weak token is ignored entirely when MCP is disabled
process.env.HOMEPAGE_MCP_ENABLED = "false";
process.env.HOMEPAGE_MCP_TOKEN = "change-me";
expect(mod.mcpTokenConfigError()).toBeNull();
});
});