Stop target credentials leaking into event databases (closes #206)
All checks were successful
check / check (push) Successful in 4m26s

A Delivery carries its Event and Target structs in memory for the
delivery engine, so GORM's automatic association save upserted the
whole target row -- config included, which holds destination URLs
and bearer credentials -- into the per-webhook event database with
an empty webhook_id. Event databases are the files most likely to be
backed up or handed to someone else, so they shipped the credentials
with them.

Register a create and update callback on every per-webhook
connection that omits associations, rather than fixing the one call
site: it covers writes inside a transaction and write paths added
later. Sweep any rows already written, before the migration on each
open, so it is idempotent and a no-op on a database with no targets
table.

Encryption of target config at rest in webhooker.db is deliberately
not part of this: it is tracked separately.
This commit is contained in:
2026-08-20 04:17:50 +00:00
parent 10c8dd2331
commit e0456c7efa
5 changed files with 488 additions and 8 deletions

View File

@@ -413,14 +413,16 @@ 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, and clears
any it finds the first time it opens the file — but a backup taken
from an older build, or of a file this version has not opened yet,
still hands over live delivery destinations.
- `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.