lacs-project / lacs-project/sysknife

open_read_only writes: audit verify and audit export migrate the database and fail on a read-only copy

Open
#267 8 comments 0 reactions 0 assignees View on GitHub
bug help wanted medium
Dominant language
Rust
Stars
12
Forks
19
Avg merge
18h 57m
Merged PRs (30d)
116

Description

`TransactionStore::open_read_only` (`crates/sysknife-daemon/src/transactions.rs:331`) calls `initialize()`, and `initialize()` opens a write transaction, creates `schema_migrations` if absent, applies pending migrations, and sets `journal_mode=WAL` on the way in:

```rust
pub fn open_read_only(path: impl AsRef) -> Result {
...
store.initialize()?;
```

```rust
let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
... "CREATE TABLE IF NOT EXISTS schema_migrations ("
```

So all three commands that claim to read the audit chain without touching it, `sysknife audit verify`, `sysknife audit export` and `sysknife audit checkpoint`, write to it.

## Why it matters

Four consequences, in rising order of how much they hurt:

1. `-wal` and `-shm` sidecars appear next to a database the operator only read.
2. A newer CLI silently upgrades an older audit database's schema and inserts a `schema_migrations` row.
3. An older CLI refuses outright: `initialize()` returns `sqlite schema version N is newer than this binary supports`, so a binary that could read all 17 columns fine will not read any.
4. A read-only copy fails. `PRAGMA journal_mode=WAL` on a 0400 file or a read-only mount returns `attempt to write a readonly database`, so the offline-auditor workflow `docs/the-audit-chain.md` advertises for `audit export` cannot run on the artifact an auditor would actually be handed.

There is also a contention path: `BEGIN IMMEDIATE` takes a write lock with a 5s busy timeout, so `sysknife audit export > rows.json` in cron against a busy daemon intermittently exits 4 after the shell has already truncated the output file.

`ensure_private_dir` is not part of this problem: it creates a missing directory at 0700 and leaves an existing one alone.

## Scope

Give the read paths a genuinely read-only open: `OpenFlags::SQLITE_OPEN_READ_ONLY`, no pragma writes, no `initialize()`, and a schema-version check by `SELECT` rather than by running migrations. Keep `open_with_key` as it is, since the daemon does need to migrate.

## Tests first

The failing test is short: write a database with `open_with_key`, `chmod 0400` it, then open it read-only and read the chain rows. That fails today with a write error, and no existing test covers a non-writable database.

## Difficulty

`medium`. The open path is small, but the schema-version check has to move from migration to inspection.

## Getting started

[CONTRIBUTING.md](https://github.com/lacs-project/sysknife/blob/main/CONTRIBUTING.md) has the build and test commands, and [docs/developer-guide.md](https://github.com/lacs-project/sysknife/blob/main/docs/developer-guide.md) covers the setup steps and how to reproduce each required check locally. No CLA and no copyright waiver. The project is MIT.

Contributor guide

Open the contributing guide

Research direction

Start in crates/sysknife-daemon/src/transactions.rs at TransactionStore::open_read_only and compare it with open_with_key and initialize(). Add a test that creates a database with open_with_key, makes it 0400, opens it read-only, and reads the chain rows. Done means audit verify, export, and checkpoint use read-only flags and SELECT-based schema inspection without migrations, pragma writes, or WAL sidecars.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, sqlite
Domain
cli, databases, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.