Skip to content

One undecryptable secret aborts the entire secrets listing (passwords are not fail-soft) #620

Open

Description

@Rushikeshiitb

get_user_secrets, search_user_secrets and get_user_secrets_with_shared decrypt the password (and recovery codes) with a bare public.decrypt_text inside a set-returning query. decrypt_text RAISES on an undecryptable value - a wrong key after a partial key rotation, storage corruption, or (with the v2 envelope) an HMAC mismatch - and one raised row aborts the WHOLE query. The user then sees every one of their secrets fail to load, not just the one bad row.

twofa_secret is already read through safe_decrypt_twofa_secret (NULL on failure); recovery codes and passwords are not - in the current RPCs both use raw decrypt_text, so both share this exposure. The same class of failure also aborts the backfill/rotation paths, but those are operator-run and fail closed by design; the listing is user-facing and should degrade per-row.

Suggested: add a try_decrypt_text(text) that returns NULL instead of raising (mirroring safe_decrypt_recovery_codes returning []), and route the password read through it and the recovery-codes read through safe_decrypt_recovery_codes in all three listing RPCs, so one corrupt row surfaces as a single blanked field while every other secret still loads. Do not name it safe_decrypt_* - the key-rotation script treats every such function as a mapped bespoke-format adapter and refuses to run if one is unmapped.

Activity

  1. akashjadon04 commented on Sep 14, 2026

    @akashjadon04

    Claiming this issue with #652. The submission hardens all three listing RPCs, preserves the non-null desktop password contract, and adds regression coverage for corrupt owned and directly shared rows.

  2. Mihir-Rabari commented on Sep 22, 2026

    @Mihir-Rabari
    Contributor

    Verified fixed on current dev: migration 20260914010000_fail_soft_secret_listing_decryption.sql (merged via #668, integrated via #695) adds try_decrypt_text and routes get_user_secrets, search_user_secrets, and get_user_secrets_with_shared through COALESCE(try_decrypt_text(...), empty string), with recovery codes via safe_decrypt_recovery_codes. Regression coverage runs in the Database pgTAP job: supabase/tests/fail_soft_secret_listing_decryption_test.sql pins corrupt-row listing alongside healthy rows, blanked password, and error-class propagation. Closable as fixed by #668/#695.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions