ekofyi
One SSRF Control Is Not Enough: The Budibase Automation Bypass
Security Research9 min read

One SSRF Control Is Not Enough: The Budibase Automation Bypass

A newly disclosed Budibase SSRF issue shows why outbound HTTP protection cannot live in one integration while automation steps call fetch directly. The real failure is architectural: security controls that are opt-in, inconsistent, and easy for new features to bypass.

The Budibase SSRF advisory published yesterday, August 14, 2026, is a clean example of a security problem I see constantly in automation products: a defensive control exists somewhere in the codebase, but the product has more than one way to make a network request.

That means the control does not actually define the security boundary.

According to GHSA-5fpj-28rv-84r7, Budibase automation steps for outgoing webhooks, Zapier, n8n, Slack, Discord, and Make.com make outbound requests with node-fetch directly. Those paths do not use the IP blacklist checks present in the REST API integration.

So a user who can create an automation can point one of those steps at an internal address. The application server makes the request, not the user's browser.

That is server-side request forgery. And in an automation platform, it is exactly the kind of capability attackers look for.

The dangerous part is not fetch() itself

fetch() is not the bug. Direct outbound network access from a feature that accepts a user-controlled URL is the bug when no policy sits between the URL and the request.

Budibase has a REST API integration path containing a blacklist check:

javascript
if (await blacklist.isBlacklisted(url)) {
  throw new Error("Cannot connect to URL.")
}

That check appears in packages/server/src/integrations/rest.ts at line 684.

But the automation steps identified in the advisory call fetch() themselves. For example, the outgoing webhook step in packages/server/src/automations/steps/outgoingWebhook.ts makes this request at line 69:

javascript
const response = await fetch(url, request)

The advisory identifies the same pattern in the Zapier, n8n, Slack, and Discord automation step implementations.

This is the central failure: the application has several outbound HTTP clients, but only one of them has an SSRF control.

A blacklist inside one integration is not outbound-request security. It is a local implementation detail.

That distinction matters because products grow through features, not through neat diagrams. A REST integration gets added. Then webhooks. Then an automation builder. Then a connector for a service that wants a callback URL. Each feature can look reasonable in isolation. Eventually, someone introduces a raw fetch() call because it is the shortest way to ship the feature.

And now the security assumption has already failed.

Why automation builders are prime SSRF territory

Automation platforms are inherently exposed to this problem because their entire job is moving data between systems.

Users are encouraged to configure endpoints, credentials, triggers, callbacks, connectors, and workflow actions. In many products, supplying an external URL is not an edge case. It is the main feature.

That creates a boundary that needs to be treated carefully:

diagram
Rendering diagram…

When the server follows a user-provided destination, it gains the server's network position. It may be able to reach addresses that a normal user cannot reach from the public internet.

The advisory specifically calls out the cloud metadata address:

text
http://169.254.169.254/latest/meta-data/

It also identifies potential access to internal services, databases, administrative panels, and Kubernetes APIs on private IP space.

Whether a particular deployment exposes those targets depends on its infrastructure. That is important. SSRF is not a magical guarantee that every internal service is accessible.

But that is not a reason to downplay it.

An SSRF primitive gives an attacker a request origin inside an environment that was supposed to be segmented. Even a limited request capability can be useful for discovering internal services, hitting unauthenticated administrative endpoints, reaching metadata services, or interacting with infrastructure APIs that rely too heavily on network location.

If an untrusted user can choose where your server connects, outbound network policy is part of your application security model.

The blacklist was bypassable — and defaulted to empty

There are really two issues described in the advisory.

The first is the automation-step bypass. The affected automation paths do not consult the blacklist module at all.

The second is that the REST API integration's blacklist is opt-in through BLACKLIST_IPS. The advisory notes that when this environment variable is not configured, the blacklist is empty. The implementation in packages/backend-core/src/blacklist/blacklist.ts returns false when there are no configured entries:

javascript
if (blackListArray?.length === 0) {
  return false
}

In practical terms, a deployment with no BLACKLIST_IPS configuration gets no blocking from that control.

This is a bad default for a feature that performs requests from a server to a URL selected by a user.

I understand why configurable deny lists exist. Some private endpoints can be legitimate integration targets in self-hosted environments. A company may deliberately want workflows to call an internal service.

But that does not justify starting from "allow every destination." It means the product needs a safe default with an explicit, auditable way to grant exceptions.

The default should protect loopback, link-local, and RFC1918 private networks. If an administrator has a legitimate need for internal automation, that should be a conscious configuration decision with clear scope. Not the accidental result of leaving an environment variable unset.

