Context
Area
akroasis fjall codec layer — declarative, config-driven middleware that consults a static BTreeSet<(table, column)> map and applies ChaCha20-Poly1305 to specified fields before write / after read. Closes the gap where filesystem access to the fjall store reveals unencrypted signal metadata.
Severity
low — incremental hardening. Today akroasis has at-rest passphrase encryption on the fjall store but no per-field encryption; an attacker with filesystem-level access can read mesh signal metadata directly. Filing as a small enhancement, not a structural redesign.
Evidence
External design prior: thunderbird/thunderbolt src/db/powersync/middleware/EncryptionMiddleware.ts + src/db/encryption/config.ts:encryptedColumnsMap.
encryptedColumnsMap: {
"signals": ["payload", "metadata"],
"vault": ["secret"],
"messages": ["body"]
}
Middleware intercepts each write, looks up the table-column pair, applies AES-GCM (Thunderbolt) or ChaCha20-Poly1305 (akroasis equivalent — already in the akroasis crypto stack) before passing through to the storage layer. Reads do the inverse.
The key insight: which columns are encrypted is a single map separate from the middleware. Adding an encrypted column is a one-line change to the map; no code modification.
Conflict
- Adjacent to AK1 (akroasis#17 hybrid PQ key wrapping) — that issue handles distributing keys across devices; this issue handles per-field encryption with the resulting keys.
- No conflict with existing akroasis issues.
Why it matters
- Filesystem-level threat surface. A laptop seizure scenario with akroasis running locally exposes signal metadata if the fjall store is at-rest only. Per-field encryption mitigates.
- Pattern is generic. A
BTreeSet<(TableId, ColumnId)> + a ColumnCodec trait covers this in <100 LOC of Rust.
- akroasis already has ChaCha20-Poly1305. No new crypto crate; just a codec layer over existing storage.
Done criteria
akroasis (likely the fjall-store wrapper crate): ColumnCodec trait with encrypt(field: &[u8], context: &EncryptionContext) -> Vec<u8> and inverse.
- Static
ENCRYPTED_FIELDS: BTreeSet<(TableId, FieldId)> lives in a single canonical place; not scattered across modules.
- Storage-layer write path consults the map; no opt-in per-call API.
- Read path does the inverse before returning to the consumer.
- Migration: existing un-encrypted data is re-encrypted on first write after the codec lands; or a one-shot migration tool wraps existing rows.
- Test: a synthetic fjall write of a mapped field produces ciphertext on disk, plaintext through the codec read path.
Source
thunderbird/thunderbolt src/db/powersync/middleware/EncryptionMiddleware.ts (read 2026-04-25). Sister to akroasis#17 (AK1 hybrid key wrapping).
Filed 2026-04-25 from thunderbolt deep-dive.
Provenance
Originally filed on the kanon forge as issue #18 on 2026-04-25T22:22:43.405368579-05:00[America/Chicago]. Recovered from 2026-05-09 pre-brick restic backup. Forge URL no longer reachable post firmware brick.
Severity
P1
Context
Area
akroasisfjall codec layer — declarative, config-driven middleware that consults a staticBTreeSet<(table, column)>map and applies ChaCha20-Poly1305 to specified fields before write / after read. Closes the gap where filesystem access to the fjall store reveals unencrypted signal metadata.Severity
low — incremental hardening. Today akroasis has at-rest passphrase encryption on the fjall store but no per-field encryption; an attacker with filesystem-level access can read mesh signal metadata directly. Filing as a small enhancement, not a structural redesign.
Evidence
External design prior:
thunderbird/thunderboltsrc/db/powersync/middleware/EncryptionMiddleware.ts+src/db/encryption/config.ts:encryptedColumnsMap.Middleware intercepts each write, looks up the table-column pair, applies AES-GCM (Thunderbolt) or ChaCha20-Poly1305 (akroasis equivalent — already in the akroasis crypto stack) before passing through to the storage layer. Reads do the inverse.
The key insight: which columns are encrypted is a single map separate from the middleware. Adding an encrypted column is a one-line change to the map; no code modification.
Conflict
Why it matters
BTreeSet<(TableId, ColumnId)>+ aColumnCodectrait covers this in <100 LOC of Rust.Done criteria
akroasis(likely the fjall-store wrapper crate):ColumnCodectrait withencrypt(field: &[u8], context: &EncryptionContext) -> Vec<u8>and inverse.ENCRYPTED_FIELDS: BTreeSet<(TableId, FieldId)>lives in a single canonical place; not scattered across modules.Source
thunderbird/thunderboltsrc/db/powersync/middleware/EncryptionMiddleware.ts(read 2026-04-25). Sister to akroasis#17 (AK1 hybrid key wrapping).Filed 2026-04-25 from
thunderboltdeep-dive.Provenance
Originally filed on the kanon forge as issue #18 on 2026-04-25T22:22:43.405368579-05:00[America/Chicago]. Recovered from 2026-05-09 pre-brick restic backup. Forge URL no longer reachable post firmware brick.
Severity
P1