Stop target credentials leaking into event databases (closes #206) (#223)
All checks were successful
check / check (push) Successful in 3m34s
All checks were successful
check / check (push) Successful in 3m34s
GORM's association upsert copied whole targets rows -- plaintext credential-bearing config -- into the per-webhook event databases with an empty webhook_id. The leak was in updateDeliveryStatus, not the create path: Update leaves Statement.Model pointing at a Delivery whose Target the engine populated, so save_before_associations upserts it. A connection-level callback now appends clause.Associations to Statement.Omits on the create and update chains of every per-webhook connection, so every write path is covered rather than one call site. Existing files are swept on first open: the leaked rows are deleted and the file is VACUUMed, because DELETE alone only unlinks the pages and leaves the credential recoverable in the file's free space. The sweep is recorded in PRAGMA user_version only after the VACUUM returns, so a sweep that fails or is interrupted fails the open and is retried on the next one, rather than being marked done. Encryption of target config at rest is deliberately out of scope and deferred to #212.
This commit was merged in pull request #223.
This commit is contained in:
30
README.md
30
README.md
@@ -506,14 +506,28 @@ backups at rest and restrict who can read them.
|
||||
- `events-{uuid}.db` and `archive-{uuid}.db` hold the **full payload
|
||||
body and headers** of every event as received, including whatever the
|
||||
sending service put in them — tokens, signatures, personal data.
|
||||
- Until
|
||||
[issue #206](https://git.eeqj.de/sneak/webhooker/issues/206) is fixed,
|
||||
the event databases **also contain target credentials**: a GORM
|
||||
association upsert on the delivery and retry write path copies
|
||||
`targets` rows, `config` included, into the per-webhook database. For
|
||||
a Slack target the `webhookUrl` *is* the bearer credential, and an
|
||||
`http` target's URL can embed userinfo. Handing someone an
|
||||
`events-*.db` today hands them live delivery destinations.
|
||||
- Event databases written before
|
||||
[issue #206](https://git.eeqj.de/sneak/webhooker/issues/206) was fixed
|
||||
**also contain target credentials**: a GORM association upsert on the
|
||||
delivery and retry write path copied `targets` rows, `config`
|
||||
included, into the per-webhook database. For a Slack target the
|
||||
`webhookUrl` *is* the bearer credential, and an `http` target's URL
|
||||
can embed userinfo. This version never writes those rows; the first
|
||||
time it opens such a file it deletes them and vacuums the file, which
|
||||
removes the credential bytes rather than only unlinking the rows.
|
||||
Deleting alone would not: the bytes stay readable in the file's free
|
||||
pages until it is rewritten. The sweep is recorded in the file's
|
||||
`user_version` only once the vacuum returns, so a sweep that fails or
|
||||
is interrupted fails the open and is retried on the next one, and a
|
||||
file this version has opened without error holds no leaked rows and
|
||||
no recoverable bytes from them. On upgrade this rewrites each
|
||||
existing `events-{uuid}.db` once, on its first open. Two cases still
|
||||
hand over live delivery destinations: a backup taken from an older
|
||||
build, and a backup of a file this version has not yet opened
|
||||
successfully. Copies already made stay affected — the sweep only
|
||||
rewrites the file it opens, and freed blocks may persist in
|
||||
filesystem snapshots and on the underlying storage. Rotate any target
|
||||
credential that was in a backup you cannot account for.
|
||||
- `webhooker.db` stores target config **unencrypted**, tracked at
|
||||
[issue #212](https://git.eeqj.de/sneak/webhooker/issues/212), next to
|
||||
the session encryption key and the Argon2id password hashes.
|
||||
|
||||
Reference in New Issue
Block a user