Blacklists are necessary here, but they are not the whole design

The remediation guidance in the advisory is directionally right: apply validation to every outbound request, centralize the HTTP client, and include default private ranges such as:

  • 127.0.0.0/8 for loopback
  • 10.0.0.0/8 for private addressing
  • 172.16.0.0/12 for private addressing
  • 192.168.0.0/16 for private addressing
  • 169.254.0.0/16 for link-local addressing

That is the baseline. But implementing SSRF defenses correctly requires thinking beyond a string comparison against the URL a user typed.

URLs resolve to IP addresses. Hostnames can resolve differently over time. Redirects can send an initially safe-looking request to an unsafe destination. A URL can also be represented in forms that simplistic parsing gets wrong.

A robust outbound request layer should, at minimum:

  1. Parse the URL with a real URL parser and only permit expected protocols, typically http: and https:.
  2. Resolve the hostname before connecting and reject resolved addresses in blocked ranges.
  3. Validate every redirect destination, not only the original URL.
  4. Consider DNS rebinding, where a hostname's resolution changes between validation and connection.
  5. Keep connection timeouts and response-size limits so an outbound request cannot quietly become a resource-exhaustion feature.
  6. Log blocked attempts with enough context to investigate abuse without exposing sensitive request data.

The important product decision is that these checks need to be unavoidable. Developers adding a new connector should not need to remember a security checklist before calling fetch().

They should not have direct access to a raw network client for user-controlled destinations in the first place.

Centralize the boundary, not the reminder

The advisory recommends a centralized HTTP client wrapper instead of direct fetch() calls. That is the right fix because it changes the shape of the engineering problem.

Without a wrapper, the security rule is procedural:

Every developer must remember to validate URLs before every outbound request.

That rule will fail eventually. Maybe in a new integration. Maybe in a background job. Maybe in a "temporary" connector that becomes permanent. Maybe in an error-handling path no one considered part of the attack surface.

With a wrapper, the rule becomes architectural:

User-controlled outbound requests must go through one policy-enforcing client.

That is a meaningful difference.

A good wrapper can own URL parsing, address policy, DNS resolution checks, redirect handling, timeouts, observability, and future fixes. It also makes review much easier. A reviewer can search for raw fetch() use and treat any result as suspicious, rather than reconstructing policy behavior across every integration.

For an automation product, I would go further: separate outbound clients by trust level.

An internal system component that calls a known, application-controlled service has different requirements from an automation action where a user supplied the destination. Those should not share an unrestricted client simply because both happen to make HTTP requests.

Different trust boundary. Different API.

What Budibase administrators should do now

The advisory references the Budibase 3.41.3 release. Administrators should review the associated release information and their deployed version, then apply the vendor's available update or remediation guidance.

While doing that, do not treat this only as a patch-management item. Review who can create and edit automations, especially automations containing outgoing webhook or connector steps.

A few practical checks are worth doing immediately:

  • Identify automation steps that accept arbitrary URLs.
  • Review whether lower-privileged users can create, modify, or trigger those automations.
  • Search existing workflow definitions for loopback, link-local, and private IP targets.
  • Check whether BLACKLIST_IPS is configured, while recognizing that configuration alone does not address the direct automation-step bypass described in the advisory.
  • Review cloud and internal-service exposure from the Budibase deployment network.
  • Rotate or otherwise assess credentials if there is reason to believe metadata or internal endpoints may have been accessed.

The last point is contextual. The advisory establishes the request capability; it does not establish that any particular environment was exploited. But when a server-side request feature could reach sensitive infrastructure, incident response should account for what that infrastructure might expose.

The broader lesson for automation software

This is not just a Budibase lesson. It is a design lesson for every product that lets users configure outbound HTTP behavior.

Webhook builders, API connectors, link-preview services, importers, document fetchers, notification integrations, and AI agent tools all tend to recreate the same primitive: "give the server a URL, and it will go there."

That primitive needs a first-class security boundary.

The mistake is treating SSRF filtering as an optional enhancement to one feature. It should be a default platform capability. If a product makes external connections on behalf of users, the outbound path deserves the same discipline engineers give to authentication middleware or database access layers.

No security-critical decision should depend on whether a developer happened to use the approved integration module instead of calling fetch() directly.

That is not defense in depth. That is defense by convention.

And conventions are exactly what sprawling automation codebases eventually route around.

Related posts

Written by Eko

If you found this useful, follow @ekofyi on X for more notes like this — or get in touch if you have a problem to solve.