diff --git a/docs/superpowers/plans/2026-07-27-mailboxes.md b/docs/superpowers/plans/2026-07-27-mailboxes.md index d243c11..04ffa9a 100644 --- a/docs/superpowers/plans/2026-07-27-mailboxes.md +++ b/docs/superpowers/plans/2026-07-27-mailboxes.md @@ -256,11 +256,30 @@ it('stores the password encrypted and never in the clear', function () { ->and($box->fresh()->password)->toBe('sehr-geheim'); }); -it('uses SECRETS_KEY, not APP_KEY — rotating APP_KEY leaves mail working', function () { +it('is keyed to SECRETS_KEY — rotating it makes the password unreadable', function () { + // The POSITIVE proof, and the one that discriminates. Without it the pair + // below passes even against an APP_KEY implementation. + $box = Mailbox::factory()->create(['password' => 'geheim']); + + config()->set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32))); + + expect(fn () => $box->fresh()->password) + ->toThrow(Illuminate\Contracts\Encryption\DecryptException::class); +}); + +it('is NOT keyed to APP_KEY — rotating that leaves mail working', function () { $box = Mailbox::factory()->create(['password' => 'bleibt-lesbar']); config()->set('app.key', 'base64:'.base64_encode(random_bytes(32))); + // forgetInstance is what makes this test mean anything. Laravel's encrypter + // is a container singleton resolved once from whatever app.key held at the + // time; config()->set() afterwards mutates the array and never rebuilds it. + // Without this line the test passes even when the model is rewired to + // Laravel's own APP_KEY-based Crypt facade — proven by mutation during the + // Task 2 review, where every test stayed green through exactly that change. + app()->forgetInstance('encrypter'); + expect($box->fresh()->password)->toBe('bleibt-lesbar'); });