Prompt and Applicable Context
Design the secure fetch path for a URL preview API. An authenticated user submits an arbitrary public HTTP or HTTPS URL. A background worker fetches the document and returns only a sanitized page title. The product cannot maintain a fixed domain allowlist because public websites are the intended input.
For this interview, allow only default ports 80 and 443, follow at most three redirects, enforce a three-second total deadline, and read at most 2 MiB after decompression. These numbers are interview assumptions rather than universal security settings. The service must not reach loopback, private, shared, link-local, multicast, reserved, documentation, or cloud-metadata destinations over IPv4 or IPv6. It must also withstand alternate IP notation, mixed DNS answers, DNS rebinding, a public URL that redirects to an internal address, oversized responses, and slow servers.
MITRE defines CWE-918 as a server receiving a URL and retrieving it without sufficiently ensuring that the request reaches the expected destination. That framing is useful: the core problem is outbound authorization. Input validation, DNS behavior, the network route, and the actual socket destination must enforce one policy. A public 2026 web-security interview discussion lists SSRF with XSS, SQL injection, and IDOR as topics candidates should be ready to explain. This supports current interview relevance but does not establish a fixed company question or a frequency claim. OWASP Top 10:2025 maps CWE-918 into Broken Access Control, reinforcing the authorization model.
The existing question bank mentions SSRF as one boundary in a crawler and a URL shortener. This question isolates the fetch path itself: parsing, resolution, destination classification, connection binding, redirects, egress controls, and proof that the policy survives time-of-check/time-of-use races.
What the Interviewer Evaluates
The first signal is whether the candidate models the destination as an authorization decision. A check for the strings localhost and 127.0.0.1 is incomplete. The destination may be an IPv6 literal, an IPv4-mapped IPv6 address, an alternative numeric representation, a hostname with both safe and unsafe answers, a redirect target, or a name whose DNS answer changes between validation and connection.
The second signal is parser discipline. Regex and substring checks do not define URL semantics. A strong answer uses one standards-based parser, rejects parse failures and embedded credentials, allows only explicit HTTP schemes and expected ports, and then makes all policy decisions from the parser's canonical fields. TLS verification still uses the original normalized hostname even when the socket is pinned to a vetted IP address.
The third signal is closing the DNS rebinding gap. Resolving and approving an IP is ineffective when the HTTP library performs a second DNS lookup before opening the socket. The fetcher must connect to a specific approved address, or send the request through a policy-aware egress proxy that does so. Every redirect and retry is a new authorization event.
The fourth signal is defense in depth. An application bug should meet a network boundary that cannot route to internal subnets or metadata endpoints. The worker should have minimal identity privileges, no ambient user cookies or authorization headers, a bounded parser, and no ability to execute page scripts or fetch subresources.
The final signal is falsifiable validation. A candidate should propose tests that exercise alternate address forms, mixed A and AAAA records, re-resolution races, redirects, decompression limits, and egress firewall rules. Saying “use an allowlist” does not solve this prompt because arbitrary public domains are a product requirement; it is appropriate only for a different, partner-only scope.
Questions to Clarify Before Answering
- Are destinations arbitrary public sites or known partners? Known partners permit an exact
scheme-host-port allowlist. Arbitrary previews need a conservative public-address policy and a network-enforced egress boundary.
- Which protocols, ports, methods, and headers are required? This prompt permits only GET over
HTTP on port 80 or HTTPS on port 443. It does not accept caller-selected methods, request bodies, proxies, cookies, authorization headers, or arbitrary headers.
- Are redirects required? They are, up to three hops. The service disables automatic redirects and
independently authorizes every Location target before following it.
- What output is needed? Only a sanitized title. The worker does not return raw HTML, execute
JavaScript, process XML external entities, render images, or fetch stylesheets, fonts, or scripts.
- What address space is reachable from the worker? The answer must cover IPv4, IPv6, container and
service-network routes, corporate networks, and provider metadata endpoints. An inventory is needed before the egress policy can be tested.
- How should cache and retries behave? A cached preview can reduce repeated fetching, but cache
misses still traverse the secure path. This design does not retry automatically. An explicit retry repeats DNS resolution, authorization, and connection pinning from the beginning.
- What availability tradeoff is acceptable? Rejecting a hostname when any returned address is
unsafe can block a misconfigured public site. The prompt chooses security over partial reachability; the rejection is observable and does not silently select another answer.
30-Second Answer Framework
“I would parse with a standards-based URL parser, allow only HTTP on port 80 or HTTPS on port 443, and reject credentials. I would resolve every A and AAAA answer through a trusted resolver, normalize IPv4-mapped IPv6, and reject the destination if any address is outside a maintained public-address policy. The worker connects directly to one approved IP but keeps the hostname for Host, TLS SNI, and certificate verification, closing the DNS-rebinding gap. Automatic redirects and retries stay off; each redirect repeats the full check. An isolated egress firewall blocks internal and metadata routes. I then enforce the three-second, 2 MiB, and three-hop limits, load no subresources, and test rebinding and redirect-to-metadata end to end.”
Step-by-Step Deep Dive
Step 1: Turn the requirement into one destination policy
Represent policy explicitly instead of distributing string checks across the API, worker, and HTTP client:
DestinationPolicy
schemes: http, https
origin_pairs: http:80, https:443
userinfo: forbidden
address_requirement: globally reachable public unicast
redirects: at most 3, authorize every hop
method: GET
caller_headers: none
total_deadline: 3 seconds
decompressed_body_limit: 2 MiBThe API authenticates the caller, applies account and tenant quotas, stores the submitted string as untrusted data, and queues a job. It does not perform a “security check” and pass a trusted flag to a later worker; DNS and routes can change before the job runs. The worker is the component opening the connection, so it makes the authoritative decision immediately before connecting.
For a partner integration, the strongest policy is an exact allowlist of normalized scheme, hostname, and port, optionally with expected paths. This preview product intentionally accepts arbitrary public hosts. Its policy therefore allows only destinations classified as public and globally reachable. Maintain the classifier from authoritative address registries and the organization's own network inventory. A hand-written list containing only RFC 1918 ranges misses loopback, link-local, shared, multicast, reserved, documentation, IPv6 unique-local, IPv4-mapped forms, and provider-specific metadata endpoints.
Step 2: Parse first and make policy decisions from parsed fields
Use the platform's WHATWG-compatible or equivalently well-tested URL parser. Require an absolute URL. Reject parse errors, a username or password component, fragments if the product has no reason to keep them, non-HTTP schemes, and ports outside the explicit policy. Normalize the hostname through the parser, including internationalized names, and never separately reinterpret the raw string with a regex.
Examples such as https://expected.example@evil.example/ show why prefix matching is unsafe: the network host is evil.example. A fragment does not select the network destination, and encodings or alternative numeric forms can create disagreements between a validator and an HTTP client. One parser must supply the scheme, canonical host, effective port, and request path used by every later step.
If the host is an IP literal, canonicalize and classify it immediately. Convert IPv4-mapped IPv6 to its embedded IPv4 value before applying both families' policies. Do not infer safety from punctuation, string length, or whether the host “looks like” a domain.
Step 3: Resolve every address and bind the connection to an approved result
For a hostname, resolve both A and AAAA records with a controlled resolver. Follow the resolver's normal CNAME processing and collect the final addresses. This prompt rejects the destination if any answer is disallowed. Each address passes the same classifier as an IP literal. Record a reason code, not the full query-bearing URL, when rejecting it.
The critical invariant is:
the IP authorized by policy == the IP used by connect()Choose one approved address and give that exact address to the socket layer. Preserve the normalized hostname for the HTTP Host header and, for HTTPS, TLS SNI and certificate hostname verification. A certificate valid for the numeric IP is not a substitute. Disable the HTTP client's independent DNS lookup; otherwise an attacker can return a public address during validation and a private address during connection.
A policy-aware egress proxy can own resolution, classification, and connection pinning instead of each worker. The worker sends the normalized URL and fixed request policy to that proxy, not an attacker-selected proxy address. The same component must make the authorization and socket decision, or pass a tamper-resistant approved-address result across a tightly controlled boundary.
DNS answers may rotate legitimately, so pinning lasts for one fetch hop, not forever. A later job, redirect, or explicit retry resolves and authorizes again. Connection pooling must be keyed by the authorized origin and policy; never reuse a socket merely because an untrusted URL string appears similar.
Step 4: Treat every redirect as a fresh outbound request
Turn off automatic redirect following. For each response with a redirect status, resolve Location against the current URL using the same parser, increment the hop count, and restart scheme, port, userinfo, DNS, address, and connection checks. Reject missing or malformed locations, loops, a fourth redirect, and any hop that resolves to a disallowed address.
Do not forward caller cookies, authorization headers, or arbitrary headers to the first host. Do not forward response-set credentials to a different origin. The fetcher uses a fixed User-Agent and a small fixed header set. Since the product needs only a title, GET is sufficient; redirects must not turn the operation into a caller-controlled POST with a body.
This closes a common bypass: a public attacker-controlled URL returns a redirect to http://169.254.169.254/, http://127.0.0.1/, or an internal service. Approving only the first URL would authorize a different final destination than the one actually contacted.
Step 5: Make the network reject what application code misses
Run fetch workers in a dedicated network segment or namespace. Its egress firewall permits DNS only to the controlled resolver and HTTP/HTTPS only through the policy-aware proxy or approved public route. It denies loopback escapes, internal service ranges, cluster networks, corporate networks, link-local space, and cloud metadata endpoints for both IP families. Validate the effective routes, not only the written rules.
The worker's service identity has only the permissions needed to read and complete preview jobs. Avoid placing broad cloud credentials in its environment. On EC2, disabling instance metadata when unused or requiring IMDSv2 reduces exposure; AWS documents both the IPv4 metadata endpoint 169.254.169.254 and the optional IPv6 endpoint. These controls add defense in depth and do not make the URL policy optional.
Use a separate worker pool from internal webhook or administration clients. A generic HTTP helper that can reach private services should not also process untrusted URLs. Network-denied attempts, including metadata destinations, should produce metrics and alerts without exposing internal topology to the caller.
Step 6: Bound the fetch and parse only the promised artifact
Apply separate DNS, connect, first-byte, idle-read, and three-second total deadlines. Stream the response and stop after 2 MiB of decompressed content; a compressed-body limit alone permits a decompression bomb. Accept only the content types needed for an HTML title. Bound concurrency per account and globally so many slow public servers cannot consume every worker.
Do not retry automatically. A retry creates another outbound authorization and can multiply load. If product requirements later add retries, each attempt starts from parsing and resolution and remains inside the job's total budget.
The parser treats the body as hostile. It does not execute JavaScript, resolve XML external entities, load images, stylesheets, fonts, iframes, or scripts, or follow metadata refresh instructions. Extract the title with a bounded streaming or isolated parser, normalize it as text, cap its length, and return only that value. Keep raw HTML out of the API response and escape the title at the eventual rendering sink.
Step 7: Observe decisions and test the socket-level invariant
Record job outcome, normalized host or a privacy-preserving host key, scheme, effective port, selected address class, redirect count, bytes, duration, and a stable denial reason. Do not log the full URL, query string, userinfo, response body, or DNS payload because URLs can contain secrets. Track denial rates for private, link-local, metadata, scheme, port, redirect, size, timeout, and content-type rules.
Test through the real resolver, proxy, firewall, and HTTP client. Include:
- loopback variants such as
127.1, IPv6::1, and IPv4-mapped IPv6; - private, shared, link-local, multicast, reserved, documentation, and metadata addresses;
- domains with one public and one private answer, plus both A and AAAA records;
- a resolver that returns public during validation and private on the next lookup;
- a public endpoint that redirects to every denied address family and one that loops four times;
- embedded credentials, encoded hosts, non-HTTP schemes, forbidden ports, and malformed URLs;
- slow headers, stalled bodies, oversized decompressed bodies, and compressed bombs;
- an HTML page whose script or image points to an internal host;
- a valid public HTTPS page whose socket is pinned while TLS verifies the original hostname;
- a deliberate application-policy bypass that the egress firewall still blocks.
The DNS-rebinding test must fail if the client performs a second lookup. The positive TLS test must fail if certificate verification accidentally targets the numeric IP. Together they prove that validation and connection use the intended identity.
High-Quality Sample Answer
“I would model SSRF prevention as outbound authorization performed by the component that opens the socket. The API authenticates and rate-limits the caller, stores the URL as untrusted input, and queues a job. The fetch worker parses it with a standards-based parser, rejects userinfo, non-HTTP schemes, and ports outside 80 and 443, and takes the canonical host and path only from that parser.
For an IP literal, I canonicalize it, unwrap IPv4-mapped IPv6, and apply a maintained public-address classifier. For a hostname, I resolve all A and AAAA answers through a controlled resolver and reject the whole destination if any result is private, loopback, shared, link-local, multicast, reserved, documentation, metadata, or otherwise outside policy. I then connect to one approved IP directly while preserving the original hostname for Host, TLS SNI, and certificate verification. That removes the second-DNS-lookup window used by rebinding attacks.
Automatic redirects and retries are disabled. For up to three redirects I parse the new location and repeat the complete authorization before opening another socket. I send only GET with fixed headers and no caller cookies or credentials. A dedicated egress proxy or firewall separately denies every internal, cluster, corporate, link-local, and metadata route over IPv4 and IPv6. The worker has a minimal service identity and cannot use a general internal HTTP client.
Each job has a three-second total deadline and a 2 MiB decompressed-body cap. The parser executes no scripts and loads no subresources; it returns only a length-bounded, sanitized title. I log reason codes, address class, redirect count, bytes, and latency without full URLs. My acceptance suite includes mixed DNS answers, rebinding between check and connect, IPv4-mapped IPv6, redirect-to-metadata, gzip bombs, slow bodies, socket-pinned TLS, and a policy bypass that must still be stopped by the network.”
Common Mistakes
- Block only
localhostand RFC 1918 → alternate IPv4 forms, IPv6, link-local, shared, reserved,
and metadata addresses remain reachable → **canonicalize and classify both address families with a maintained public-destination policy.**
- Validate with a regex → the validator and HTTP parser can disagree about credentials, encoding,
host, and port → use one standards-based parser and consume only its canonical fields.
- Resolve, check, then let the client resolve again → DNS rebinding changes the socket destination
after approval → pin the connection to a vetted IP while verifying TLS against the hostname.
- Approve only the first URL → an allowed public host can redirect to an internal service →
disable automatic redirects and reauthorize every hop.
- Choose the safe answer from mixed DNS results → an address-selection change can later reach the
unsafe answer → reject the hostname when any returned address violates this prompt's policy.
- Forward caller headers → cookies or authorization values leak to an attacker-selected host →
construct a fixed outbound request with no ambient credentials.
- Rely on IMDSv2 or a cloud setting alone → internal services and other address ranges remain
exposed → **combine application authorization, minimal identity, metadata hardening, and egress denial.**
- Limit only compressed bytes → a small body can expand until memory or CPU is exhausted → **cap
decompressed bytes, parsing work, time, and concurrency.**
- Parse the page like a browser → scripts and subresources create new unreviewed outbound requests
→ extract only the promised artifact with active content and external entity resolution disabled.
- Log full URLs for debugging → query secrets and credentials move into observability systems →
log bounded canonical fields and stable reason codes.
Follow-up Questions
What changes when every destination is a known partner?
Use an exact allowlist of normalized scheme, hostname, and port and provision it through reviewed configuration. Resolve and classify the address anyway: a compromised DNS record or configuration mistake should not turn a trusted name into an internal route. A private partner endpoint belongs in a separate authenticated integration with an explicit network path, not in the public preview worker.
Can the service safely support redirects across domains?
Yes, if cross-domain redirects are a product requirement and every hop repeats the entire policy. Strip credentials and origin-specific state, resolve the new hostname, classify every result, pin the new connection, and count the hop. A simpler policy can require the same origin, but it rejects common public redirectors; the interviewer should hear that product tradeoff explicitly.
Why reject a hostname when one DNS answer is public and another is private?
It creates a stable fail-closed contract. Address ordering varies across resolvers, IP families, and retries. Selecting one public answer during validation while another component later selects the private answer reopens the gap. A product that wants partial acceptance needs the same component to pin only approved addresses for every connection and must test failover carefully; this prompt takes the simpler conservative policy.
What if the product needs screenshots or JavaScript-rendered previews?
Put rendering in a more isolated tier with the same egress policy. A browser creates many secondary requests, so intercept and authorize every navigation, redirect, worker, WebSocket, and subresource. Disable local file access and unnecessary browser features, cap CPU, memory, time, and downloads, and destroy the sandbox after each job. Rendering does not inherit trust from the initial page URL.
How would you prove the firewall is effective after deployment?
Run controlled canaries from the real worker identity and namespace against denied IPv4, IPv6, cluster, corporate, and metadata destinations, then confirm both connection failure and the expected network telemetry. Repeat after route, container, proxy, or cloud-network changes. Configuration review shows intent; canaries and flow logs show the effective path.