Support

Need help?

Most answers are already written down. For everything else, Oneop does not publish a response time — there is no SLA instrument behind one — so the fastest thing you can do is send a complete first message.

Search the help centreEmail support@oneop.ioOpen your workspace

Worth including

  • The email you log in with
  • The URL of the screen it happened on
  • What you expected, and what happened instead

Goes somewhere else

Security

SSRF protection: why integrations need outbound request guardrails

By The Oneop team8 min read

TL;DR

Server-side request forgery happens when a user supplies a URL and your server fetches it, letting an attacker reach cloud metadata endpoints, internal admin panels and databases from inside your network. A blocklist of private IP literals does not stop it, because hostnames resolve, encodings vary, and DNS answers change between the check and the connection.

Almost every integration feature in business software ends up asking the customer for a URL. Send a webhook here. Fetch the product image from there. Import this CSV. Crawl this help centre. Stream audit events to that SIEM. Each of those is a request your server makes, from inside your network, to an address a stranger chose. That is the whole attack, and it has a name.

This post covers where user-supplied URLs enter a platform, why the obvious defence fails, and which controls hold up.

What SSRF is, precisely

Server-side request forgery is an attack in which an application fetches an attacker-controlled URL from its own network position. The attacker never touches the target directly. Your server does it for them, carrying whatever trust the network grants it: no authentication prompt for the internal metrics dashboard, no firewall rule between the app tier and the database, no credential needed for the cloud metadata service.

The vulnerability is not that your server makes outbound requests. It is that the destination is chosen by input and the request inherits internal trust. Both halves have to be true for the attack to work, which is also why both halves suggest a fix.

Where user-supplied URLs get in

The webhook configuration field is the obvious one, and it is rarely the only one. In a platform with CRM, support, commerce and finance modules, the surfaces multiply:

SurfaceWhat the user suppliesWhy it is easy to miss
Outbound webhooksThe delivery endpointTreated as configuration, so it is validated once and trusted forever
HTTP-call automation blocksA full URL, method and bodyThe feature exists to call arbitrary endpoints, so validation feels like it defeats the point
Image and file fetchersA remote image or attachment URLThe response is binary and never shown as text, which hides the leak but not the reachability
Knowledge-base or site crawlersA starting URL, then every link it findsThe crawler expands the input set on its own
Import from URLA CSV or JSON locationShips as a convenience feature and skips the review the webhook got
Audit or SIEM streamingA collector endpointRuns as a background job with no user watching the error
Mailbox and service setupAn IMAP, SMTP or API hostNot shaped like a URL, so the URL guard never runs on it
Link previews and unfurlingAny link pasted into a conversationAnyone who can send a message can trigger a fetch

The pattern worth extracting: any field that becomes a hostname is an SSRF surface, whether or not it looks like a URL bar.

The failure case: a webhook that was checked at the wrong moment

Here is a concrete sequence that has burned real teams. An operator configures an outbound webhook pointing at https://hooks.example-attacker.net/collect. The platform validates the URL at save time, resolves the hostname, sees a public address, and stores the configuration. Everything is correct so far.

Two hours later an event fires. The delivery worker takes the stored URL string and hands it to an HTTP client, which resolves the hostname again. This time the authoritative DNS server, which has been serving a one-second TTL, answers 169.254.169.254. The worker opens a connection to the cloud instance metadata service, requests the IAM credentials path, and, because webhook delivery logs the response body for debugging, writes a set of temporary cloud credentials into a log the attacker can read through the delivery-history screen.

Three separate mistakes compound here. Validation ran against the string rather than the connection. The DNS answer was allowed to change between check and use. And the response body was echoed back to the caller. Fixing any one of them breaks the chain, which is a good argument for fixing all three.

Why a blocklist of IP literals does not work

The first instinct is a deny list: reject 127.0.0.1, localhost, 10.*, 192.168.*, 169.254.169.254. It fails for reasons that are all boring and all fatal.

Hostnames are not addresses. internal.attacker.net is a perfectly ordinary public hostname that resolves to 127.0.0.1. No string check sees anything wrong with it.

Addresses have many spellings. 127.0.0.1 is also 2130706433 in decimal, 0x7f000001 in hex, 017700000001 in octal, and 127.1 in the shortened dotted form most resolvers still accept. IPv6 adds ::1, ::ffff:127.0.0.1 and 0:0:0:0:0:ffff:7f00:1. A regular expression over the user’s text is playing a game it cannot win.

The special ranges are longer than people remember. Beyond loopback and RFC 1918, an honest check has to cover link-local 169.254.0.0/16 (where every major cloud parks its metadata service), carrier-grade NAT 100.64.0.0/10, 0.0.0.0/8, the IPv6 unique-local range fc00::/7, IPv6 link-local fe80::/10, and whatever internal ranges your own VPC uses.

