pdf-server: remove domain allowlist, require HTTPS only (#487)

Replace the hardcoded list of allowed remote origins with a simple
HTTPS protocol check. Any HTTPS URL is now accepted for remote PDFs.
Local file allowlisting remains unchanged.
This commit is contained in:
Olivier Chafik 2026-02-23 13:27:15 -08:00 • committed by GitHub
commit 4d9ba1ea0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 7 additions and 40 deletions

View file

@ -19,7 +19,6 @@ import {
pathToFileUrl,
fileUrlToPath,
allowedLocalFiles,
allowedRemoteOrigins,
DEFAULT_PDF,
} from "./server.js";
@ -132,9 +131,6 @@ async function main() {
}
console.error(`[pdf-server] Ready (${urls.length} URL(s) configured)`);
console.error(
`[pdf-server] Allowed origins: ${[...allowedRemoteOrigins].join(", ")}`,
);
if (stdio) {
await startStdioServer(createServer);

View file

@ -2,7 +2,7 @@
* PDF MCP Server
*
* An MCP server that displays PDFs in an interactive viewer.
* Supports local files and remote URLs from academic sources (arxiv, biorxiv, etc).
* Supports local files and remote HTTPS URLs.
*
* Tools:
* - list_pdfs: List available PDFs
@ -44,26 +44,6 @@ export const CACHE_MAX_LIFETIME_MS = 60_000; // 60 seconds
/** Max size for cached PDFs (defensive limit to prevent memory exhaustion) */
export const CACHE_MAX_PDF_SIZE_BYTES = 50 * 1024 * 1024; // 50MB
/** Allowed remote origins (security allowlist) */
export const allowedRemoteOrigins = new Set([
"https://agrirxiv.org",
"https://arxiv.org",
"https://chemrxiv.org",
"https://edarxiv.org",
"https://engrxiv.org",
"https://hal.science",
"https://osf.io",
"https://psyarxiv.com",
"https://ssrn.com",
"https://www.biorxiv.org",
"https://www.eartharxiv.org",
"https://www.medrxiv.org",
"https://www.preprints.org",
"https://www.researchsquare.com",
"https://www.sportarxiv.org",
"https://zenodo.org",
]);
/** Allowed local file paths (populated from CLI args) */
export const allowedLocalFiles = new Set<string>();
@ -134,14 +114,11 @@ export function validateUrl(url: string): { valid: boolean; error?: string } {
return { valid: true };
}
// Remote URL - check against allowed origins
// Remote URL - require HTTPS
try {
const parsed = new URL(url);
const origin = `${parsed.protocol}//${parsed.hostname}`;
if (
![...allowedRemoteOrigins].some((allowed) => origin.startsWith(allowed))
) {
return { valid: false, error: `Origin not allowed: ${origin}` };
if (parsed.protocol !== "https:") {
return { valid: false, error: `Only HTTPS URLs are allowed: ${url}` };
}
return { valid: true };
} catch {
@ -417,7 +394,7 @@ export function createServer(): McpServer {
// Create session-local cache (isolated per server instance)
const { readPdfRange } = createPdfCache();
// Tool: list_pdfs - List available PDFs (local files + allowed origins)
// Tool: list_pdfs - List available PDFs
server.tool(
"list_pdfs",
"List available PDFs that can be displayed",
@ -443,7 +420,7 @@ export function createServer(): McpServer {
);
}
parts.push(
`Remote PDFs from ${[...allowedRemoteOrigins].join(", ")} can also be loaded dynamically.`,
`Any remote PDF accessible via HTTPS can also be loaded dynamically.`,
);
return {
@ -451,7 +428,6 @@ export function createServer(): McpServer {
structuredContent: {
localFiles: pdfs.filter((p) => p.type === "local").map((p) => p.url),
allowedDirectories: [...allowedLocalDirs],
allowedOrigins: [...allowedRemoteOrigins],
},
};
},
@ -531,11 +507,6 @@ export function createServer(): McpServer {
},
);
// Build allowed domains list for tool description (strip https:// and www.)
const allowedDomains = [...allowedRemoteOrigins]
.map((origin) => origin.replace(/^https?:\/\/(www\.)?/, ""))
.join(", ");
// Tool: display_pdf - Show interactive viewer
registerAppTool(
server,
@ -547,7 +518,7 @@ export function createServer(): McpServer {
Accepts:
- Local files explicitly added to the server (use list_pdfs to see available files)
- Local files under directories provided by the client as MCP roots
- Remote PDFs from: ${allowedDomains}`,
- Any remote PDF accessible via HTTPS`,
inputSchema: {
url: z.string().default(DEFAULT_PDF).describe("PDF URL"),
page: z.number().min(1).default(1).describe("Initial page"),