CVE-2026-66838 Overview
CVE-2026-66838 is a SQL Injection vulnerability [CWE-89] in the elixir-ecto/postgrex PostgreSQL driver for Elixir. The flaw affects Postgrex.stream/4 and, by extension, Ecto.Repo.stream/2, which expose a :comment option that is appended directly to the streamed statement without escaping or validation. An attacker who influences the :comment value can close the comment delimiter with */ and append arbitrary SQL clauses to the streamed query. Injected SQL runs under the database connection's role. The issue affects postgrex versions from 0.19.3 before 0.22.4.
Critical Impact
Attacker-controlled :comment input in Postgrex.stream/4 or Ecto.Repo.stream/2 enables SQL clause injection into the streamed statement, allowing data disclosure or modification within the executing statement's scope.
Affected Products
- elixir-ecto/postgrex versions 0.19.3 through 0.22.3
- Ecto.Repo.stream/2 in applications using affected postgrex versions
- Elixir applications passing user-influenced values to the :comment option
Discovery Timeline
- 2026-08-07 - CVE-2026-66838 published to NVD
- 2026-08-07 - Last updated in NVD database
Technical Details for CVE-2026-66838
Vulnerability Analysis
The vulnerability resides in Postgrex.stream/4. Postgrex constructs the PostgreSQL Parse message by concatenating the caller-supplied :comment string into the statement text as an SQL block comment. The comment is inserted without escaping or rejecting the closing delimiter */. When an attacker controls the :comment value, they can terminate the comment early and append additional SQL clauses that become part of the parsed statement.
Every other execution path in Postgrex validates the option using comment_not_present!/1, which rejects use of the :comment option in contexts where injection would be possible. stream/4 omitted this check, leaving the streaming path exposed. Because the PostgreSQL Parse message accepts a single command, the injection is confined to the currently streamed statement, and stacked statements cannot be chained through this vector.
Exposure through Ecto.Repo.stream/2 broadens reach because Ecto forwards the same option to Postgrex. Any application that passes untrusted input into the :comment option, directly or indirectly, is susceptible.
Root Cause
The root cause is missing input validation on the :comment option in the streaming code path. Postgrex.stream/4 failed to invoke comment_not_present!/1, which is the guard used by every other query execution function. The comment string is concatenated into the Parse message, so an embedded */ terminates the SQL comment and allows appended tokens to be parsed as SQL.
Attack Vector
Exploitation requires the attacker to influence the value passed to the :comment option of Postgrex.stream/4 or Ecto.Repo.stream/2. The injected SQL executes under the privileges of the database role bound to the connection. Impact is limited to the single streamed statement, so data disclosure or manipulation is constrained to what one PostgreSQL command can express, such as adding a UNION SELECT or manipulating WHERE clauses of a compatible streamed query.
Postgrex.Stream.t()
when option: execute_option | {:max_rows, pos_integer}
def stream(%DBConnection{} = conn, query, params, options \\ []) do
+ comment_not_present!(options)
options = Keyword.put_new(options, :max_rows, @max_rows)
%Postgrex.Stream{conn: conn, query: query, params: params, options: options}
end
Source: GitHub Postgrex Commit Fix. The patch adds a call to comment_not_present!(options) at the top of stream/4, bringing the streaming path in line with the validation used by every other execution function.
Detection Methods for CVE-2026-66838
Indicators of Compromise
- PostgreSQL server logs containing streamed statements whose trailing SQL comment includes */ followed by additional SQL tokens.
- Unexpected UNION, SELECT, or clause fragments appearing after /* ... */ markers in parsed statements.
- Application logs showing user-controlled data flowing into a :comment keyword passed to Postgrex.stream/4 or Ecto.Repo.stream/2.
Detection Strategies
- Perform static code review across Elixir codebases for calls to Postgrex.stream/4 and Ecto.Repo.stream/2 that pass a :comment option sourced from request parameters, headers, or external data.
- Enable PostgreSQL log_statement = 'all' in non-production environments and scan captured statements for */ occurring inside SQL comment blocks appended to streamed queries.
- Audit dependency manifests (mix.lock) for postgrex versions between 0.19.3 and 0.22.3.
Monitoring Recommendations
- Alert on PostgreSQL query patterns that combine SQL block comments with clause keywords such as UNION, ORDER BY, or WHERE immediately following */.
- Baseline the SQL statements emitted by streaming endpoints and flag deviations in comment structure or trailing tokens.
- Track dependency updates for postgrex and ecto and alert when vulnerable versions remain in production.
How to Mitigate CVE-2026-66838
Immediate Actions Required
- Upgrade postgrex to version 0.22.4 or later, which adds the missing comment_not_present!/1 call to stream/4.
- Remove any user-controlled input from the :comment option in calls to Postgrex.stream/4 and Ecto.Repo.stream/2 until the upgrade is deployed.
- Rotate database credentials if evidence indicates the streaming path processed attacker-controlled comments.
Patch Information
The fix is delivered in postgrex0.22.4 via commits 4011be852c99dc61ddb98cb01aa41e8775a0e3dd and e1ecba618ddea4cee2556bd6ad9b6285e05f9d3c. Both patches add comment_not_present!(options) to stream/4, causing streams that include a :comment option to fail fast rather than concatenating the value into the statement text. Full advisory details are available in the GitHub Security Advisory GHSA-3gww-3f36-2388 and the CNA advisory for CVE-2026-66838.
Workarounds
- Refuse to pass a :comment option to Postgrex.stream/4 or Ecto.Repo.stream/2 in application code paths that cannot be immediately upgraded.
- If comment annotation is required for observability, hard-code allowlisted static strings and reject any value containing */.
- Restrict the PostgreSQL role used by application connections to the minimum privileges required, limiting the scope of any injected clause.
# Update the postgrex dependency in mix.exs
# Then run:
mix deps.update postgrex
mix deps.get
mix compile
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

