Skip to content

Commit 07709c9

Browse files
committed
basic SSRF protection / ZIP_ALLOWED_DOMAINS
1 parent 1910aa1 commit 07709c9

File tree

1 file changed

+16
-5
lines changed

1 file changed

+16
-5
lines changed

mcp-server/src/services/mcp.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -643,25 +643,36 @@ export const createMcpServer = (): McpServerWrapper => {
643643
}
644644

645645
if (name === ToolName.ZIP_RESOURCES) {
646-
const MAX_ZIP_FETCH_SIZE = Number(process.env.MAX_ZIP_FETCH_SIZE ?? String(10 * 1024 * 1024)); // 10 MB default
647-
const MAX_ZIP_FETCH_TIME_MILLIS = Number(process.env.MAX_ZIP_FETCH_TIME_MILLIS ?? String(30 * 1000)); // 30 seconds default.
646+
const ZIP_MAX_FETCH_SIZE = Number(process.env.ZIP_MAX_FETCH_SIZE ?? String(10 * 1024 * 1024)); // 10 MB default
647+
const ZIP_MAX_FETCH_TIME_MILLIS = Number(process.env.ZIP_MAX_FETCH_TIME_MILLIS ?? String(30 * 1000)); // 30 seconds default.
648+
// Comma-separated list of allowed domains. Empty means all domains are allowed.
649+
const ZIP_ALLOWED_DOMAINS = (process.env.ZIP_ALLOWED_DOMAINS ?? "raw.githubusercontent.com").split(",").map(d => d.trim().toLowerCase()).filter(d => d.length > 0);
648650

649651
const { files, outputType } = ZipResourcesInputSchema.parse(args);
650652
const zip = new JSZip();
651653

652-
let remainingUploadBytes = MAX_ZIP_FETCH_SIZE;
653-
const uploadEndTime = Date.now() + MAX_ZIP_FETCH_TIME_MILLIS;
654+
let remainingUploadBytes = ZIP_MAX_FETCH_SIZE;
655+
const uploadEndTime = Date.now() + ZIP_MAX_FETCH_TIME_MILLIS;
654656

655657
for (const [fileName, urlString] of Object.entries(files)) {
656658
try {
657659
if (remainingUploadBytes <= 0) {
658-
throw new Error(`Max upload size of ${MAX_ZIP_FETCH_SIZE} bytes exceeded`);
660+
throw new Error(`Max upload size of ${ZIP_MAX_FETCH_SIZE} bytes exceeded`);
659661
}
660662

661663
const url = new URL(urlString);
662664
if (url.protocol !== 'http:' && url.protocol !== 'https:' && url.protocol !== 'data:') {
663665
throw new Error(`Unsupported URL protocol for ${urlString}. Only http, https, and data URLs are supported.`);
664666
}
667+
if (ZIP_ALLOWED_DOMAINS.length > 0 && (url.protocol === 'http:' || url.protocol === 'https:')) {
668+
const domain = url.hostname;
669+
const domainAllowed = ZIP_ALLOWED_DOMAINS.some(allowedDomain => {
670+
return domain === allowedDomain || domain.endsWith(`.${allowedDomain}`);
671+
});
672+
if (!domainAllowed) {
673+
throw new Error(`Domain ${domain} is not in the allowed domains list.`);
674+
}
675+
}
665676

666677
const response = await fetchSafely(url, {
667678
maxBytes: remainingUploadBytes,

0 commit comments

Comments
 (0)