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-38CW-85XC-XR9X
9.80.04%

Veramo ORM: Decentralized Identity, Centralized SQL Injection

Alon Barad
Alon Barad
Software Engineer

Feb 17, 2026·5 min read·22 visits

PoC Available

Executive Summary (TL;DR)

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.

The Hook: Your Keys, My Database

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 Flaw: Trusting the Sort Order

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

The Code: The Smoking Gun

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.

The Exploit: Stealing the Crown Jewels

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:

  1. Breakout: The quote " after issuanceDate closes the identifier context.
  2. Logic Reset: The attacker reconstructs the necessary FROM and JOIN clauses to satisfy the original query structure, then adds WHERE 1=0 to ensure the original query returns nothing.
  3. The Heist: The UNION ALL SELECT appends the results of the attacker's query. In this case, they are selecting the privateKeyHex from the private-key table.
  4. Formatting: They use json_object to format the stolen key nicely within the result set structure expected by the application.
  5. Cleanup: The comment sequence -- - 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.

The Impact: Identity Theft at Scale

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:

  1. Impersonation: They can sign Verifiable Credentials as the issuer. If the issuer is a bank or a government agency, the attacker can mint fake IDs, fake loan approvals, or fake diplomas.
  2. Revocation Authority: They can revoke legitimate credentials, effectively deleting a user's digital existence.
  3. Permanent Compromise: Unlike a password, you can't just 'reset' a private key associated with an immutable DID on a blockchain. The DID itself is burned. The reputation attached to it is destroyed.

This vulnerability scores a near-perfect severity because it allows unauthenticated remote attackers to completely undermine the trust model of the entire system.

The Fix: Upgrade or Die

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:

  1. Update Immediately: Upgrade @veramo/data-store and @veramo/data-store-json to version 6.0.2 or higher.
  2. Rotate Keys: If you were running a vulnerable version on a public-facing endpoint, you must assume your keys are compromised. Rotate your DIDs and revocation registries. Yes, it's painful. No, you don't have a choice.
  3. Audit Logs: Check your database logs for UNION statements or weird syntax errors involving the private-key table. If you see them, you've been hit.

Official Patches

VeramoVeramo v6.0.2 Release Notes

Fix Analysis (1)

Technical Appendix

CVSS Score
9.8/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
EPSS Probability
0.04%
Top 100% most exploited

Affected Systems

@veramo/data-store@veramo/data-store-jsonVeramo Agents utilizing SQL or JSON file databases

Affected Versions Detail

Product
Affected Versions
Fixed Version
@veramo/data-store
Veramo
< 6.0.26.0.2
@veramo/data-store-json
Veramo
< 6.0.26.0.2
AttributeDetail
Vulnerability IDGHSA-38cw-85xc-xr9x
CWECWE-89 (SQL Injection)
CVSS9.8 (Critical)
Attack VectorNetwork
ImpactData Exfiltration (Private Keys)
Patch Commit067e39dd76f11ee2d25b99c8361d4f02a4223e3b

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1059.006Command and Scripting Interpreter: Python/SQL
Execution
T1555Credentials from Password Stores
Credential Access
CWE-89
SQL Injection

Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

Known Exploits & Detection

GitHub AdvisoryProof of Concept demonstrating extraction of private keys via UNION SELECT
NucleiDetection Template Available

Vulnerability Timeline

Patch Committed (v6.0.2)
2026-01-16
Advisory Published
2026-01-16

References & Sources

  • [1]GitHub Advisory GHSA-38cw-85xc-xr9x
  • [2]Veramo Documentation

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.