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:
parent
219b4b1605
commit
4d9ba1ea0e
2 changed files with 7 additions and 40 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
Loading…
Reference in a new issue