Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The control you need depends on where the request originates. Browser JavaScript is constrained by the same-origin policy and can share responses only when a server permits it with CORS. A server endpoint that fetches a URL supplied by a browser user is a different security boundary: the server, not the browser, makes the outbound connection. CORS does not firewall that connection. Protect browser state changes with CSRF controls, and protect server egress with strict destination, redirect, privilege and network controls.

Start by locating the request boundary

An origin is the combination of scheme, host and port. For example, https://app.example and https://api.example are different origins even though they share a registrable domain; http://app.example is also different because the scheme changed. The same-origin policy limits what a script can read or manipulate across those boundaries.

Flow What is being protected Primary enforcement point Typical bypass or failure
Browser script requests another origin Reading a response or using a cross-origin resource Browser plus response headers from the target server Overly broad CORS origins, credential mistakes, or assuming a blocked read stopped the request
Browser submits an authenticated state change Integrity of the user’s account action Application server, using CSRF validation and request context Simple cross-origin requests can still be sent; cookies may authenticate them
Server fetches a caller-supplied URL Internal network and local-resource reachability Server URL handling, egress firewall and runtime privileges Redirects, alternate schemes, DNS tricks or broad network access

Trace the arrows before choosing a header. In a browser-to-API call, the browser decides whether JavaScript receives the response. In a URL-preview, image-import, webhook-check or screenshot endpoint, your server resolves and connects to the destination. A browser’s CORS policy is not applied to that server-to-server hop.

What same-origin policy and CORS actually do

Same-origin policy limits script access

The browser normally allows a script to read freely only from its own origin. It can still display many cross-origin resources, such as images or stylesheets, under separate rules. The policy is about script-mediated access; it is not a universal outbound packet filter.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

CORS grants narrowly defined read access

Cross-Origin Resource Sharing (CORS) is a server opt-in. The target response can include an Access-Control-Allow-Origin value naming the requesting origin, allowing browser JavaScript to read that response. A server-to-server client does not need CORS headers, because no browser is enforcing them on that hop.

A “simple” cross-origin request can be sent even when the initiating script cannot read the response. HTML forms have historically been able to submit such requests, so treating CORS as “prevention of cross-origin requests” is unsafe. A state-changing endpoint must defend itself even if its response is unreadable.

Credential modes change the risk

Fetch defaults to credentials: "same-origin". omit sends no credentials, same-origin sends them only to the same origin, and include allows credentials on cross-origin requests when the browser and server policy permit it. Credentialed CORS responses require an explicit allowed origin; * cannot be combined with credentials. Sending cookies cross-origin increases CSRF exposure, so enable it only for a defined client and endpoint.

mode: "no-cors" is not a workaround. It produces an opaque response that JavaScript cannot inspect and restricts methods and headers. It does not grant readable data and does not turn an unsafe server endpoint into a safe one.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

CSRF: protect authenticated state changes separately

Cross-site request forgery (CSRF) occurs when an attacker causes a victim’s browser to send an authenticated request to your application. Use an unpredictable, server-validated CSRF token for cookie-authenticated state changes. Validate it on every unsafe method and bind it to the user’s session or a server-side secret as appropriate.

Rank #2
Sale
The Web Application Hacker's Handbook: Finding and Exploiting Security Flaws
  • Comes with secure packaging
  • It can be a gift item
  • Easy to read text
  • Do not use GET for actions that create, delete, transfer or otherwise change state.
  • Use a synchronizer token or a signed double-submit token and reject missing, malformed or mismatched values.
  • Set session cookies with an intentional SameSite value. SameSite is defense in depth, not a complete replacement for a token.
  • For APIs, consider an authorization header with a token that a cross-site form cannot set, while still applying normal authentication and authorization checks.

Use Fetch Metadata as request context

Modern browsers can send Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest and related Fetch Metadata headers. Sec-Fetch-Site distinguishes relationships such as same-origin, same-site, cross-site and none. A policy might allow same-origin requests, permit selected navigations, and reject cross-site requests to sensitive endpoints. Log unknown or absent values and preserve legitimate integrations; these headers supplement, rather than replace, CSRF tokens.

Example browser call

