Do not turn a screenshot URL into a route inside your network.

A browser follows redirects, resolves hostnames, and loads subresources. Treat every user-supplied capture target as an outbound network request—not as harmless image input.

Public URLs only · DNS and subrequest checks · Updated July 20, 2026

The actual request surface

The page is not the only destination.

If your endpoint passes an untrusted URL directly to page.goto(), the browser can reach more than the first hostname. Redirects, images, scripts, frames, stylesheets, and font requests can each trigger another connection. A hostname can also resolve differently between validation and connection.

Blocking 127.0.0.1 with one regular expression is not enough.

IPv6, IPv4-mapped IPv6, link-local addresses, cloud metadata, private DNS answers, mixed public/private answers, redirects, and DNS rebinding all need a connection-time policy.

Put a gate on every outbound connection.

Latchshot first validates the requested target, then runs Chromium through a local outbound proxy. The proxy resolves each HTTP request or HTTPS tunnel, rejects any hostname with a non-public answer, and connects to the validated address rather than handing the hostname back to the operating system.

One capture, five guarded stages
  1. Input envelopeAbsolute HTTP(S), no URL credentials, ≤2,048 characters
  2. Port gateOnly standard web ports 80 and 443
  3. DNS gateEvery answer must be public; mixed answers fail closed
  4. Pinned proxyEach request or tunnel connects to a validated IP
  5. Isolated renderFresh browser context, service workers blocked, downloads disabled

The same proxy path covers the initial document, redirects, and page subresources. It reduces SSRF exposure inside Latchshot; it does not authorize a URL on behalf of your application.

Send one public target from trusted server code.

Keep the Latchshot key outside the browser. Your application authenticates its own user first, decides whether the capture is allowed, and sends only the public target and bounded output options.

curlpublic URL → guarded PNG
curl --fail-with-body \
  https://latchshot.fly.dev/v1/render \
  -H "Authorization: Bearer $LATCHSHOT_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{
    "url": "https://example.com",
    "kind": "screenshot",
    "format": "png",
    "width": 1280,
    "height": 800,
    "waitUntil": "domcontentloaded",
    "timeout": 15000
  }' \
  --output capture.png

On success, the response body is the image. A successful render consumes one monthly success; a rejected target or failed render does not.

Make rejection behavior part of the contract.

Do not return an image of an error page or silently retry a target-policy failure. Latchshot returns structured JSON with an HTTP 400 status before quota consumption.

Input classExampleResult
Local scheme or filefile:///etc/passwdinvalid_target
URL credentialshttps://user:pass@example.cominvalid_target
Loopback or private hosthttp://127.0.0.1private_target
Cloud metadata / link-localhttp://169.254.169.254private_target
Mixed DNS answersOne public answer + one private answerprivate_target
Non-web porthttps://example.com:8443blocked_port
Resolution failureHostname has no usable answerunresolvable_target

On a narrow screen, scroll the rejection table horizontally.

HTTP responsetarget policy failure
HTTP/2 400
content-type: application/json

{
  "error": {
    "code": "private_target",
    "message": "local and private network addresses are not allowed"
  }
}

Keep authorization in your application.

A guarded renderer protects its own network boundary. It cannot decide whether your user is allowed to capture a competitor, whether a public bearer link contains customer data, or how long your stored image should live.

Before capture
Authenticate the caller, authorize the product action, normalize the URL, apply any product-specific domain allowlist, and enforce your own per-user quota.
During capture
Use one server-held API key, bound dimensions and timeout, and preserve structured target-policy errors instead of retrying them.
After capture
Validate the media type and byte limit, store only if needed, choose retention, and keep the target URL out of public logs and analytics.
Do not put secrets in the target URL.

A syntactically public URL can still carry a bearer token, signed query string, or private customer identifier. Latchshot intentionally offers no authenticated-page or private-preview contract.

Test the policy, not only the happy-path image.

  1. Public controlA stable public page returns the expected image type and dimensions.
  2. Address familiesLoopback, RFC1918, link-local, IPv6 local, and IPv4-mapped IPv6 inputs all fail.
  3. DNS mixturesA hostname with one public and one private answer fails rather than choosing the public answer.
  4. Redirect and subresourceA public first URL cannot redirect or load an asset into a private destination.
  5. Error hygieneOperational detail omits target URLs and API-key-shaped credentials.
  6. Resource boundsTimeout, page-size, queue, rate, and response-size limits fail explicitly.

Latchshot's public contract covers the hosted behavior. If you operate Playwright yourself, implement equivalent checks at connection time and test them inside the same network where Chromium runs.

Use the guardrail for the job it actually covers.

Use the hosted endpoint

The output starts from one public page.

Target
Public HTTP or HTTPS on port 80 or 443
State
No cookies, login, customer header, private DNS, or local file
Output
One synchronous PNG, JPEG, or PDF owned by your application

Keep a browser in your network

The browser must reach private state.

Reach
Internal previews, VPN services, non-standard ports, or local files
Session
Cookies, login, custom headers, certificates, or user impersonation
Workflow
Clicks, typing, arbitrary scripts, extraction, tracing, or multi-step navigation

This is a defense-in-depth boundary, not a security guarantee or a substitute for application authorization, egress policy, patching, logging, and review.

Want to classify your current boundary?

Run the free browser-local seven-gate check without sending answers or source. Inspect the public risk-ranked report specimen, then use the fixed-scope $149 endpoint safety review for one public-repository JavaScript, TypeScript, or Python screenshot route, including a report, focused patch, regression tests, and residual-risk handoff.

Test the public-only boundary.

Create one card-free key with 100 successful renders each UTC month. Start with a public page you are allowed to capture and keep the rejected-target cases in your integration suite.

Get a free key