Skip to content

[Bugs] mcp, editor: harden image URL fetching and login redirect - #1122

Merged
Aymericr merged 2 commits into
mainfrom
cursor/ssrf-and-login-redirect-fixes-b7ab
Oct 21, 2026
Merged

Aymericr merged 2 commits into
mainfrom
cursor/ssrf-and-login-redirect-fixes-b7ab

Conversation

@Aymericr

@Aymericr Aymericr commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

Two security hardening fixes.

mcp: safeFetch (used by analyze_floorplan_image, analyze_room_photo, photo_to_scene)

  • Resolves the host of every hop (initial URL and each redirect) with dns.lookup({ all: true }) and requires every resolved address to be public.
  • Classifies addresses after normalising all IPv4/IPv6 text forms, judging IPv6 forms that embed an IPv4 address by the embedded address. The blocked set now also covers CGNAT 100.64.0.0/10, NAT64 64:ff9b::/96, and a few other special-use ranges.
  • Connects only to the addresses that were checked, through a pinned socket lookup. Requests go through node:http/node:https instead of global fetch, which works the same under Node and Bun.
  • Logs a [pascal-mcp] safe_fetch_blocked {...} line to stderr whenever a fetch is refused by policy (scheme, host/address, or origin allowlist).
  • The network boundary (resolve, request) is an optional third argument so tests can cover redirects and DNS changes without real network access. Callers are unchanged.

apps/editor: post-login next redirect

  • safeNextPath moved to lib/safe-next-path.ts. It now parses next against the app's public origin, requires the same origin, and returns the normalised path, falling back to / otherwise.

How to test

  1. bun test packages/mcp/src/lib/safe-fetch.test.ts: covers the address classifier for every range and encoding, DNS answers containing any private address, rechecks on redirects, connection pinning (including one real socket test), size caps, and the block log.
  2. cd apps/editor && bun test lib/safe-next-path.test.ts: same-origin paths are kept and anything else falls back to /.
  3. bun run ci passes locally.

Screenshots / screen recording

N/A, non-visual change.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch
Open in Web Open in Cursor 

Note

High Risk
Rewrites server-side URL fetching (SSRF/DNS-rebind) and post-login redirect validation—both are classic high-impact attack surfaces if wrong.

Reviewed by Cursor Bugbot for commit 9425501. Bugbot is set up for automated code reviews on this repo. Configure here.

@Aymericr Aymericr added the bot:bugs label Oct 21, 2026 — with Cursor
@Aymericr
Aymericr marked this pull request as ready for review October 21, 2026 23:28
@Aymericr
Aymericr merged commit 4b4994b into main Oct 21, 2026
4 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T23:31:22.129204Z 9425501 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9425501d56

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const location = res.headers.get('location')
const status = res.statusCode ?? 0
if (status >= 300 && status < 400) {
res.resume()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Destroy redirect and error responses instead of draining them

When an attacker-controlled endpoint returns a redirect or non-2xx response with an unbounded body, res.resume() drains that body without applying maxBytes; after the next hop succeeds or the error is thrown, the finally block clears the timeout while the prior response can continue streaming indefinitely. Repeated image-tool calls can therefore leave persistent sockets consuming bandwidth and resources despite the advertised size and timeout limits. Destroy these responses, or drain them under the same cap and abort lifecycle, rather than resuming them unchecked.

Useful? React with 👍 / 👎.

details: Record<string, unknown>,
log: Record<string, unknown> = {},
): never {
console.error(`[pascal-mcp] safe_fetch_blocked ${JSON.stringify({ reason, ...details, ...log })}`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Redact URLs before writing blocked-fetch logs

Blocked requests currently serialize details.url verbatim, including URL userinfo and query parameters. Image inputs commonly use presigned URLs whose query contains credentials or access tokens, so an allowlist rejection, redirect to a blocked host, or newly-private DNS answer can persist those secrets in server logs visible beyond the MCP caller. Log only a sanitized origin/hostname and path, or explicitly strip credentials and the query string.

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9425501. Configure here.

if (a === 0) return true // current-network
if (a >= 224) return true // multicast / reserved
if (a === 198 && (b === 18 || b === 19)) return true // 198.18.0.0/15 benchmarking
if (a! >= 224) return true // multicast, reserved, broadcast

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IPv4 /24 check blocks a /16

Medium Severity

isBlockedV4 treats any 192.0.*.* address as blocked. The comment describes IETF 192.0.0.0/24, but the check only compares the first two octets, so it matches 192.0.0.0/16. Public hosts in that /16 (including ICANN/IANA space such as 192.0.43.8) are refused by safeFetch, so vision and photo_to_scene image URLs on those addresses fail.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9425501. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants