Skip to content

fix: compile SQLite 3.53.0 from source to fix WAL-reset corruption bug - #6900

Merged
nesquena-hermes merged 3 commits into
nesquena:masterfrom
qxxaa:fix/sqlite-wal-reset-bug
Aug 15, 2026
Merged

nesquena-hermes merged 3 commits into
nesquena:masterfrom
qxxaa:fix/sqlite-wal-reset-bug

Conversation

@qxxaa

@qxxaa qxxaa commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Problem

The python:3.12-slim base image ships SQLite 3.46.1 from Debian Trixie,
which is vulnerable to the WAL-reset corruption bug discovered March 2026.
All three Docker deployment modes (single-container, two-container,
three-container) share this Dockerfile and are affected.

state.db (sessions, messages, FTS indexes) runs in WAL mode by default.
Debian has not backported the fix and shows no indication of doing so.

Ref: https://sqlite.org/wal.html#walresetbug

Fix

Compiles SQLite 3.53.0 from the official amalgamation tarball during the
Docker build. The shared library installs to /usr/local/lib, which takes
ldconfig priority over the system /usr/lib without removing the distro
package.

  • --disable-static --disable-readline keeps compilation minimal
  • Build tools (gcc, make, libc6-dev) are purged in the same layer
  • SQLITE_VERSION and SQLITE_YEAR are build args for easy bumps
  • A build-time Python assertion fails the image if the linked library is
    still vulnerable

Alternatives considered

  • Different base image (e.g. python:3.13-slim on a distro that ships
    3.51.3+, or Alpine which tracks SQLite upstream more closely). Less
    build complexity but more intrusive - changes the package manager,
    available system libraries, and potentially the Python version. Left to
    maintainer's discretion whether a base image bump is preferred over the
    source compile.
  • Wait for Debian backport. No indication this is coming. The bug is
    a silent corruption risk, not a crash, so distro urgency is low.

Verification

  • hermes doctor no longer reports the WAL-reset advisory
  • python3 -c "import sqlite3; print(sqlite3.sqlite_version)" returns 3.53.0
  • Existing Docker regression tests pass
  • New structural tests pin the upgrade invariants against Dockerfile refactors

Image size impact

The compiled libsqlite3.so.3.53.0 is ~5 MB. The build toolchain (~204 MB)
is purged in the same layer and does not persist.

@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The follow-up pins and verifies the SQLite source archive while retaining build-time checks for the linked version, secure deletion, and FTS5 support.

  • Compiles SQLite 3.53.0 into /usr/local for Docker deployment modes.
  • Verifies the archive with a pinned SHA-256 before extraction.
  • Adds structural regression coverage for the Dockerfile upgrade invariants.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
Dockerfile Builds and validates SQLite 3.53.0, verifies its pinned archive checksum, and removes compilation dependencies afterward.
tests/test_sqlite_wal_reset_upgrade.py Adds repository-standard structural assertions covering the SQLite source build, checksum verification, feature flags, runtime guards, and layer ordering.

Reviews (4): Last reviewed commit: "docker(sqlite): compile with SQLITE_SECU..." | Re-trigger Greptile

Comment thread Dockerfile Outdated
Comment thread tests/test_sqlite_wal_reset_upgrade.py
Comment thread Dockerfile
@qxxaa
qxxaa force-pushed the fix/sqlite-wal-reset-bug branch from 8b9ca65 to c3e8958 Compare August 10, 2026 13:18
@nesquena-hermes nesquena-hermes added the size:M Medium PR (≤10 files, ≤250 LOC) label Aug 10, 2026

@nesquena-hermes nesquena-hermes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — the WAL-reset corruption bug is real and worth fixing, and the source-compile approach with a build-time version assertion is a sound shape. CI is green (25/25 incl. the docker-build + all three compose-smoke jobs), but a regression gate on the exact head (c3e8958) surfaced two blockers that the smoke jobs structurally can't catch (they only probe startup + /health), so I'm requesting changes before this can ship.

1. [BLOCKER] The compiled SQLite drops FTS5 → silent full-text search breakage

The sqlite-autoconf amalgamation compiled with the current ./configure --disable-static --disable-readline flags does not enable FTS5. Debian's stock libsqlite3 ships with -DSQLITE_ENABLE_FTS5=1; the from-source build here does not, so the replacement library loses FTS5.

state.db (shared with the Agent in the two/three-container modes) uses FTS5 for session/message full-text recall. With an FTS5-less library:

  • CREATE VIRTUAL TABLE … USING fts5 raises sqlite3.OperationalError: no such module: fts5.
  • The Agent detects this and silently drops FTS triggers + returns empty search results rather than erroring, so it looks fine on boot but session search quietly returns nothing.
  • In multi-container mode, opening the shared state.db from a no-FTS5 process can remove the persisted FTS triggers until a capable process rebuilds them.

This trades a corruption bug for a silent search-loss regression, so it's a net downgrade until FTS5 is restored.

Fix: enable FTS5 in the compile (either --enable-fts5 on ./configure, or CFLAGS="-DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_RTREE" — mirror the feature set Debian's package enables; --all/--enable-all is the simplest way to match the distro build). Then extend the build-time Python assertion to prove FTS5 survived, not just the version:

python3 -c "import sqlite3; \
  v=sqlite3.sqlite_version; \
  assert tuple(int(x) for x in v.split('.')) >= (3,51,3), f'SQLite {v} still vulnerable'; \
  c=sqlite3.connect(':memory:'); c.execute('CREATE VIRTUAL TABLE t USING fts5(x)'); \
  print(f'OK sqlite {v} + fts5')"

The FTS5 vtable creation is the part that would have failed this build as-is.

2. [BLOCKER] Pin the amalgamation SHA-256 before extraction

The tarball is fetched over HTTPS but extracted + its build scripts run as root with no content verification, so an artifact substitution at the origin would execute in every published image. Pin the checksum and verify before tar:

ARG SQLITE_SHA256=<official 3.53.0 amalgamation sha256>
...
    && curl -fsSL "https://sqlite.org/${SQLITE_YEAR}/sqlite-autoconf-${SQLITE_VERSION}.tar.gz" -o sqlite.tar.gz \
    && echo "${SQLITE_SHA256}  sqlite.tar.gz" | sha256sum -c - \
    && tar xzf sqlite.tar.gz \

(Please compute the SHA-256 from the official download yourself rather than trusting a value pasted in review.)

Not blockers

  • The linked-library mechanism itself is verified working: build installs /usr/local/lib/libsqlite3.so.0, ldconfig runs, and the Python version assertion passes on the compiled lib.
  • Build-tool purge-in-same-layer is correct; no separate regression.
  • The structural Dockerfile tests are fine as invariant pins; once you add the FTS5 config, please also assert --enable-fts5 (or the CFLAGS) is present so a future refactor can't silently drop it again.

Once FTS5 is restored (with the build proving an FTS5 vtable creates) and the checksum is pinned, this is a strong reliability fix. Happy to re-gate on the re-push.

@nesquena-hermes nesquena-hermes added the gate-fail Gate found blocking issue(s); fix-spec in comment; awaiting fix/re-push label Aug 10, 2026
@qxxaa

qxxaa commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. Both blockers addressed in 38f89a9:

1. FTS5/FTS4/R-Tree restored

Added --enable-fts5 --enable-fts4 --enable-rtree to ./configure. The
build-time assertion now creates and drops an FTS5 virtual table in
addition to the version check, so the build fails if the module is missing
rather than producing a silently broken image.

2. SHA-256 pinned

The amalgamation tarball is now verified with sha256sum -c before
extraction. The hash is a build arg (SQLITE_SHA256) alongside the
existing version and year args, so version bumps remain self-contained.
SHA-256 was computed from a fresh download of the official archive.

Regression tests updated to cover both: checksum verification before
extraction, --enable-fts5 in the configure flags, and FTS5 vtable
creation in the build-time assertion (9 tests, up from 6).

@qxxaa
qxxaa requested a review from nesquena-hermes August 10, 2026 18:53
qxxaa and others added 3 commits August 15, 2026 02:47
The python:3.12-slim base ships SQLite 3.46.1 (Debian Trixie), which is
vulnerable to the WAL-reset corruption bug discovered March 2026.
Debian has not backported the fix.

Compiles SQLite 3.53.0 from the official amalgamation tarball during the
Docker build. Installs to /usr/local/lib (takes ldconfig priority over
/usr/lib). Build tools (gcc, make, libc6-dev) are purged in the same
layer. A build-time Python assertion fails the image build if the linked
library is still vulnerable.

Version and year are build args for easy bumps.

https://sqlite.org/wal.html#walresetbug
Address review feedback (nesquena-hermes):

1. Add --enable-fts5 --enable-fts4 --enable-rtree to ./configure so the
   compiled SQLite matches the distro package's feature set. Without
   FTS5, state.db session/message full-text search breaks silently.

2. Pin the amalgamation tarball SHA-256 as a build arg and verify with
   sha256sum -c before extraction.

3. Extend the build-time assertion to create and drop an FTS5 virtual
   table, proving the module is available - not just the version number.

4. Add regression tests for checksum verification, FTS5 configure flag,
   and FTS5 build-time vtable assertion.
…-row erasure

Codex gate found a SILENT data-privacy regression: the Debian base image's SQLite is
built with SQLITE_SECURE_DELETE (PRAGMA secure_delete=1, deleted content overwritten),
but compiling 3.53.0 from the amalgamation without the flag drops it to 0 -> deleting a
session removes its rows but leaves transcript bytes recoverable in state.db. Reproduced
end-to-end via api/models.py delete path. Add CPPFLAGS=-DSQLITE_SECURE_DELETE and a
build-time assertion that PRAGMA secure_delete==1 (fails the image build if not).

Co-authored-by: qxxaa <qxxaa@users.noreply.github.com>
@nesquena-hermes
nesquena-hermes force-pushed the fix/sqlite-wal-reset-bug branch from 38f89a9 to a6fb254 Compare August 15, 2026 03:05
@nesquena-hermes
nesquena-hermes dismissed their stale review August 15, 2026 03:14

Stale — on a superseded head (c3e8958). Re-gated the converged head a6fb254: SHA-256 independently verified against sqlite.org, Docker image build + all 3 container smoke tests green, Codex-reviewed. Codex found the build dropped SQLITE_SECURE_DELETE (deleted rows recoverable) — I applied the fix (CPPFLAGS=-DSQLITE_SECURE_DELETE + build-time PRAGMA secure_delete==1 assertion, Co-authored @qxxaa) and CI's Docker build validates it. Dismissing to unblock.

@nesquena-hermes
nesquena-hermes merged commit 1aab1e4 into nesquena:master Aug 15, 2026
25 checks passed
nesquena-hermes added a commit that referenced this pull request Aug 15, 2026
…te preserved) (#6900, @qxxaa) (#7043)

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Shipped in exp-v0.52.227 🎉 Thanks @qxxaa — the Docker image now compiles SQLite 3.53.0 from source to fix the WAL-reset corruption bug. I independently verified the pinned SHA-256 matches the official sqlite.org archive, and the gate caught one thing worth calling out: compiling from the amalgamation dropped Debian's SQLITE_SECURE_DELETE, which would have silently left deleted conversation rows recoverable in state.db. I added CPPFLAGS=-DSQLITE_SECURE_DELETE plus a build-time PRAGMA secure_delete == 1 assertion (Co-authored with you) so the image fails to build if secure-delete ever regresses. Docker image build + all three container smoke tests are green. Live on the experimental image now.

nesquena-hermes added a commit that referenced this pull request Aug 15, 2026
…follow-up to #6900 (#7044)

* fix(docker): register /usr/local/lib in ld.so.conf.d so the source-built SQLite loads on arm64

The #6900 SQLite-from-source upgrade passed the amd64 docker-smoke but FAILED the
multi-arch release build on linux/arm64 with 'AssertionError: SQLite 3.46.1 still
vulnerable' — Python's sqlite3 kept loading the base image's /usr/lib multiarch
libsqlite3 (3.46.1) instead of the freshly-compiled /usr/local/lib copy (3.53.0),
because /usr/local/lib is NOT in the default ld.so search path on Debian arm64
(it happened to be picked up on amd64). Register /usr/local/lib via an
ld.so.conf.d entry before ldconfig so the new lib wins on every architecture.

The PR's own build-time version assertion is what surfaced this (correctly), and
it also serves as the verification that the fix works once the arm64 build passes.

Follow-up to #6900 (exp-v0.52.227, whose multi-arch image failed to build/push).

* test-fix: keep wal.html link within 500 chars of ARG (condense arm64 comment)

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
nesquena-hermes added a commit that referenced this pull request Aug 15, 2026
…) (#7044) (#7045)

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
@qxxaa
qxxaa deleted the fix/sqlite-wal-reset-bug branch August 15, 2026 10:32
alai04 pushed a commit to alai04/hermes-webui that referenced this pull request Aug 31, 2026
nesquena#6900)

* fix: compile SQLite 3.53.0 from source to fix WAL-reset corruption bug

The python:3.12-slim base ships SQLite 3.46.1 (Debian Trixie), which is
vulnerable to the WAL-reset corruption bug discovered March 2026.
Debian has not backported the fix.

Compiles SQLite 3.53.0 from the official amalgamation tarball during the
Docker build. Installs to /usr/local/lib (takes ldconfig priority over
/usr/lib). Build tools (gcc, make, libc6-dev) are purged in the same
layer. A build-time Python assertion fails the image build if the linked
library is still vulnerable.

Version and year are build args for easy bumps.

https://sqlite.org/wal.html#walresetbug

* fix: enable FTS5/FTS4/R-Tree and pin tarball SHA-256

Address review feedback (nesquena-hermes):

1. Add --enable-fts5 --enable-fts4 --enable-rtree to ./configure so the
   compiled SQLite matches the distro package's feature set. Without
   FTS5, state.db session/message full-text search breaks silently.

2. Pin the amalgamation tarball SHA-256 as a build arg and verify with
   sha256sum -c before extraction.

3. Extend the build-time assertion to create and drop an FTS5 virtual
   table, proving the module is available - not just the version number.

4. Add regression tests for checksum verification, FTS5 configure flag,
   and FTS5 build-time vtable assertion.

* docker(sqlite): compile with SQLITE_SECURE_DELETE to preserve deleted-row erasure

Codex gate found a SILENT data-privacy regression: the Debian base image's SQLite is
built with SQLITE_SECURE_DELETE (PRAGMA secure_delete=1, deleted content overwritten),
but compiling 3.53.0 from the amalgamation without the flag drops it to 0 -> deleting a
session removes its rows but leaves transcript bytes recoverable in state.db. Reproduced
end-to-end via api/models.py delete path. Add CPPFLAGS=-DSQLITE_SECURE_DELETE and a
build-time assertion that PRAGMA secure_delete==1 (fails the image build if not).

Co-authored-by: qxxaa <qxxaa@users.noreply.github.com>

---------

Co-authored-by: qxxaa <qxxaa@users.noreply.github.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
alai04 pushed a commit to alai04/hermes-webui that referenced this pull request Aug 31, 2026
…te preserved) (nesquena#6900, @qxxaa) (nesquena#7043)

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
alai04 pushed a commit to alai04/hermes-webui that referenced this pull request Aug 31, 2026
…follow-up to nesquena#6900 (nesquena#7044)

* fix(docker): register /usr/local/lib in ld.so.conf.d so the source-built SQLite loads on arm64

The nesquena#6900 SQLite-from-source upgrade passed the amd64 docker-smoke but FAILED the
multi-arch release build on linux/arm64 with 'AssertionError: SQLite 3.46.1 still
vulnerable' — Python's sqlite3 kept loading the base image's /usr/lib multiarch
libsqlite3 (3.46.1) instead of the freshly-compiled /usr/local/lib copy (3.53.0),
because /usr/local/lib is NOT in the default ld.so search path on Debian arm64
(it happened to be picked up on amd64). Register /usr/local/lib via an
ld.so.conf.d entry before ldconfig so the new lib wins on every architecture.

The PR's own build-time version assertion is what surfaced this (correctly), and
it also serves as the verification that the fix works once the arm64 build passes.

Follow-up to nesquena#6900 (exp-v0.52.227, whose multi-arch image failed to build/push).

* test-fix: keep wal.html link within 500 chars of ARG (condense arm64 comment)

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
alai04 pushed a commit to alai04/hermes-webui that referenced this pull request Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gate-fail Gate found blocking issue(s); fix-spec in comment; awaiting fix/re-push size:M Medium PR (≤10 files, ≤250 LOC)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants