Skip to content

[codex] Batch D: crypto & webhook hardening (round-2 audit) - #22

Merged
alextitov19 merged 1 commit into
mainfrom
codex/fix-crypto-webhooks
Jun 19, 2026
Merged

[codex] Batch D: crypto & webhook hardening (round-2 audit)#22
alextitov19 merged 1 commit into
mainfrom
codex/fix-crypto-webhooks

Conversation

@alextitov19

Copy link
Copy Markdown
Contributor

Fixes the round-2 crypto/webhook findings (discussion #13), favoring low-risk changes (crypto format changes that could make existing data un-decryptable are deliberately avoided).

  • Webhook v0 (legacy body-only, replayable - no timestamp binding) is now off by default; opt in via BENMORE_WEBHOOK_LEGACY_V0=1 for a migration window. v1 (t.body) remains. (Delivery already used the strict dial-time SSRF client.)
  • Signed-URL key: replaced the sync.Once that permanently cached an ephemeral key when serverSecret wasn't set yet (breaking signed-URL validation across instances/restart) with a mutex that returns an uncached ephemeral pre-init and derives+caches the real key once serverSecret is available.
  • Argon2id KDF: warn (not reject - rejecting would lose access to data encrypted with that key) when params are below the OWASP-ish floor.
  • Fixed the misleading rotation comment (cryptoDeriveAppKey, not bare sha256).

Intentionally not changed: M-3 AAD column-move prevention on the generic read path (DecryptRowFields only has the result-set key, often an alias → strict enforcement would false-reject legit reads and make data inaccessible; the threat needs DB-write access, and benmore_decrypt still enforces where the true column is known). Row-id AAD binding would need a v3 format + full re-encrypt migration.

Tests added; go build+vet+test -tags sqlite_fts5 ./... green; -race clean.

- Webhook v0 (legacy body-only, replayable - no timestamp binding) is now OFF by
  default; opt in for a migration window via BENMORE_WEBHOOK_LEGACY_V0=1. v1
  (t.body) remains. Delivery already used the strict dial-time SSRF client.
- Signed-URL signing key: replaced the sync.Once that permanently cached an
  EPHEMERAL key when serverSecret wasn't set yet (breaking signed-URL validation
  across instances/restart) with a mutex that returns an uncached ephemeral
  pre-init and derives+caches the real serverSecret-keyed key once available.
- Argon2id KDF: warn (not reject - rejecting would make an existing key
  un-derivable and lose data) when params are below the OWASP-ish floor
  (time<2 / mem<19MiB).
- Fixed the rotation doc comment that wrongly said sha256(newKeySource); it uses
  cryptoDeriveAppKey (versioned: SHA-256 or Argon2id).

Note: the M-3 AAD column-move PREVENTION is intentionally left as-is on the
generic read path - DecryptRowFields only has the result-set key (often an
alias), so strict enforcement there would false-reject legit aliased reads and
make data inaccessible; the threat needs DB-write access and enforcement remains
in benmore_decrypt where the true column is known. Row-id AAD binding would need
a v3 format + full re-encrypt migration (disproportionate to a DB-write threat).

go build + vet + test -tags sqlite_fts5 ./... green; -race clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alextitov19
alextitov19 merged commit 4a93bde into main Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant