Feb 27, 2026·6 min read·20 visits
Curio versions 1.24.3 through 1.27.3-rc2 were too chatty. When an error occurred, the application returned the raw database error message to the user. Because the application constructed connection strings containing plaintext passwords, this error message included the keys to the kingdom. Attackers can trigger this via standard API calls to steal database credentials.
A critical information disclosure vulnerability in the Filecoin Curio storage implementation allows authenticated attackers to extract full database credentials (including plaintext passwords) simply by triggering an application error. The flaw stems from improper error handling where raw database driver exceptions—containing the connection string—are piped directly back to the HTTP response body.
Curio is the heavy lifter of the Filecoin ecosystem. It's the machinery that helps storage providers seal sectors, prove storage, and ultimately earn FIL. It is a complex distributed system that relies heavily on a central nervous system: the database (typically YugabyteDB or PostgreSQL). This database holds the state of every deal, every sector, and the financial tracking for the operation.
Now, imagine if that central nervous system decided to print its access codes on a billboard every time it had a hiccup. That is essentially what happened here. In the world of distributed systems, "observability" is a buzzword everyone loves. We want logs, we want metrics, we want to know why things failed. But there is a fine line between helpful debugging information and catastrophic oversharing.
This vulnerability is a classic case of "The Road to Hell is Paved with Good Intentions." The developers wanted to make sure that when the database connection failed, the user (or the log) knew exactly why. Unfortunately, the "why" included the "how"—specifically, the postgresql://user:password@host connection string.
The root cause here is a beautiful, tragic collaboration between two components: the Go pgx database driver and Curio's HTTP error handlers.
First, let's look at the pgx driver. When pgx fails to connect or encounters a fatal error, it generates an error object. To be helpful, the string representation of this error often includes the configuration that failed. If you initialize your connection using a DSN (Data Source Name) string like postgres://admin:SuperSecret@127.0.0.1:5432/db, the driver treats that string as a fundamental property of the connection context.
Second, we have the Curio HTTP handlers. In over 18 different locations—specifically in pdp/handlers.go and market/mk12—the code followed a pattern that every junior developer is taught to avoid in production: piping err.Error() directly to the HTTP response.
It looked something like this:
if err := db.DoSomething(); err != nil {
// "Hey client, here is exactly what went wrong, including my secrets!"
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}When the database operation fails, err.Error() returns the pgx error, which contains the DSN, which contains the password. The server essentially vomits its own credentials back to the requester. It's the digital equivalent of a bank teller shouting the vault combination because the vault door is stuck.
Let's look at the fix in commit 551da78e to understand the severity. The patch reveals exactly how the credentials were being mishandled.
The Vulnerable Pattern (Before):
Originally, the connection string was built by blindly concatenating the password into the URL. This URL was then passed to the driver, making it part of the driver's error context.
// The "Before" logic (pseudocode based on patch analysis)
dsn := fmt.Sprintf("postgres://%s:%s@%s:%s/%s",
user, password, host, port, dbName)
config, err := pgxpool.ParseConfig(dsn)The Fix (After):
The remediation strategy was two-fold. First, stop putting the password in the URL string. Second, explicitly sanitize error messages before they leave the boundary.
// The "After" logic
// 1. Construct DSN *without* the password
dsn := fmt.Sprintf("postgres://%s@%s:%s/%s", user, host, port, dbName)
config, err := pgxpool.ParseConfig(dsn)
// 2. Inject password directly into the config struct
// This keeps it out of the DSN string representation
config.ConnConfig.Password = passwordFurthermore, the developers introduced a new errFilter function. This function acts as a firewall for error strings, frantically scrubbing sensitive keywords before they hit the logs or the HTTP response:
func errFilter(err error) error {
if err == nil { return nil }
msg := err.Error()
// Redact sensitive patterns
msg = strings.ReplaceAll(msg, password, "********")
// ... other redactions ...
return errors.New(msg)
}They essentially had to build a censorship layer for their own application because the driver was too honest.
Exploiting this does not require complex heap spraying or ROP chains. It requires network access (often authenticated, depending on the specific endpoint configuration) and the ability to annoy the database.
The Attack Chain:
pdp handlers (ECDSA JWT auth) and market handlers are affected. In a multi-tenant or cluster environment, low-privilege access is often sufficient.Example Response:
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain
failed to connect to `host=10.0.0.5 user=curio password=CorrectHorseBatteryStaple dbname=yugabyte`: dial tcp 10.0.0.5:5433: connect: connection refusedJust like that, the attacker has the username, password, hostname, and database name. They can now connect directly to the YugabyteDB instance, bypassing Curio entirely. From there, they can modify balances, delete sectors, or ransom the storage cluster.
Why is this a "drop everything and patch" situation? Because the database is the source of truth for the Filecoin storage provider.
If an attacker gains direct SQL access via the leaked credentials:
DROP DATABASE curio;. Game over. The storage provider loses track of where data is stored on disk, leading to slashing and loss of collateral.This is not just an information leak; it is a full compromise of the storage cluster's control plane. The CVSS score might say 7.5, but for a storage provider holding millions of dollars in collateral, the business impact is a solid 10.0.
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Curio Filecoin Project | >= 1.24.3, < 1.27.3-rc2 | 1.27.3-rc2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-209 |
| CWE Name | Generation of Error Message Containing Sensitive Information |
| Attack Vector | Network |
| CVSS Score | 7.5 (High) |
| Exploit Status | PoC Available |
| Patch Status | Released (v1.27.3-rc2) |
An OS command injection vulnerability in yt-dlp before 2026.06.09 allows unauthenticated remote attackers to execute arbitrary shell commands via crafted media metadata when a user processes media using the --exec post-processing parameter with unsafe string interpolation conversions.
An in-depth technical analysis of multiple security vulnerabilities in the self-hosted Docker API server of Crawl4AI up to version 0.8.7. These flaws include a critical arbitrary file write via symlink traversal and TOCTOU weakness, CRLF log injection, webhook header injection, and SSRF filter gaps. These have been remediated in version 0.8.8.
A technical evaluation of the Crawl4AI open-source web crawling and scraping library revealed a high-severity credential exfiltration vulnerability in its self-hosted Dockerized API server. The flaw arises from an unvalidated base_url parameter in request payloads and a dynamic prefix resolution mechanism that retrieves system environment variables. Unauthenticated remote attackers can leverage these features in tandem to extract host-level secrets or redirect configured LLM API keys to an external listener under their control.
The Crawl4AI Docker API server, in versions 0.8.6 and prior, contains multiple critical vulnerabilities including improper path sanitization, missing authentication on administration routes, hardcoded JWT secrets, and SSRF. These vulnerabilities allow remote, unauthenticated attackers to write arbitrary files, execute arbitrary code, and pivot into private cloud environments.
A local security vulnerability in the Nuxt development server (nuxt dev) allows local unprivileged users to access sensitive configuration files and source code. On Linux environments running Node.js 20+, Nuxt bound its internal vite-node IPC server to an abstract-namespace Unix socket without any peer authentication, enabling co-resident local users to connect and request module code directly.
Mozilla Bleach is an open-source HTML sanitizing library for Python. Versions up to and including 6.3.0 contain an incomplete filtering implementation in the URI validation logic ('sanitize_uri_value'). This logic fails to detect disallowed protocols, such as 'javascript:', if they contain Unicode invisible characters, whitespace characters, or characters with a code point greater than U+00A0. While standard-compliant web browsers do not directly execute invalid URI schemes containing these non-standard characters, downstream systems that normalize Unicode text by stripping invisible or non-ASCII characters can unintentionally reactivate the 'javascript:' prefix, causing Cross-Site Scripting (XSS). Additionally, this behavior violates Bleach's core sanitization contract by outputting URIs that bypass protocol allowlists configured by the caller.