A state_dir holding %, ? or # breaks the database URL pixa derives from it #290

Closed
opened 2026-10-16 02:11:49 +02:00 by clawbot · 2 comments
Collaborator

Where: internal/config/config.go, where db_url is derived from state_dir when it is omitted: fmt.Sprintf("file:%s/state.sqlite3?_pragma=journal_mode(WAL)", c.StateDir), on next at 1fd5cf8. Found while working on #288.

What is wrong: the directory is put into a file: URL as it is, and the database driver reads that URL with percent-escapes decoded and with ? starting the parameters. So a state_dir holding % opens a different path or fails, and one holding ? or # cuts the path short and loses the journal mode setting.

How it fails: an operator sets state_dir (or PIXA_STATE_DIR) to a directory whose name holds one of those characters; pixa fails to start or keeps its database somewhere else than the state directory.

Severity: low

Definition of done:

  1. The derived db_url names <state_dir>/state.sqlite3 exactly, whatever characters the directory holds, with the journal mode parameter kept.
  2. Test first, for a state_dir holding %, ? and #; make check green.

Package: internal/config.

Model: opus-5-5

Where: `internal/config/config.go`, where `db_url` is derived from `state_dir` when it is omitted: `fmt.Sprintf("file:%s/state.sqlite3?_pragma=journal_mode(WAL)", c.StateDir)`, on `next` at `1fd5cf8`. Found while working on https://git.eeqj.de/sneak/pixa/issues/288. What is wrong: the directory is put into a `file:` URL as it is, and the database driver reads that URL with percent-escapes decoded and with `?` starting the parameters. So a `state_dir` holding `%` opens a different path or fails, and one holding `?` or `#` cuts the path short and loses the journal mode setting. How it fails: an operator sets `state_dir` (or `PIXA_STATE_DIR`) to a directory whose name holds one of those characters; pixa fails to start or keeps its database somewhere else than the state directory. Severity: low Definition of done: 1. The derived `db_url` names `<state_dir>/state.sqlite3` exactly, whatever characters the directory holds, with the journal mode parameter kept. 2. Test first, for a `state_dir` holding `%`, `?` and `#`; `make check` green. Package: `internal/config`. Model: opus-5-5
clawbot self-assigned this 2026-10-16 02:11:55 +02:00
Author
Collaborator

Plan. The SQLite driver pixa uses takes everything after the first ? as its own parameters, and SQLite reads the path of a file: URL up to the first ? or #, turning each %HH into the byte it stands for. So the derived db_url will be built with Go's net/url: a url.URL with scheme file, the path state_dir followed by /state.sqlite3, and the query _pragma=journal_mode(WAL). Its String() writes a %, ? or # in the directory name as %25, %3F and %23, which SQLite decodes back, so it opens exactly that file. OmitHost is set so the URL still reads file:/var/lib/pixa/state.sqlite3?... and a relative state_dir is not taken for a host name. The directory is used as given, not cleaned, so .. resolves the way the operating system resolves it.

A failing test goes first. It makes a temporary directory whose name holds %41, ? and #, sets state_dir to it, opens the derived db_url with the driver, and checks that the database is the file state.sqlite3 in that directory and in WAL mode. No existing test changes.

Model: opus-5-5

Plan. The SQLite driver pixa uses takes everything after the first `?` as its own parameters, and SQLite reads the path of a `file:` URL up to the first `?` or `#`, turning each `%HH` into the byte it stands for. So the derived `db_url` will be built with Go's `net/url`: a `url.URL` with scheme `file`, the path `state_dir` followed by `/state.sqlite3`, and the query `_pragma=journal_mode(WAL)`. Its `String()` writes a `%`, `?` or `#` in the directory name as `%25`, `%3F` and `%23`, which SQLite decodes back, so it opens exactly that file. `OmitHost` is set so the URL still reads `file:/var/lib/pixa/state.sqlite3?...` and a relative `state_dir` is not taken for a host name. The directory is used as given, not cleaned, so `..` resolves the way the operating system resolves it. A failing test goes first. It makes a temporary directory whose name holds `%41`, `?` and `#`, sets `state_dir` to it, opens the derived `db_url` with the driver, and checks that the database is the file `state.sqlite3` in that directory and in WAL mode. No existing test changes. Model: opus-5-5
Author
Collaborator

Built as planned in #292. One difference from the plan: the URL is built in a small function defaultDBURL next to newFromSmartConfig, which would otherwise have gone over the linter's function length limit.

Model: opus-5-5

Built as planned in https://git.eeqj.de/sneak/pixa/pulls/292. One difference from the plan: the URL is built in a small function `defaultDBURL` next to `newFromSmartConfig`, which would otherwise have gone over the linter's function length limit. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/pixa#290