GHSA-rfx3-98h7-v3xp

Suggest an improvement
Source
https://github.com/advisories/GHSA-rfx3-98h7-v3xp
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-rfx3-98h7-v3xp/GHSA-rfx3-98h7-v3xp.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-rfx3-98h7-v3xp
Aliases
Published
2026-09-17T14:51:41Z
Modified
2026-09-17T15:00:07Z
Severity
  • 9.1 (Critical) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L CVSS Calculator
Summary
Marten's LINQ provider has SQL injection via unescaped string literals
Details

Several code paths in Marten's LINQ provider and tenant-management internals interpolated a runtime, potentially attacker-influenced value into generated SQL as a single-quoted string literal without escaping or parameterization. A value containing a single quote (') could break out of the literal and inject arbitrary SQL.

The primary vector is a Dictionary<,> indexer key used in a Where filter — Where(x => x.Attributes[key] == v) — a common "filter by attribute name" / EAV pattern. It is confirmed with an executed proof-of-concept and yields filter / multi-tenant authorization bypass (returning other tenants' rows) and blind data exfiltration. Where the application permits ;-batched statements (Npgsql default), data modification is also possible.

Affected sinks

  • Linq/Members/Dictionaries/DictionaryItemMember.cs — dictionary indexer key (primary, confirmed PoC)
  • Linq/Members/Dictionaries/DictionaryContainsKeyFilter.csDictionary.ContainsKey(key) (Newtonsoft serializer only; System.Text.Json escapes the quote)
  • Linq/Parsing/SelectParser.cs — a constant string projected through Select(x => new { L = runtimeString })
  • Internal/Operations/DeleteAllForTenant.cs — tenant id reaching per-tenant projection teardown via IEventStore.DeleteProjectionProgressAsync (an in-code comment wrongly claimed the value was validated)
  • Schema/DatabaseScopedTenantPartitions.cs — tenant id inlined into FOR VALUES IN ('...') partition DDL
  • Events/Daemon/Internals/EventLoader.cs (defense-in-depth) — per-tenant partition-pruning literal

Proof of concept (primary vector)

Benign: x.Attributes["nonexistent-key"] == "v" returns 0 rows. Attack: x.Attributes["nonexistent' = '' or 1=1 --"] == "v" returns all rows. Generated SQL:

select d.data from public.mt_doc_doc as d where d.data -> 'Attributes' ->> 'x' or 1=1 --' = :p0;

Fix

Each sink now escapes embedded single quotes (.Replace("'", "''"), mirroring the existing Ordering.BuildNgramRankExpression) or binds the value as a parameter (DeleteAllForTenant). Where a literal is retained for partition pruning, escaping preserves plan-time pruning while closing the injection. Regression tests lock down each vector.

Workarounds

Until upgrading: do not pass untrusted input as a dictionary indexer key / ContainsKey argument / Select constant, nor as a tenant id into projection teardown or provisioning; and disable multi-statement command batching to limit blast radius.

Credit

Reported privately and responsibly by an external security researcher with an executed PoC. Additional sinks found during the follow-up LINQ-wide audit.

Database specific
{
    "cwe_ids": [
        "CWE-89"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-17T14:51:41Z",
    "nvd_published_at": "2026-09-16T21:17:13Z",
    "severity": "CRITICAL"
}
References

Affected packages

NuGet / Marten

Package

Affected ranges

Type
ECOSYSTEM
Events
Introduced
7.0.0
Fixed
9.13.0

Affected versions

7.*
7.0.0
7.1.0
7.1.1
7.2.0
7.3.0
7.3.1
7.4.0
7.5.0
7.6.0
7.7.0
7.8.0
7.9.0
7.10.0
7.10.1
7.11.0
7.12.0
7.13.0
7.13.1
7.14.0
7.15.0
7.16.0
7.17.0
7.17.1
7.18.0
7.19.0
7.19.1
7.20.0
7.20.1
7.20.2
7.21.0
7.21.1
7.22.0
7.23.0
7.23.1
7.24.0
7.25.0
7.25.1
7.25.2
7.26.0
7.26.1
7.26.2
7.26.3
7.26.4
7.26.5
7.26.6
7.27.0
7.28.0
7.28.1
7.28.2
7.29.0
7.30.0
7.30.1
7.30.2
7.30.3
7.31.0
7.31.1
7.31.2
7.31.3
7.32.0
7.33.0
7.33.1
7.33.2
7.33.3
7.34.0
7.34.1
7.35.0
7.35.1
7.35.2
7.35.3
7.36.0
7.37.0
7.37.1
7.37.2
7.37.3
7.38.0
7.38.1
7.39.0
7.39.1
7.39.2
7.39.3
7.39.5
7.39.6
7.40.0
7.40.1
7.40.2
7.40.3
7.40.4
7.40.5
8.*
8.0.0-alpha-2
8.0.0-alpha-3
8.0.0-alpha-5
8.0.0-alpha-6-
8.0.0-beta-1
8.0.0-beta-2
8.0.0-rc-1
8.0.0-rc-2
8.0.0
8.0.1
8.1.0
8.1.2
8.2.0
8.2.1
8.3.0
8.3.1
8.3.2
8.3.3
8.4.0
8.5.0
8.6.0
8.7.0
8.8.0
8.8.1
8.8.2
8.9.0-channels-alpha-1
8.9.0-channels-alpha-2
8.9.0
8.10.0
8.10.1
8.11.0
8.12.0
8.13.0
8.13.1
8.13.2
8.13.3
8.14.0
8.15.0
8.15.1
8.15.2
8.15.3
8.15.4
8.16.0
8.16.1
8.16.2
8.16.3
8.16.4
8.17.0
8.18.0
8.18.1
8.18.2
8.18.3
8.19.0
8.20.0
8.21.0
8.22.0
8.22.1
8.22.2
8.23.0
8.24.0
8.25.0
8.26.0
8.26.1
8.26.2
8.27.0
8.28.0
8.29.0
8.29.1
8.29.2
8.29.3
8.30.0
8.30.1
8.31.0
8.32.0
8.32.1
8.33.0
8.34.0
8.34.1
8.34.2
8.35.0
8.36.0
8.37.0
8.37.1
8.37.2
8.37.3
8.37.4
8.38.0
8.38.1
9.*
9.0.0
9.0.1
9.0.2
9.2.0
9.2.1
9.3.0
9.3.1
9.3.2
9.3.4
9.3.5
9.4.0
9.5.0
9.5.1
9.5.2
9.5.3
9.6.0
9.7.0
9.7.1
9.7.2
9.7.3
9.7.4
9.7.5
9.7.6
9.8.0
9.8.1
9.8.2
9.9.0
9.9.1
9.10.0
9.11.0
9.12.0

Database specific

last_known_affected_version_range
"<= 9.12.0"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-rfx3-98h7-v3xp/GHSA-rfx3-98h7-v3xp.json"