fix(security): record the Stripe last-4 hint as deliberate and lock its ceiling (BUNYIP-443) #435
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/BUNYIP-443-stripe-secret-last4-hint"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Decision (AC1): keep the last-four hint for the Stripe secret key and webhook secret, rather than moving them to the write-only posture BUNYIP-432 chose for the SMTP password. PMS-342 shipped the last-four hint deliberately for operator convenience in identifying which credential is set, and we are retaining it. This PR makes that decision explicit and unregressable; it does not change behaviour.
Why no behaviour change was needed
The read and save paths are already safe for a kept hint:
StripeConfigResponsecarries only themask_secretform (prefix +***+ last 4) and thehas_*booleans. The full plaintext is never a field on the response, so it cannot be serialized to the client.valueattribute, so the field posts empty unless the admin types a new secret; empty is dropped before the request body is built (admin.rs).update_stripe_configfilters empty toNone, andStripeConfigRepository::updateusesCASE WHEN $1 IS NOT NULL THEN $1 ELSE secret_key END, so an untouched field leaves the stored secret unchanged. The masked value can never be written back as the real secret.Changes
StripeConfigResponsethat the last-four hint is a reviewed, deliberate posture (PMS-342 kept it, BUNYIP-443 retained it over write-only), with a pointer to how to move to write-only later if revisited. Stops a future security pass re-flagging it as the same defect class as BUNYIP-432.masked_response_never_carries_the_full_secret: build a response from full secrets, serialize it, and assert the full value and its middle are absent while only the prefix + last-4 hint is present. Locks the ceiling so a change tomask_secretor the response cannot silently widen the exposure back toward the full value.mask_secretsweep (AC)After BUNYIP-432 removed the SMTP use,
mask_secrethas exactly one client-facing caller left: this Stripe response (secret_key_masked/webhook_secret_masked). Every other reference is its own unit tests. No storage change (secrets remain AES-256-GCM encrypted at rest).Acceptance criteria
CASE WHEN ... ELSE secret_key).mask_secretclient-facing sweep recorded (Stripe response only).just check-containergreen (fmt + clippy-D warnings+ workspace tests, 313 domain tests).🤖 Generated with Claude Code
https://claude.ai/code/session_018TXaT3P192nDsZzbzHETb9