Stop target credentials leaking into event databases (closes #206)
Some checks failed
check / check (push) Failing after 2m36s
Some checks failed
check / check (push) Failing after 2m36s
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:
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