Architecture
APP_KEY rotation
Pitchbar encrypts every customer-controlled secret at rest under
the Laravel APP_KEY. If that key leaks, you need to
rotate to a new one without losing access to existing data. The
security:rotate-app-key Artisan command makes the
rotation safe and idempotent.
Columns covered
workspaces.cta_context_secretworkspaces.byok_keysworkspaces.slack_webhook_urlworkspaces.teams_webhook_urlwebhook_subscriptions.secretworkspace_api_tokens.shopper_signing_secretdsr_requests.result_payloadintegration_connections.credentials_encryptedsources.credentials_encryptedapp_settings.*(Stripe, PayPal, Razorpay, Cloudflare, OpenAI, OpenRouter, mail, internal queue secrets)
Rotation flow
- Generate a new key:
php artisan key:generate --show # copy the output - Add the new key as the active
APP_KEYand move the old key intoAPP_PREVIOUS_KEYS(comma separated if multiple). Example:APP_KEY=base64:NEW_KEY APP_PREVIOUS_KEYS=base64:OLD_KEY - Deploy. Laravel will decrypt rows with either key (new takes priority on writes); reads using the old ciphertext fall back to the previous key. The app keeps serving uninterrupted.
- Run the sweep:
php artisan security:rotate-app-key --confirm-production - The command iterates every encrypted column in chunks of 200 rows, decrypts with the current key (or the previous-key fallback), and re-encrypts with the new key. Skipped rows (rows whose ciphertext cannot be decrypted) are reported but do not abort the sweep.
- Once the command completes, remove
APP_PREVIOUS_KEYSfrom your env. The old key is no longer needed for any row.
Safety flags
--confirm-productionโ required inproductionenvironment. Omit instaging/local.--dry-runโ counts rows + columns that would be re-encrypted without writing. Useful to preview the surface before committing.
Idempotency
Running the command twice is harmless. The second pass decrypts and re-encrypts the same values; the ciphertext bytes change (fresh IV) but the plaintext remains intact.
What if a row cannot decrypt?
The command logs the column and row id to the console and
moves on. That row stays on whatever key produced its current
ciphertext. If you removed
APP_PREVIOUS_KEYS before the sweep completed,
those rows are unrecoverable โ restore from a backup and
re-run with the previous key in env.
Audit footprint
The command does not write to audit_logs on its
own โ but the operator who runs it should record the rotation
in their change-management system. If you want every rotation
auto-audited, wrap the artisan call in a deploy script that
emits an audit row.