* fix(credentials): stop double-encrypting undecryptable safeStorage blobs
When local decrypt failed (e.g. OSCrypt key churn after reboot), encrypt
used to wrap the leftover enc:v1 ciphertext again, permanently poisoning
the vault. Startup sync could also push those placeholders to cloud and
make download restore the same poison.
- Keep real enc:v1 blobs unchanged on encrypt (header check, no wrap)
- Strip device-bound placeholders when applying portable sync payloads
- Guard startup local-wins / merge round-trips before upload
- Skip vault init re-encrypt writes when secrets are still undecrypted
Fixes#2702
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sync): strip enc:v1 secrets from smart-merge uploads
Bugbot found that after local apply sanitized placeholders, legacy v1
smart-merge could still decrypt an unstripped remote, merge it, and
re-upload the poison. Strip device-bound credentials on remote decrypt
and again on the merged payload before upload.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): address Codex P2 review on #2702
- Always re-encrypt vault init batches so plaintext siblings are not
left unprotected when one record is a stale enc:v1 placeholder
- Sanitize device-bound secrets before convergent restore prepare so
CRDT replica commit matches vault import
- Require a complete safeStorage blob (header + min 31 bytes) before
treating enc:v1 as ciphertext; encrypt header-only coincidences
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): address Codex P1 review on #2702
- Decode enc:v1 payloads with atob in the renderer-safe domain helper
- Heal poisoned remote secrets from local/base before smart-merge so
good credentials are not discarded as remote-only deletions
- Keep post-merge strip so leftover enc:v1 never uploads
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): address Codex P1 CBC min and startup heal
- Accept 19-byte v10/v11 CBC OSCrypt blobs (not only 31-byte GCM)
- Heal poisoned remote secrets before startup smart-merge
- Strip unresolved placeholders on merge round-trip upload
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sync): sanitize payloads at every apply/commit boundary
Codex P1: vault apply stripped enc:v1 while commitRemoteInspection and
convergent materialization could keep the poison. Sanitize merged/remote
payloads before apply+base commit, strip on CRDT materialization, and
sanitize convergent apply inputs.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sync): heal local and remote secrets before smart-merge
Smart-merge could select a locally-changed entity whose only secret
delta was enc:v1 poison, then strip and upload empty secrets. Heal
both sides from the opposite payload/base before merge on all sync
paths.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sync): keep enc:v1 intact during convergent materialize
Stripping device-bound secrets inside materializeSyncPayloadFromConvergentState
broke envelope validation for poisoned-but-consistent v2 snapshots, so decrypt
could not hydrate the clouds #2702 needs to recover. Portable stripping stays
at apply/upload boundaries.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): detect Windows DPAPI by decoded header bytes
Real DPAPI blobs start with 01 00 00 00 d0 8c... and base64-encode as
AQAAANCM..., so the previous AQAAAA string prefix rejected them and
allowed double-wrapping after key rotation. Match decoded headers in
both the main-process bridge and renderer predicate.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* test(credentials): use complete enc:v1 fixtures for stricter detector
Short placeholders like enc:v1:djEwAAAA no longer pass the platform
header + minimum-size checks. Update auth/SFTP/proxy/sync fixtures to
full v10-shaped blobs so npm test matches production validation.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sync): treat preferred credential deletions as authoritative
When healing enc:v1 before merge, an empty/missing secret on a present
preferred entity is an intentional clear and must not be revived from
base. Only fall back to base when preferred is absent or also poisoned.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): reject impossible v10/v11 ciphertext lengths
Accept only CBC-aligned sizes (19+16n) or GCM-sized blobs (>=31) so
coincidental enc:v1 plaintext of intermediate length is encrypted instead
of treated as an undecryptable placeholder.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): require full DPAPI provider GUID signature
Accept Windows safeStorage blobs only when they start with version
01 00 00 00 plus provider GUID df9d8cd0-1501-11d1-8c7a-00c04fc297eb,
so coincidental 01 00 00 00 prefixes are not treated as ciphertext.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>