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.
#1 Best Overall
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.
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
- 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
SameSitevalue. 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.
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.
Rank #3
- The attacker submits a destination to an import, preview, webhook verification or image-fetch endpoint.
- The application parses the value and opens a connection with the server’s network identity.
- The destination may be an internal address, a non-HTTP scheme, or a public URL that redirects internally.
- 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.
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.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →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,ModeandDestvalues 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.
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.
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:
Best Value
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.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesIs 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.
Recommended Free Tools
Quick Recap
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.

