CVE-2026-44204 Overview
CVE-2026-44204 is a SQL injection vulnerability in Shelf, an open-source platform for tracking physical assets. The flaw affects versions from 1.12 up to but not including 1.20.1. An authenticated user with any role can inject arbitrary SQL through the sortBy query parameter on the /assets route. Successful exploitation allows the attacker to read data from any table in the database, including records belonging to other organizations in multi-tenant deployments. The vulnerability is classified under [CWE-20: Improper Input Validation] and has been remediated in Shelf version 1.20.1.
Critical Impact
Any authenticated Shelf user can read arbitrary database tables across tenant boundaries by injecting SQL through the sortBy parameter on /assets.
Affected Products
- Shelf (shelf.nu) versions 1.12 through 1.20.0
- Self-hosted Shelf deployments prior to 1.20.1
- Multi-tenant Shelf instances sharing a single database
Discovery Timeline
- 2026-05-12 - CVE-2026-44204 published to NVD
- 2026-05-14 - Last updated in NVD database
Technical Details for CVE-2026-44204
Vulnerability Analysis
The vulnerability resides in the asset listing endpoint /assets, which accepts a sortBy query parameter used to construct ORDER BY clauses in raw SQL queries. The application interpolates the user-supplied value directly into the SQL string without validating it as a safe identifier. PostgreSQL does not parameterize identifiers such as column names, table names, or aliases — they must be inlined into the query string. Without an allowlist check, an authenticated attacker can break out of the identifier context and append arbitrary SQL fragments.
Because the vulnerable code path uses Prisma.raw() together with $queryRaw or $executeRaw, the injected payload executes with the same database privileges as the application itself. The Shelf application connects with broad read access across all tenant tables, so the injection bypasses the row-level isolation that normally separates organizations.
Root Cause
The filter-parsing.ts module in apps/webapp/app/modules/asset/ accepted the sortBy value and forwarded it to raw SQL composition without enforcing a strict identifier pattern. No allowlist of sortable columns existed prior to the fix, and there was no rejection of characters outside the conservative SQL identifier set.
Attack Vector
Exploitation requires only a valid authenticated session of any role. The attacker issues a GET request to /assets with a crafted sortBy parameter containing SQL syntax. The injected fragment is concatenated into the ORDER BY clause and evaluated by PostgreSQL, returning rows from arbitrary tables via techniques such as boolean-based or UNION-based extraction.
// Patch: apps/webapp/app/utils/sql.ts (added in 1.20.1)
// Matches a conservative SQL identifier: must start with a letter or
// underscore, followed by letters, digits, or underscores. No quoting,
// no dots, no spaces, no operators, no Unicode.
const SAFE_SQL_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
/**
* Returns true if `value` is safe to inline as a Postgres identifier.
*/
export function isSafeSqlIdentifier(value: string): boolean {
return SAFE_SQL_IDENTIFIER.test(value);
}
Source: Shelf commit 5d35c15
The patch introduces an allowlist-based identifier validator and applies it in filter-parsing.ts before any value is passed to Prisma.raw(). Rejecting characters outside [a-zA-Z0-9_] neutralizes injection because the PostgreSQL parser cannot exit identifier context using only those characters.
Detection Methods for CVE-2026-44204
Indicators of Compromise
- HTTP requests to /assets containing sortBy values with non-alphanumeric characters such as spaces, parentheses, commas, single quotes, or SQL keywords like UNION, SELECT, CASE, or --.
- Unusually long sortBy query parameter values in web server or reverse proxy access logs.
- PostgreSQL query logs showing ORDER BY clauses referencing tables or columns not part of the asset listing schema.
- Authenticated sessions issuing high volumes of /assets requests with varying sortBy payloads, consistent with blind SQL injection enumeration.
Detection Strategies
- Inspect application and reverse proxy logs for sortBy values that fail the ^[a-zA-Z_][a-zA-Z0-9_]*$ pattern.
- Enable PostgreSQL log_statement = 'all' temporarily on non-production tiers to capture the exact raw queries generated by /assets requests.
- Deploy a Web Application Firewall (WAF) rule that blocks SQL metacharacters in the sortBy parameter on the /assets endpoint.
Monitoring Recommendations
- Alert on any /assets request whose sortBy parameter contains characters outside the safe identifier set.
- Monitor application error logs for PostgreSQL syntax errors originating from the asset listing code path, which often indicate injection probing.
- Track per-user request rates to /assets and flag accounts that generate anomalous query volume across short windows.
How to Mitigate CVE-2026-44204
Immediate Actions Required
- Upgrade all Shelf instances to version 1.20.1 or later without delay.
- Audit PostgreSQL audit logs and application logs for sortBy payloads containing SQL metacharacters since the upgrade window opened.
- Rotate any secrets, tokens, or credentials stored in the Shelf database if injection activity is suspected.
- Review tenant data access in multi-organization deployments to determine whether cross-tenant reads occurred.
Patch Information
The fix is included in Shelf 1.20.1. The remediation commit 5d35c15 introduces the isSafeSqlIdentifier allowlist utility in apps/webapp/app/utils/sql.ts and applies it within apps/webapp/app/modules/asset/filter-parsing.ts before any user-supplied identifier reaches Prisma.raw(). Details are documented in the GitHub Security Advisory GHSA-69xv-wmgg-3qp3 and the upstream commit.
Workarounds
- If patching is not immediately possible, deploy a WAF or reverse proxy rule that rejects /assets requests where sortBy does not match ^[a-zA-Z_][a-zA-Z0-9_]*$.
- Restrict access to the Shelf application to trusted networks or VPN users until the upgrade is completed.
- Reduce the database role used by Shelf to the minimum privileges required, limiting the blast radius of any remaining injection paths.
# Example NGINX rule to reject unsafe sortBy values on /assets
location /assets {
if ($arg_sortBy !~ "^[a-zA-Z_][a-zA-Z0-9_]*$") {
return 400;
}
proxy_pass http://shelf_upstream;
}
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.


