Skip to content

Wipe keys before dispatching decrypt-failure events - #39

Draft
robbinjanssen wants to merge 1 commit into
feature/file-messagesfrom
feature/v2.2-security-key-wipe
Draft

Wipe keys before dispatching decrypt-failure events#39
robbinjanssen wants to merge 1 commit into
feature/file-messagesfrom
feature/v2.2-security-key-wipe

Conversation

@robbinjanssen

Copy link
Copy Markdown
Contributor

Summary

Fixes a key-hygiene defect found during a security review of the v2 line: decrypt-failure events could hand unwiped key material to listeners.

The Laravel events expose their SecureMessage via a public readonly property (added in the v2.0 modernization), so any listener — and anything a queued listener serializes the event to (Redis, database, an error tracker) — can read it. The library's wipe contract only runs inside DecryptException::__construct when the exception is given a secure message. Three failure paths throw without one and therefore dispatched an instance that still held decrypted keys:

  • Missing storage key file — database key + verification code present.
  • Malformed stored ciphertext (via the v2.0 fromString() hardening) — all key parts present, i.e. the full 32-byte key reconstructable, co-located with the (still valid) encrypted content. This dispatch path is new to v2.0: on v1 malformed input was an uncaught PHP error, so no event fired.
  • Missing file blob (added in v2.1) — database key + verification code present.

This breaks the core promise that the three key parts never co-locate.

Fix

Wipe the secure message in Laravel\Factory::decryptMessage()'s catch block, before any event is dispatched, regardless of whether the exception carried it. Wiping is idempotent, so the paths that were already wiped (wrong code, expiry, hit-point limit) are unaffected. One chokepoint covers all present and future failure paths.

Verification

  • composer test: 51 tests, 333 assertions, green.
  • Regression tests on both null paths (missing key file, missing file blob) assert the dispatched event's SecureMessage has no database key, storage key, meta key or verification code. Confirmed they fail without the fix (2 failures) and pass with it.
  • composer analyse (PHPStan level 6) and php-cs-fixer: clean.

Branch/merge notes

Branched off feature/file-messages (v2.1) and targets it, so the fix covers both the v2.0 event paths and the v2.1 file-blob path. Merge order: #37 (v2.0) → #38 (v2.1) → this. GitHub retargets automatically as each base merges.

🤖 Generated with Claude Code

The Laravel events expose their SecureMessage as a public readonly property,
so listeners (and anything they serialise the event to, such as a queued
listener writing to Redis) can read it. Most decrypt-failure paths hand over a
wiped instance, because the DecryptException constructor wipes the keys when it
is given the secure message. Three paths throw without it and left the
decrypted key material on the dispatched instance:

- a missing storage key file (database key + verification code present);
- malformed stored ciphertext (all key parts present);
- a missing file blob (database key + verification code present).

This violates the split-key promise that the key parts never co-locate. Wipe
the secure message in the catch block, before any event is dispatched,
regardless of whether the exception carried it. The already-wiped paths are
unaffected (wiping is idempotent). Regression tests assert the dispatched event
carries no key material on both null paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@robbinjanssen robbinjanssen added the bugfix Inconsistencies or issues which will cause a problem for users or implementors. label Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Inconsistencies or issues which will cause a problem for users or implementors. development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants