fix(mcp,editor): close PUT-route URL bypass + vision-tool SSRF (Phase 10 A2)
Pre-push audit agent A2 flagged two HIGH-severity security issues that would have shipped in the PR had we not checked: 1. PUT /api/scenes/[id] URL-validation bypass. Phase 8 P4 fixed the POST /api/scenes route by replacing a loose z.unknown() graph schema with AnyNode superRefine. The fix never made it to the PUT handler - an attacker could resubmit the same javascript:/file:/// payloads via PUT. Fixed by extracting the tight validator into apps/editor/lib/graph-schema.ts and sharing it across both routes. 2. SSRF in photo_to_scene / analyze_floorplan_image / analyze_room_photo. All three tools called raw fetch(image) on user-supplied URLs with no validation - a direct http://169.254.169.254/latest/meta-data/ exfil primitive on any cloud host. Added packages/mcp/src/lib/safe-fetch.ts that: - Blocks loopback (127.0.0.0/8, ::1) - Blocks link-local incl. cloud metadata (169.254.0.0/16) - Blocks private ranges (10/8, 172.16/12, 192.168/16, fc00::/7) - Blocks .local/.internal/.corp hostnames + localhost variants - Blocks v4-mapped IPv6 loopback (::ffff:127.0.0.1) - Manual redirects (max 3), revalidating the allowlist per hop - 20 MB response-size cap (streamed, enforced per-chunk) - 10s timeout - Optional PASCAL_ALLOWED_ASSET_ORIGINS env allowlist Tests: 8 new SSRF guard tests, all vision tests still pass, full suite 302/302. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
08e7b6db71
commit
8757de0c36
@@ -101,17 +101,11 @@ type ImageBlock = {
|
||||
*/
|
||||
async function resolveImageBlock(image: string): Promise<ImageBlock> {
|
||||
if (/^https?:\/\//i.test(image)) {
|
||||
const res = await fetch(image)
|
||||
if (!res.ok) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
`failed to fetch image: ${res.status} ${res.statusText}`,
|
||||
{ url: image, status: res.status },
|
||||
)
|
||||
}
|
||||
const buf = Buffer.from(await res.arrayBuffer())
|
||||
const data = buf.toString('base64')
|
||||
const mimeType = res.headers.get('content-type') ?? 'image/jpeg'
|
||||
// SSRF-safe fetch (see packages/mcp/src/lib/safe-fetch.ts).
|
||||
const { safeFetch } = await import('../../lib/safe-fetch')
|
||||
const res = await safeFetch(image, { accept: 'image/*' })
|
||||
const data = res.buffer.toString('base64')
|
||||
const mimeType = res.contentType ?? 'image/jpeg'
|
||||
return { type: 'image', data, mimeType }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user