Redirects move the target after the check. A URL can point at a compliant public host that answers 302 Location: http://169.254.169.254/. If the HTTP client follows redirects automatically, the validated URL is not the URL that gets fetched.

DNS answers change. This is the rebinding case above. Any design that validates a name and later connects by name has a window, and the window is exactly as long as the attacker wants it to be.

The controls that hold

Four mechanisms, in order of how much they buy you.

Allowlist the destination where the feature allows it. If an integration only ever talks to one vendor’s API, the destination belongs in code or configuration, not in a user field. Most SSRF surfaces turn out not to need arbitrary URLs at all. The ones that genuinely do, such as a customer’s own webhook receiver, are a small minority worth handling carefully.

Resolve, validate, then pin. Resolve the hostname yourself. Check every address the resolver returns, not just the first. Reject the whole set if any address falls in a forbidden range. Then connect to the specific address you validated, carrying the original hostname in the Host header and TLS SNI so certificate validation still works. Pinning is what closes the rebinding window, because the name is never resolved a second time.

Re-run the check on every redirect hop. Disable your HTTP client’s automatic redirect following. Handle each 3xx yourself, apply the full resolve-and-pin check to the new location, and enforce a hop ceiling so a redirect loop cannot hold a worker open.

Route outbound traffic through an egress proxy. A single outbound chokepoint with an allowlist enforces the boundary at the network layer, below any application code path that might forget. It also gives you one place to apply timeouts, response-size caps and logging.

Two supporting rules matter as much as the four above. Cap the response size and the wall-clock timeout, so a slow or enormous target cannot exhaust your workers. And never return the raw response body or a detailed connection error to the user, because a blind SSRF becomes an exploitable one the moment the error message distinguishes “connection refused” from “connection timed out”.

How Oneop handles outbound requests

Oneop’s rule is that every surface where a customer can supply a URL goes through one SSRF guard: webhook blocks, API-call blocks in the chatbot, the crawler, the SIEM audit stream and IMAP connection setup. The guard resolves the hostname and rejects private, loopback and link-local addresses, and it runs again on every redirect hop rather than once at configuration time, which is the part that closes the DNS-rebinding window described above. Redirects are followed manually with a hop ceiling, and every outbound request carries a timeout. The security page states each of those controls by name.

The inbound direction is governed by the same posture. Every webhook Oneop accepts is signature-verified and fails closed, so an event that arrives without a valid signature, or when the signing secret is not configured, is rejected rather than trusted. The per-source verification table lives on the integrations page alongside what each integration syncs and in which direction.

Outbound request handling is one of those controls that is invisible when it works, which is why it is worth asking any vendor about specifically. The architectural reason Oneop can enforce it in one place is the same reason it can enforce stock and ledger invariants in one place, an argument laid out in why Oneop and, for AI specifically, in why your AI agent should live inside your system of record. Guardrails that only exist in some code paths are not guardrails, a point we make about automation generally in when not to automate.

Frequently asked questions

What is server-side request forgery in plain terms?

Server-side request forgery is an attack where someone gives your application a URL and your server fetches it on their behalf. Because the request originates inside your network, it reaches things the attacker could never reach directly: cloud metadata services, internal admin interfaces, databases bound to localhost, and other services that trust the network they sit on rather than checking who is calling.

Is blocking 127.0.0.1 and 10.0.0.0/8 enough to stop SSRF?

No. A blocklist of address literals is trivially bypassed with a hostname that resolves to a private address, an alternate encoding such as decimal or hexadecimal, an IPv4-mapped IPv6 address, or a DNS record that returns a public address once and a private one a moment later. Validation must happen on the resolved address at connection time, not on the string the user typed.

What is DNS rebinding and why does it defeat URL validation?

DNS rebinding is the gap between validating a hostname and connecting to it. An attacker serves a public address with a one-second TTL when your validator resolves the name, then serves 169.254.169.254 when your HTTP client resolves it again a moment later. Checking the URL at configuration time and connecting later is exactly the window this exploits.

Should outbound integration requests follow redirects?

Only if every hop is validated the same way the first URL was. A redirect is a new destination chosen by the remote server, so a compliant first request to a public host can be redirected straight to a link-local metadata address. The safer default is to follow redirects manually with a hop ceiling, re-running the address check on each one, or to disable redirect following entirely.

Does an egress proxy replace URL validation in the application?

It complements it. An egress proxy with an allowlist enforces the boundary at the network layer, where a bug in one code path cannot bypass it, and it gives you one place to log and rate-limit outbound traffic. Application-level validation still matters because it produces a clear error to the user and covers clients that do not route through the proxy.

Written by

The Oneop team

Writing from the team that builds Oneop — an AI customer platform with web chat and email on top of a real CRM, help desk, ledger and warehouse.

Related reading

Start on the free plan and see the real thing

No credit card. Free plan, 2 seats.

Free forever · 2 seats · No credit card