const response = await fetch("https://api.example.test/profile", {
  method: "POST",
  credentials: "include",
  headers: {
    "Content-Type": "application/json",
    "X-CSRF-Token": csrfToken
  },
  body: JSON.stringify({ displayName })
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);

The server should verify the session, token, origin policy and any Fetch Metadata decision before changing data. A permissive CORS response alone is not authorization.

Browser resource isolation is not network isolation

Cross-Origin Resource Policy (CORP) can prevent a cross-origin no-cors response body from being exposed to a document, although the request itself still occurs. It is useful for protecting resources from unintended embedding or reading.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Cross-origin isolation is a document-level state exposed by window.crossOriginIsolated. It matters for capabilities such as SharedArrayBuffer and for reducing certain side channels, normally through coordinated isolation headers. Neither CORP nor cross-origin isolation restricts what a backend process can connect to. Keep browser resource policy and server egress policy as separate controls.

How SSRF reaches internal services

Server Side Request Forgery (SSRF) happens when an attacker influences a request that your server makes. A preview endpoint accepting url=https://site.example may be able to reach loopback services, private-address hosts, cloud metadata endpoints or administrative interfaces that the attacker cannot access directly.

  1. The attacker submits a destination to an import, preview, webhook verification or image-fetch endpoint.
  2. The application parses the value and opens a connection with the server’s network identity.
  3. The destination may be an internal address, a non-HTTP scheme, or a public URL that redirects internally.
  4. Even if the response body is discarded, status codes, timing, response size or request volume can reveal information or cause load.

DNS resolution, IPv4/IPv6 differences, encoded hostnames, user-info syntax and redirect chains can make naive string checks unreliable. A URL that looks public at validation time can resolve differently at connection time, so validation must be paired with restricted network reach.

Layered SSRF protection checklist

1. Prefer fixed destinations

If the product can call one known service, remove the URL parameter and use a configured endpoint. Otherwise maintain a narrow allow-list of hosts and, where possible, expected paths. This is stronger than trying to blacklist every private address.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

2. Parse structurally and allow only required schemes

Use a standards-compliant URL parser, normalize the hostname, reject user-info components and permit only schemes the feature needs. For an ordinary web fetch, HTTPS is generally sufficient, as MDN notes. Reject loopback, link-local, private and reserved address ranges after resolving the hostname, for both IPv4 and IPv6.

const ALLOWED_HOSTS = new Set(["images.example.com"]);

function validateDestination(raw) {
  const u = new URL(raw);
  if (u.protocol !== "https:") throw new Error("HTTPS required");
  if (u.username || u.password) throw new Error("User info is not allowed");
  if (!ALLOWED_HOSTS.has(u.hostname.toLowerCase())) {
    throw new Error("Destination is not allow-listed");
  }
  return u;
}

async function fetchSafely(raw) {
  const first = validateDestination(raw);
  const res = await fetch(first, { redirect: "manual", signal: AbortSignal.timeout(8000) });
  if (res.status >= 300 && res.status < 400) {
    const location = res.headers.get("location");
    if (!location) throw new Error("Redirect without location");
    const next = new URL(location, first);
    validateDestination(next.href); // apply the same checks to every hop
    return fetch(next, { redirect: "error", signal: AbortSignal.timeout(8000) });
  }
  return res;
}

This example demonstrates the shape of the checks, not a complete IP-range implementation. In production, resolve the host with your network library, reject disallowed address ranges, and protect against DNS rebinding by ensuring the connection uses an approved result.

3. Control redirects

Disable automatic redirects when possible. If redirects are a product requirement, impose a small hop limit and validate every Location target with the same scheme, host and address rules. Never validate only the initial URL.

4. Reduce privileges and network reach

Run the fetcher as a low-privilege component in a separate network segment. Permit egress only to required destinations and ports; block metadata, control-plane and internal service ranges at the network layer. Do not colocate an unrestricted fetch worker with sensitive administration services.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

5. Bound response handling

Set connection and total timeouts, cap redirects, limit response bytes, restrict content types, and stream or discard data safely. Avoid interpreting fetched content as executable code. Enforce concurrency and rate limits so an attacker cannot turn the endpoint into a load generator.

6. Log and monitor

Record the requested URL, normalized host, resolved address, redirect hops, outcome, duration and bytes, while removing credentials and sensitive query values. Alert on private-address attempts, unusual schemes, repeated failures and spikes in destinations. Return a generic error to callers rather than leaking internal response details.

Testing and troubleshooting

  • “CORS error” in the browser: verify the exact scheme, host and port in Access-Control-Allow-Origin; check preflight method and headers; do not add a wildcard when credentials are included.
  • The request changed data despite a CORS failure: add and validate a CSRF token, reject unsafe cross-site requests with Fetch Metadata where appropriate, and remove state-changing GET routes.
  • Public URLs work but redirects expose private hosts: turn off automatic redirects or validate each hop and enforce egress deny rules.
  • Hostname checks pass but private access remains possible: inspect DNS resolution, IPv6 handling, numeric or encoded addresses and connection-time results; enforce IP-range policy at the firewall and runtime.
  • Legitimate integrations break after Fetch Metadata rules: review Sec-Fetch-Site, Mode and Dest values for that flow, then create a narrowly scoped exception with an independent authentication control.
  • Requests hang or consume memory: add connect/read/overall timeouts, byte limits, concurrency limits and bounded redirect counts.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If the specific job is generating a clean screenshot of a URL rather than implementing your own browser automation, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status.

One GET request returns PNG, JPEG, WebP or PDF. The API supports full-page and element captures, device and viewport settings, dark mode, retina scale, custom CSS and JavaScript, waits, request blocking, headers, cookies, user agents, timezone and geolocation, transparent backgrounds, resizing, cache TTLs, signed links, asynchronous webhooks, bulk capture and a usage API. An MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo documentation for parameters and response headers. Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account.

FAQ

Does a server need CORS headers to fetch a URL?

No. CORS is enforced by browsers for script access. A server-side HTTP client can connect without CORS headers, so SSRF controls remain necessary.

Should I return the fetched response body to users?

Only when the feature requires it. Discarding the body reduces exposure but does not eliminate SSRF; timing, status and load effects can still matter.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is an allow-list always required?

A fixed destination or narrow allow-list is the safest design. When arbitrary public URLs are unavoidable, combine strict parsing, scheme and address checks, redirect validation, isolated egress and monitoring.

Frequently Asked Questions

Does a server need CORS headers to fetch a URL?

No. CORS is a browser enforcement mechanism; server-side clients can connect without it, so SSRF controls are still required.

Can disabling redirects alone stop SSRF?

It removes one attack path but does not address direct private addresses, alternate schemes or broad network access. Use the full layered checklist.

What does Sec-Fetch-Site tell my server?

It describes the relationship between the request's initiator and target, such as same-origin, same-site, cross-site or none. Use it as context alongside CSRF authentication and authorization controls.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.