October 12, 2026·6 min read·engineeringrqlitesecurityauthenticationapi

rqlite 10.6.0 rejects incomplete authentication entries

rqlite 10.6.0 fixes permission inheritance caused by reusing a decoded credential object. It also adds structured statement errors with numeric SQLite codes and selectable response formats.

A missing field in an authentication file must not acquire its meaning from the previous user. rqlite 10.6.0, released on 12 October 2026, fixes a loader that could do exactly that. Reusing a credential object while decoding successive entries allowed an omitted field to retain an earlier value. The release rejects incomplete entries and creates a fresh object for each decode.

The release changelog scopes the security issue to misconfigured authentication files. Correctly configured files are not affected by the reported condition. That scope matters, but so does the parser's response to a configuration mistake. An authorization policy should not silently derive permissions from the ordering of unrelated user records.

I have worked with rqlite for about a year and value how little operational machinery it requires for replicated relational storage. Configuration is part of that appeal. A small authentication file is manageable only if its records have independent, explicit meanings. This repair makes that independence part of the loader's behavior.

One decoded object carried state between entries

The security patch moves the credential variable inside the decoding loop. Previously, the same object received each JSON entry. Fields absent from a later entry could therefore remain populated from the earlier decode. The maps subsequently built for the later username could contain permissions that its own record never supplied.

An illustrative malformed input makes the ordering problem concrete. The passwords below are explanatory placeholders, and this is deliberately not a valid configuration to deploy.

[
  {
    "username": "first-user",
    "password": "example-only-first",
    "perms": ["all"]
  },
  {
    "username": "second-user",
    "password": "example-only-second"
  }
]

The second entry omits perms. A reader might interpret that omission as no grant or as an invalid record. Neither interpretation authorizes carrying the first user's grants forward. The patch's fresh-object allocation prevents the decoder's previous state from becoming an implicit input to the next user's policy.

The distinction is between decoding a JSON object and validating an authentication record. A JSON decoder can successfully map the members present in an object without establishing that every field required by the application appeared. The application must define what missing information means before accepting the record as policy.

This also explains why changing only serialization tags would be insufficient. The problem concerns values in a destination object during decoding and the subsequent construction of authorization maps. Required-field rules need an explicit enforcement step at that boundary.

The loader now requires a complete policy record

The tagged credential loader rejects an empty username, an empty password for an ordinary user, and a permissions list with no entries. Its special all-users entry is exempt from the password requirement because that policy applies to requests without checking an individual password. The exemption has a defined purpose within the authorization model.

These checks change the treatment of omission from accidental inheritance to an error. They also reject an explicitly empty permissions array. Those two input forms are distinct during decoding, but the new validation treats both as insufficient to define an accepted credential entry.

That behavior has an upgrade consequence for installations carrying inactive users with no permissions in their files. Such an entry is no longer an accepted way to describe a credential. Its presence needs a deliberate policy decision before the file can satisfy the new loader's requirements.

The important property is independence from neighboring records. Reordering valid user entries should not change which permissions each entry declares. An incomplete record must not gain a different effective policy simply because an administrator placed a more privileged user immediately before it.

The regression tests exercise omissions after valid records

The credential-store tests include missing username, missing password, missing permissions, and empty permissions cases. They also place incomplete records after a valid record to check that later fields do not inherit previous values. That second arrangement targets the original cross-entry condition directly.

A single malformed record at the beginning of a file is insufficient to expose this bug. There must first be a previous value available to retain. The test arrangement therefore matters as much as the missing field. It constructs the state that made object reuse unsafe for independent credentials.

This is a useful difference between syntax testing and policy testing. A parser test can establish that the JSON is well formed. A policy test establishes that a particular incomplete entry fails under the application's rules, including when earlier input has populated a reused destination.

The tests also separate password inheritance from permission inheritance. Both are consequences of retaining fields, but their security effects differ. Checking one field's omission alone leaves the other behavior unproved, even though the implementation repair uses the same fresh-object boundary.

Configuration generators face the same distinction as hand-edited files. A generator that conditionally omits empty properties can produce valid JSON while failing the credential format's requirements. Its expected output needs to describe a complete accepted user record, or deliberately omit the user altogether according to the deployment's policy. Merely checking that a generated file parses does not establish that the authorization loader will accept it.

The most useful failure report identifies the incomplete record without borrowing missing content from elsewhere in the document. That keeps the administrative correction local to the user entry that actually lacks information. It also prevents an apparently harmless reordering of generated output from changing the permissions implied by the file.

Statement errors gain a structured representation

Version 10.6.0 also adds error_v2 for statement failures. The JSON encoding definitions give that object a message and optional SQLite information. The SQLite portion can include a primary code, an extended code, and a system error number. It appears when the failure originated in SQLite.

This gives clients a way to classify an engine failure without treating the English message as a protocol. Messages remain useful to a person investigating the request. Numeric categories let code distinguish the classes of failure for which the application has an explicit response.

SQLite's result-code documentation defines the relationship between primary and extended codes. The primary code occupies the low eight bits and describes a broad category. Extended codes supply more detail. Existing numeric meanings are stable, while new codes can be added, so clients still need a fallback for values they do not recognize.

That fallback is part of a robust client design. A newly recognized engine condition should not become success merely because it is absent from a client's local enumeration. The structured object communicates an error even when its detailed category requires a newer client to interpret fully.

Clients can select the error representation

The error-format change introduces the errors query parameter. errors=v1 selects the legacy string field, while errors=v2 selects the structured form. The default renders both. This permits a client to adopt structured failures without requiring every existing consumer to change its parser at the same instant.

The HTTP tests check the formats across execute, query, and combined request endpoints. Their missing-table case has the following structured result when v2 is selected.

{
  "results": [
    {
      "error_v2": {
        "message": "no such table: foo",
        "sqlite": {
          "code": 1,
          "extended_code": 1
        }
      }
    }
  ]
}

This is a statement result inside the response. The tests retain a successful HTTP status for the response carrying that failed statement. A client that checks only the transport status misses the database outcome, regardless of whether it chooses the old or new error representation.

The structured field also does not imply that every failure has a SQLite code. Authentication rejection and an engine statement error occur at different points in request handling. A parser should preserve that distinction and inspect the fields actually present instead of manufacturing an engine code for every unsuccessful request.

Explicit records improve both boundaries

The authentication repair and error-format addition address different problems, but each replaces an ambiguous interpretation with a more explicit representation. Credential validation establishes whether a record defines an acceptable policy. Structured statement failures give callers fields whose meaning is defined independently of message wording.

For me, the security repair takes priority in assessing this release. Its behavior can be stated precisely. Every accepted ordinary-user record supplies its own username, password, and nonempty permissions list. A later record cannot obtain those values from an earlier decode, and a client can separately adopt the new statement-error fields when its response handling is ready.