Repository navigation
Conversation
BossConsole#620: get_user_secrets, search_user_secrets and get_user_secrets_with_shared each decrypt password_encrypted and recovery_codes_encrypted with a bare call to 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 an HMAC/padding mismatch - and one raised row aborts the whole query, so the caller sees every one of their secrets fail to load rather than just the bad one. twofa_secret already reads through safe_decrypt_twofa_secret (NULL on failure). recovery_codes_encrypted already had a safe wrapper too - safe_decrypt_recovery_codes, whose own doc comment says "used internally by get_user_secrets functions" - but whichever later migration touched these RPCs stopped calling it and fell back to raw decrypt_text, so it sat unused. password_encrypted never had a safe wrapper at all. Adds try_decrypt_text(text), mirroring safe_decrypt_recovery_codes's BEGIN/EXCEPTION WHEN OTHERS THEN RETURN NULL shape for a scalar result, locked down the same way safe_decrypt_twofa_secret is (no client role gets EXECUTE). Routes the password read in all three RPCs through it and the recovery-codes read back through the existing safe wrapper. One corrupt row now surfaces as a blanked field - password NULL, recovery_codes [] - while every other secret in the page still loads. Deliberately not named safe_decrypt_*: ops/rotate_master_encryption_key.sql's re-encrypt loop treats every function matching that name pattern as a mapped bespoke-format adapter and refuses to run if one is unmapped. try_decrypt_text wraps the same plain envelope that script already re-encrypts directly, so it must stay outside that sweep. Scoped to the three user-facing listing RPCs only; the operator-run backfill/rotation paths keep using raw decrypt_text and should still fail closed.
|
Claude finished @kshivang's task in 8m 18s —— View job Review of
|
…ailures Includes desktop regression coverage from risa-labs-inc#621 by Rushikeshiitb and review repairs by kshivang; related independent risa-labs-inc#652 work by akashjadon04 informed the review.
|
@Antriksh1984 Thanks for the focused listing recovery change and real encrypted/corrupt pgTAP fixtures. I repaired the end-to-end contract in b3197c5 on your original branch, preserving your authorship. Reconciled the completed Claude review of 762c752 against this repair:
Independently verified #621/#652 overlap and #647/current migration ordering, which Claude could not fetch. #621 remains an outside-assignment draft; #652 was already consolidated there and closed. Neither was mutated. Their contributor/test provenance is explicit in this PR description. All three normalized RPC bodies match current dev apart from intended decrypt substitutions; authorization, sharing, ordering and output schemas are preserved. Validation: 860 assertions / 35 disposable PostgreSQL suites pass, plus the two key probes and mutation evidence. Current-head hosted database, quality, compatibility, shell, version and AI checks pass; platform builds are still running. No live database changes or visual pass claimed. Will merge only to dev after the remaining gates pass under the shared merge lock. |
…668) * fix(secrets): make the secret listing RPCs fail soft on one bad row BossConsole#620: get_user_secrets, search_user_secrets and get_user_secrets_with_shared each decrypt password_encrypted and recovery_codes_encrypted with a bare call to 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 an HMAC/padding mismatch - and one raised row aborts the whole query, so the caller sees every one of their secrets fail to load rather than just the bad one. twofa_secret already reads through safe_decrypt_twofa_secret (NULL on failure). recovery_codes_encrypted already had a safe wrapper too - safe_decrypt_recovery_codes, whose own doc comment says "used internally by get_user_secrets functions" - but whichever later migration touched these RPCs stopped calling it and fell back to raw decrypt_text, so it sat unused. password_encrypted never had a safe wrapper at all. Adds try_decrypt_text(text), mirroring safe_decrypt_recovery_codes's BEGIN/EXCEPTION WHEN OTHERS THEN RETURN NULL shape for a scalar result, locked down the same way safe_decrypt_twofa_secret is (no client role gets EXECUTE). Routes the password read in all three RPCs through it and the recovery-codes read back through the existing safe wrapper. One corrupt row now surfaces as a blanked field - password NULL, recovery_codes [] - while every other secret in the page still loads. Deliberately not named safe_decrypt_*: ops/rotate_master_encryption_key.sql's re-encrypt loop treats every function matching that name pattern as a mapped bespoke-format adapter and refuses to run if one is unmapped. try_decrypt_text wraps the same plain envelope that script already re-encrypts directly, so it must stay outside that sweep. Scoped to the three user-facing listing RPCs only; the operator-run backfill/rotation paths keep using raw decrypt_text and should still fail closed. * Fix installed-client compatibility and preserve systemic decryption failures Includes desktop regression coverage from #621 by Rushikeshiitb and review repairs by kshivang; related independent #652 work by akashjadon04 informed the review. --------- Co-authored-by: Shivang <shivang.iitk@gmail.com>
…668) * fix(secrets): make the secret listing RPCs fail soft on one bad row BossConsole#620: get_user_secrets, search_user_secrets and get_user_secrets_with_shared each decrypt password_encrypted and recovery_codes_encrypted with a bare call to 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 an HMAC/padding mismatch - and one raised row aborts the whole query, so the caller sees every one of their secrets fail to load rather than just the bad one. twofa_secret already reads through safe_decrypt_twofa_secret (NULL on failure). recovery_codes_encrypted already had a safe wrapper too - safe_decrypt_recovery_codes, whose own doc comment says "used internally by get_user_secrets functions" - but whichever later migration touched these RPCs stopped calling it and fell back to raw decrypt_text, so it sat unused. password_encrypted never had a safe wrapper at all. Adds try_decrypt_text(text), mirroring safe_decrypt_recovery_codes's BEGIN/EXCEPTION WHEN OTHERS THEN RETURN NULL shape for a scalar result, locked down the same way safe_decrypt_twofa_secret is (no client role gets EXECUTE). Routes the password read in all three RPCs through it and the recovery-codes read back through the existing safe wrapper. One corrupt row now surfaces as a blanked field - password NULL, recovery_codes [] - while every other secret in the page still loads. Deliberately not named safe_decrypt_*: ops/rotate_master_encryption_key.sql's re-encrypt loop treats every function matching that name pattern as a mapped bespoke-format adapter and refuses to run if one is unmapped. try_decrypt_text wraps the same plain envelope that script already re-encrypts directly, so it must stay outside that sweep. Scoped to the three user-facing listing RPCs only; the operator-run backfill/rotation paths keep using raw decrypt_text and should still fail closed. * Fix installed-client compatibility and preserve systemic decryption failures Includes desktop regression coverage from #621 by Rushikeshiitb and review repairs by kshivang; related independent #652 work by akashjadon04 informed the review. --------- Co-authored-by: Shivang <shivang.iitk@gmail.com>
Fixes #620.
One corrupt encrypted password or recovery-code field previously aborted an authorized secrets listing. The three RPCs now preserve rows, return a non-null blank password for corrupt data, and use the existing recovery-code fallback. Authorization, sharing precedence, pagination and RPC schemas are unchanged. Rotation remains strict; no live database deployment is included.
Original implementation and pgTAP coverage: @Antriksh1984.
Review repairs by @kshivang: preserve installed-client non-null password decoding, propagate missing/empty/malformed key and systemic privilege/decoder/internal failures, report SQLSTATE-only warnings, register the internal helper in security audits, and add fault regression cases. Desktop regression coverage is retained from @Rushikeshiitb's #621 and its @kshivang review repairs. @akashjadon04's independent #652 implementation is also acknowledged as related provenance; its coverage was consolidated into #621 previously.
#621 (currently draft, outside this review assignment) and closed #652 address the same #620 listing behavior. Neither is closed or changed by this PR review. #647 is a distinct merged random-IV encryption change, fully retained; v2 does not provide authenticated encryption. A valid-but-wrong key cannot reliably be distinguished from damaged ciphertext in that format.
Validation on repair b3197c5: disposable local PostgreSQL applied the migration chain and passed 860 assertions / 35 suites. Fixture generation/check and diff whitespace checks pass. Focused desktop decoding tests, ktlintCheck and detekt PASS through the shared build lock. The original catch-all helper fails all five systemic-error mutation assertions; supplemental malformed/NULL-key probes pass. All ten hosted current-head checks PASS (run 34912766436). Claude completed the requested original-head review; all substantive findings are resolved in this repair with evidence: #668 (comment) .
No UI change; no visual pass claimed, app launch, new tab or live database operation. Merged into dev as f1eaba0 after current-head checks and review gates passed under the shared merge lock.