Feb 18, 2026·7 min read·10 visits
The Tlon extension for OpenClaw didn't check if the URL you gave it was safe. Attackers could point it at `localhost` or AWS metadata (`169.254.169.254`) to steal credentials or map internal networks. Fixed in version 2026.2.14 by adding a strict SSRF guard and URL validator.
A classic Server-Side Request Forgery (SSRF) vulnerability in OpenClaw's Tlon (Urbit) extension allowed authenticated users to coerce the server into making arbitrary HTTP requests to internal networks, loopback interfaces, or cloud metadata services. By failing to validate the user-supplied 'ship' URL, the application acted as an open proxy for internal reconnaissance.
In the world of open-source software, the core application is usually a fortress. It's beaten, battered, and hardened by thousands of eyes. But the extensions? The plugins? The "optional integrations"? That's the soft underbelly. That's where the bodies are buried.
OpenClaw is a robust gateway, but it offers an extension for Tlon (Urbit)—a decentralized personal server platform that is already esoteric enough to make most security researchers glaze over. And that’s exactly why this vulnerability is interesting. It hides in the complexity of a feature most people probably don't use, but is available to anyone who installs the default suite.
The vulnerability here isn't some complex memory corruption or a race condition in the kernel. It's a logic error born of trust. The application assumed that if a user provides a URL for their "ship" (Urbit server), they probably mean a public URL on the internet. But as we know in this industry, if you give a user a text box that accepts a URL, and you don't sanitize it, you haven't built a feature—you've built a proxy.
The root cause here is Server-Side Request Forgery (SSRF), specifically CWE-918. Conceptually, SSRF is like telling a security guard, "Hey, go check what's inside that room for me." A good guard checks if you have clearance for that room. A vulnerable guard—like OpenClaw's Tlon extension—just walks into the bank vault because you asked nicely.
In the Tlon extension, the code needed to authenticate with a user's Urbit ship. To do this, it took a url and a code from the configuration. It then constructed a request to ${url}/~/login.
The flaw was the total absence of boundary checks. There was no logic to say, "Hey, maybe we shouldn't allow the URL to resolve to 127.0.0.1 or 169.254.169.254." The fetch API in Node.js (and the browser) is protocol-agnostic and network-agnostic. It will happily fetch whatever you tell it to. This means the OpenClaw server, sitting comfortably behind your corporate firewall, could be tricked into scanning your internal network or retrieving sensitive cloud instance metadata.
Let's look at the smoking gun. This is the code that existed prior to version 2026.2.14. It's painfully simple, which is usually a good thing, except when it involves network calls based on user input.
auth.ts)// The 'url' parameter comes directly from user config
export async function authenticate(url: string, code: string): Promise<string> {
// 🚩 DANGER: No validation of 'url' before usage
const resp = await fetch(`${url}/~/login`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `password=${code}`,
});
// ... processing response
}If I set my URL to http://localhost:22, this code tries to POST to the SSH port. If I set it to an internal Jenkins server, it tries to login there.
bfa7d21e)The patch introduced by Peter Steinberger is a masterclass in defense-in-depth. They didn't just add a regex check; they ripped out the underlying fetch mechanism and replaced it with a guarded version.
// The new world order
import { fetchWithSsrFGuard } from '@openclaw/plugin-sdk';
// 1. Validate the URL structure first
const validatedUrl = validateUrbitBaseUrl(rawUrl);
// 2. Use the guarded fetcher
const resp = await fetchWithSsrFGuard(`${validatedUrl}/~/login`, {
method: 'POST',
// ...
}, {
// 3. Explicit policy: Block private networks by default
allowPrivateNetwork: config.allowPrivateNetwork ?? false,
auditContext: 'tlon-urbit-login'
});The fix does three critical things:
allowPrivateNetwork: false).So, how do we weaponize this? Let's assume OpenClaw is running on an AWS EC2 instance. This is the holy grail of SSRF targets because of the Instance Metadata Service (IMDS).
http://169.254.169.254/latest/meta-data/iam/security-credentials/admin-role/~/login the code appends. A simple # fragment identifier usually kills the rest of the path: http://169.254.169.254/.../admin-role#)fetch('http://169.254.169.254/.../admin-role#/~/login').# creates a client-side fragment, effectively stripping the login path from the server request.The application expects a login response but instead gets a JSON blob containing AccessKeyId, SecretAccessKey, and Token. Depending on how OpenClaw handles the error, this data might be:
Even without full response reflection, a Blind SSRF allows us to port scan the internal network. http://localhost:6379 responding quickly vs. http://localhost:6380 timing out tells us where the Redis instance lives.
You might look at the CVSS score of 6.5 and think, "Medium severity? I'll patch it next month." That would be a mistake. CVSS scores often fail to capture context.
If your OpenClaw instance is running inside a Kubernetes cluster, this SSRF allows an attacker to hit the Kubelet API. If it's in AWS, it's IAM credentials. If it's on a corporate LAN, it's access to that dusty old Windows 2008 server that hasn't been patched since the Obama administration.
SSRF turns the perimeter firewall into Swiss cheese. The attacker is no longer "outside"; they are originating requests from trusted infrastructure. Trust relationships (like "allow traffic from 10.0.0.5") are immediately bypassed. This vulnerability transforms OpenClaw from a productivity tool into a pivot point for lateral movement.
The remediation is straightforward: Update to version 2026.2.14.
The patch implemented in commit bfa7d21e is robust. It moves the responsibility of safety from the developer (who might forget a check) to the framework (the plugin-sdk).
If you cannot patch immediately:
169.254.169.254 (Cloud Metadata)127.0.0.0/8 (Localhost)10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (Private LANs)Never use a raw fetch or axios call with user-supplied URLs. Always use a wrapper library that resolves DNS before connection and verifies the IP against a denylist. Remember: DNS Rebinding attacks can bypass simple string checks on hostnames.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
openclaw openclaw | <= 2026.2.13 | 2026.2.14 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Server-Side Request Forgery (SSRF) |
| CWE ID | CWE-918 |
| CVSS Score | 6.5 (Medium) |
| Attack Vector | Network |
| Privileges Required | Low (Authenticated User) |
| Exploit Status | PoC Available |
The application does not properly validate that the destination IP address of a request is not within a private network range.