Build1 distinct publisher3 min readPublished
The advisory pins CVE-2026-24031 on admins who cleared auth_username_chars, but a source comparison of 2.4.2 and 2.4.3 traces it to a regression in the settings layer. That is why the fix is a package, not a config edit.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
Follow any of these and your For You feed starts watching them — no settings page required.
build
Object or file storage: the wrong pick stays quiet until inode exhaustion1 distinct publisher
build
Fifteen shipped Laravel apps split evenly on whether the database enforces relationships1 distinct publisher
build
Sling's incremental mode leaves five cancelled orders alive in the warehouse1 distinct publisher
build
Once the question needs a cube, you own the parser1 distinct publisher
Two layers each assumed the other was doing the escaping. The username a client supplies at login gets reused in SQL and LDAP queries and in file paths through `%u`, `%n` and `%d` substitution [10]. `auth_username_chars` filters the bytes. The SQL passdb has its own escape hook, because filtering allowed bytes is the wrong tool for quoting a query string built from a template like `SELECT username, password FROM users WHERE username = '%u'` [11]. Both defences exist, but the source comparison shows only one of them was actually running.
The whitelist literal in `src/auth/auth-settings.c` is 67 characters long but sets 66 distinct entries in the 256-byte map, because the digit run is written `01234567890` and repeats the zero [5][17]. So the default rejects 190 of the 256 possible byte values [18]. Clearing the setting flips all 190 to accepted in a single `memset` [7]. The empty-string branch even carries the comment `/* all chars are allowed */`, so the inversion is documented, just not anywhere an administrator would read it [7].
Now look at the caller. `sql_lookup_pass` fills `struct settings_get_params` with `.escape_func = passdb_sql_escape` and the database handle as context, and that function calls the driver's `sql_escape_string()` for MySQL, PostgreSQL or SQLite [12][13]. The header documents the contract: if the function is non-NULL, all `%variables` are escaped with it [14]. Anyone reviewing `passdb-sql.c` in isolation signs it off. According to the dev.to analysis, the request is discarded one layer down in `settings_var_expand_init()` in `src/lib-settings/settings.c`, where the settings system can take a var-expand escape function from two different places [15].
That distinction decides what you do this week. Read the advisory literally and the remedy is to put the whitelist back, since a cleared `auth_username_chars` is what the one-line summary blames [1]. Under the regression reading it is only the precondition, and restoring the whitelist removes the trigger on the path you already thought about while leaving the dropped escape in place [3]. The material we have does not enumerate which other callers pass an `escape_func` through the same code, so I would not assume the SQL passdb is the only one, and I would read the 2.4.2-to-2.4.3 diff before deciding otherwise [4].
There is a cheap local check. `sql_lookup_pass` logs the fully expanded query at debug level before handing it to `sql_query()` [16]. On a 2.4.2 build you can send a username containing a quote and see in the auth debug log whether it arrived escaped. That check settles which reading applies to your binary, but settling it does not patch anything.
For the misconfiguration framing to be the whole story, the escape hook would have to have been honoured, in which case clearing the whitelist would still be a question about file paths and `%u` in filenames but not an authentication bypass [10]. The analysis says it was not honoured [3]. Treat `auth_username_chars` as an input-shape policy, keep it populated because unicode support is not worth an unfiltered byte range, and take the package.
Ranked by verification strength, evidence, and original report placement.
Dovecot's official security advisory OXDC-2026-0001, covering CVE-2026-24031, summarises the issue in one line: "Dovecot SQL based authentication can be bypassed when auth_username_chars is cleared by admin."
The same advisory line states the vulnerability allows bypassing authentication for any user and user enumeration.
The dev.to analysis states that the actual root cause is a regression introduced during the 2.4 settings-system refactor, where an explicitly-requested SQL escape function is silently discarded, and that auth_username_chars being cleared is only the precondition that lets the bug be triggered in practice.
The analysis is based on a direct clone of the dovecot/core repository, comparing the vulnerable 2.4.2 source with the patched 2.4.3 source.
The default value in src/auth/auth-settings.c is .username_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@", allowing only letters, digits and . - _ @.
SQL metacharacters such as a single quote or a semicolon are not part of the default auth_username_chars whitelist at all.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · September 2, 2026
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Checkable code, one checker
Almost every load-carrying statement here is a quotation from a file a reader can open — the default whitelist literal, the memset branch, the passdb's escape request, the header comment, the precedence diff. That is unusually verifiable for a single self-published post. What is missing is a second pair of eyes: no vendor confirmation of the regression reading, no independent reproduction, and the one claim about how administrators actually configure the setting is asserted rather than shown.
Fixed upstream, exposure unknown
The concrete uptake we can point to stops at the project itself: an advisory exists and the precedence fix is in 2.4.3. Beyond that, nothing — no exploitation reports, no distribution package timelines, no sense of how many installations ever cleared auth_username_chars and therefore sit in the vulnerable configuration. A patch that shipped is not the same as a patch that landed.
Advisory undersells; the analysis doesn't oversell
The direction of distortion runs backwards from the usual. One official line puts the flaw on an administrator's config choice, while the quoted diff shows a library discarding an escape function it documented as guaranteed — a difference that changes the remedy from an edit to an upgrade. dev.to could have made a meal of that and mostly doesn't: it concedes the cleared setting is the precondition and stops where the code stops. The small negative reflects a story whose consequence is larger than its billing, tempered by the fact that its exploitability premise is unmeasured.
Blogger correcting a vendor
Two interests are in the room and neither is hidden. A vendor summarising its own regression as an admin's cleared setting has an obvious preference for that framing, and an independent author gains standing by publishing the correction with the diff attached. Neither party is selling a product in this text, and the code excerpts are falsifiable, which caps how far either incentive can bend the account.
Solid on mechanism, thin on reach
Confidence splits cleanly. On how the bug works — inverted empty-string semantics, an inert byte filter, a dropped escape callback, a three-line precedence fix — the quoted source makes this about as firm as a single-source story gets. On who is actually exposed and whether anyone has been hit, we are working from one sentence of unverified field practice. Believe the mechanism; hold the blast radius open.