Feb 17, 2026·5 min read·22 visits
Critical SQL Injection in Veramo data store (< v6.0.2) allows attackers to dump the entire database—including private keys—by injecting malicious SQL into the 'order' parameter. Patch immediately.
Veramo, a popular framework for Decentralized Identifiers (DIDs) and Verifiable Credentials, shipped with a critical SQL injection vulnerability in its data store ORM. By manipulating the sorting parameters of a query, an unauthenticated attacker could bypass input sanitization and execute arbitrary SQL commands. This flaw allows for the complete exfiltration of the database, including the most sensitive asset in the ecosystem: the private keys used to sign credentials. The irony of a 'self-sovereign' identity system losing its sovereignty to a 1990s-style attack vector is palpable.
Veramo is the Swiss Army knife for the decentralized web. It promises 'Self-Sovereign Identity' (SSI)—the idea that you, and only you, control your digital identity through cryptographic keys and Verifiable Credentials. It's a noble goal, built on complex standards like DIDs (Decentralized Identifiers). But as with all complex cryptographic castles, the drawbridge is usually built by a sleep-deprived developer using an ORM.
In this specific case, the vulnerability isn't in the fancy cryptography. It's in the boring, mundane layer that stores data. The @veramo/data-store package handles saving and retrieving claims, identifiers, and credentials. It wraps TypeORM to make life easier for developers.
Unfortunately, in versions prior to 6.0.2, it made life significantly easier for hackers too. By failing to validate input in the query builder, Veramo inadvertently turned its database into a public library where the 'fiction' section contains your private keys.
The root cause is a classic failure to sanitize inputs passed to a Query Builder. Modern ORMs like TypeORM are generally good at preventing SQL injection if you use them correctly. They automatically parameterize values in where clauses. However, they often trust developers to handle structural elements—like column names and aliases—responsibly.
In Veramo's DataStoreORM, specifically within the decorateQB() function, the code accepted a user-controlled order array. This array dictates how results are sorted. The application took the column property from this user input and plugged it directly into the query as an alias or an orderBy target.
Because the input wasn't whitelisted against actual table columns, the database engine treated the injected string as part of the SQL structure. This isn't just a 'syntax error' waiting to happen; it's a full breakout. The code effectively said, 'I trust the user to tell me which column to sort by,' without realizing the user might want to sort by "some_column" UNION ALL SELECT private_key....
Let's look at the breakdown. The vulnerability stems from how the order loop handled column names. It blindly trusted item.column.
The Vulnerable Logic (Conceptual):
// Inside decorateQB()
if (args.order) {
args.order.forEach((item) => {
// DANGEROUS: item.column is injected directly into the query builder
qb.addSelect(item.column, 'alias_name');
qb.addOrderBy('alias_name', item.direction);
});
}The Fix (Commit 067e39dd76f11ee2d25b99c8361d4f02a4223e3b):
The maintainers introduced a strict allowlist. Instead of trusting the input, they now verify that the requested column actually exists in a hardcoded ALLOWED_COLUMNS list.
// The Remediation
const ALLOWED_COLUMNS = ['issuer', 'subject', 'issuanceDate', ...];
if (args.order) {
args.order.forEach((item) => {
// Check if the column is in the allowlist
if (!ALLOWED_COLUMNS.includes(item.column)) {
throw new Error(`Invalid column name: ${item.column}`);
}
// Safe to proceed
qb.addOrderBy(item.column, item.direction);
});
}This is the difference between a secure application and a leaky sieve. By enforcing an allowlist, the attack surface for this vector drops to zero.
How bad can a sorting bug be? Fatal. The proof-of-concept payload provided in the disclosure demonstrates a sophisticated UNION-based injection. The attacker crafts a payload that breaks out of the addSelect or orderBy context, closes the previous query logic, and appends a UNION ALL SELECT to retrieve data from the private-key table.
The Payload:
{
"order": [
{
"direction": "ASC",
"column": "issuanceDate\" AS \"issuanceDate\" FROM \"claim\" \"claim\" LEFT JOIN ... WHERE 1=0 UNION ALL SELECT ..., (SELECT json_object('privateKeyHex', privateKeyHex) ), ... FROM `private-key`-- -"
}
]
}Breakdown of the Attack:
" after issuanceDate closes the identifier context.FROM and JOIN clauses to satisfy the original query structure, then adds WHERE 1=0 to ensure the original query returns nothing.UNION ALL SELECT appends the results of the attacker's query. In this case, they are selecting the privateKeyHex from the private-key table.json_object to format the stolen key nicely within the result set structure expected by the application.-- - comments out the rest of the original query to prevent syntax errors.The result? The API returns a list of 'claims' that are actually the server's private keys.
In a centralized system, a database breach is bad. In a decentralized identity system, it's existential. The Veramo agent often acts as a custodian for DIDs. If an attacker extracts the private keys:
This vulnerability scores a near-perfect severity because it allows unauthenticated remote attackers to completely undermine the trust model of the entire system.
There is no fancy workaround here. You cannot WAF your way out of this reliably because the injection happens inside a complex JSON object structure that might look legitimate to generic filters.
Action Plan:
@veramo/data-store and @veramo/data-store-json to version 6.0.2 or higher.UNION statements or weird syntax errors involving the private-key table. If you see them, you've been hit.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
@veramo/data-store Veramo | < 6.0.2 | 6.0.2 |
@veramo/data-store-json Veramo | < 6.0.2 | 6.0.2 |
| Attribute | Detail |
|---|---|
| Vulnerability ID | GHSA-38cw-85xc-xr9x |
| CWE | CWE-89 (SQL Injection) |
| CVSS | 9.8 (Critical) |
| Attack Vector | Network |
| Impact | Data Exfiltration (Private Keys) |
| Patch Commit | 067e39dd76f11ee2d25b99c8361d4f02a4223e3b |
Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')