CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-PG2V-8XWH-QHCC
6.5

The Call Is Coming From Inside the House: OpenClaw SSRF Analysis

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 18, 2026·7 min read·10 visits

PoC Available

Executive Summary (TL;DR)

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.

The Hook: The Niche Extension Trap

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 Flaw: The Naive Courier

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.

The Code: From Trust to Paranoia

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.

The Vulnerable Code (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.

The Fix (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:

  1. Normalization: It forces the URL into a standard format.
  2. Guarding: It uses a specialized fetch wrapper that resolves the DNS before making the request to ensure the IP isn't on a denylist (Private IPs, Loopback, Link-Local).
  3. Opt-In: It assumes the user is malicious or mistaken by default (allowPrivateNetwork: false).

The Exploit: Cloud Metadata Heist

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).

The Setup

  1. Target: An OpenClaw instance where we have permission to configure extensions (or we've found a way to inject configuration).
  2. Goal: Exfiltrate temporary AWS credentials attached to the EC2 role.

The Attack Chain

  1. Configuration Injection: We navigate to the Tlon extension settings.
  2. Payload: In the "Ship URL" field, we input:
    http://169.254.169.254/latest/meta-data/iam/security-credentials/admin-role
    (Note: In a real attack, we might need to handle the trailing /~/login the code appends. A simple # fragment identifier usually kills the rest of the path: http://169.254.169.254/.../admin-role#)
  3. Trigger: We click "Save & Connect" or trigger a manual sync.
  4. Execution:
    • The backend executes fetch('http://169.254.169.254/.../admin-role#/~/login').
    • The # creates a client-side fragment, effectively stripping the login path from the server request.
    • The request hits the AWS metadata service.

The Result

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:

  • Returned in a "Connection Failed: <response_body>" error message to the UI.
  • Logged to an internal error log we can read.
  • Blindly confirmed (Timing attack to map internal ports).

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.

The Impact: Why You Should Care

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 Fix: Hardening the Perimeter

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).

Mitigation Strategies

If you cannot patch immediately:

  1. Disable the Extension: If you aren't using Tlon/Urbit, disable the extension entirely. Zero attack surface is the best attack surface.
  2. Egress Filtering: Your servers should not be allowed to talk to everything. Configure your firewall (iptables, security groups) to BLOCK outgoing connections to:
    • 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)

Developer Takeaway

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.

Official Patches

GitHubOfficial Patch Commit

Fix Analysis (1)

Technical Appendix

CVSS Score
6.5/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

Affected Systems

OpenClaw ServerTlon (Urbit) Extension

Affected Versions Detail

Product
Affected Versions
Fixed Version
openclaw
openclaw
<= 2026.2.132026.2.14
AttributeDetail
Vulnerability TypeServer-Side Request Forgery (SSRF)
CWE IDCWE-918
CVSS Score6.5 (Medium)
Attack VectorNetwork
Privileges RequiredLow (Authenticated User)
Exploit StatusPoC Available

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1133External Remote Services
Persistence
T1552.005Cloud Instance Metadata API
Credential Access
CWE-918
Server-Side Request Forgery (SSRF)

The application does not properly validate that the destination IP address of a request is not within a private network range.

Known Exploits & Detection

Internal AnalysisProof of concept involves setting the Urbit URL to local or cloud metadata addresses.

Vulnerability Timeline

Vulnerability patched in commit bfa7d21e
2026-02-14
Version 2026.2.14 released
2026-02-14
GHSA Advisory Published
2026-02-18

References & Sources

  • [1]GHSA Advisory
  • [2]CWE-918: SSRF

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.