diff --git a/README.md b/README.md index 60aecbd..3770282 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,10 @@ let it run automatically when a credential is missing. Works with the Chromium f Edge, Chromium, Vivaldi, Opera) and Safari (needs Full Disk Access). Manual paste stays the fallback everywhere else. +**Windows and Linux:** plx itself runs fine — every conversion works the same. Only this auto-fetch +shortcut is macOS-only, so paste the cookies once as described above and they're stored like any +other credential. + Set credentials in `.env`, export them in your shell, or paste when prompted. Full setup: [`docs/CONFIG.md`](docs/CONFIG.md). diff --git a/docs/CONTINUATION.md b/docs/CONTINUATION.md index 4867fc9..fc860df 100644 --- a/docs/CONTINUATION.md +++ b/docs/CONTINUATION.md @@ -204,8 +204,25 @@ OS permission dialog — and fall back to manual paste if nothing is found. `tryAutoFillCredentials(cfg)` — additive: env vars and stored credentials win, manual paste stays the fallback. First use pops the macOS Keychain "Allow" dialog. -Out of scope for now: Windows (DPAPI) / Linux (libsecret) cookie decrypt, and the -Firefox key4.db/encryptedcookies format. +Out of scope, with the reasons — these are not all equally hard: + +- **Windows, Chromium family — treat as closed, not merely unimplemented.** Plain + DPAPI only ever covered Chrome < 127. Since Chrome 127 (July 2024) App-Bound + Encryption stores cookies as `v20` blobs whose key is wrapped by User-DPAPI and + then SYSTEM-DPAPI, so only a SYSTEM process or `elevation_service.exe` can + unwrap it — the feature exists specifically to stop what this module does. Every + published bypass is COM hijacking or injection into the elevation service, which + is malware behaviour and would get plx flagged as an infostealer. Do not ship it. +- **Linux, Chromium family** — libsecret/kwallet. Unimplemented, not blocked. +- **Firefox — the actually cheap one, and the note it replaces was wrong.** + `key4.db` protects saved *logins*, not cookies; `cookies.sqlite` stores cookie + values in **plaintext** on every OS. No DPAPI, no App-Bound Encryption, and it + reuses the `node:sqlite` import already here. One backend would cover Windows, + macOS and Linux at once. Gotchas: profile discovery via `profiles.ini`, and the + DB is WAL-mode and held open by the browser, so copy it (plus `-wal`) to a temp + file rather than relying on `readOnly: true`. + +If auto-fetch on Windows is ever asked for, Firefox is the answer; Chromium is not. ## Deezer → Spotify (WORKING — verified live) @@ -285,7 +302,7 @@ The hashes in `spotify.ts` (`ADD_TO_PLAYLIST_SHA`, `SEARCH_TRACKS_SHA`, `LIBRARY ## Release blockers -1. ~~Push to GitHub~~ — done (`4ca1db5`). **Confirm the CI matrix passes** (ubuntu/macos/windows × node 22/24) — pending. +1. ~~Push to GitHub~~ — done (`4ca1db5`). **CI matrix** (ubuntu/macos/windows × node 22/24): ubuntu and macos green; both **windows legs were red from the day the matrix landed** — `configDir()` ignored `XDG_CONFIG_HOME` on win32, so the per-test temp dirs collapsed onto one real `%APPDATA%\plx` and state leaked between cases (`config.test.ts:43`). Fixed on `fix/windows-config-dir`; confirm green there before merging. 2. ~~Fix the Deezer → Spotify reverse flow~~ — done, verified live (see "Verified live" above). 3. Publish `plx@0.1.0` to npm. diff --git a/src/config.ts b/src/config.ts index e38f5fb..0c786cc 100644 --- a/src/config.ts +++ b/src/config.ts @@ -20,9 +20,13 @@ const LEGACY_KEYS = ['deezerArl', 'spotifyDc']; type Stored = { credentials?: Record; recentUrls?: string[] } & Record; +// XDG_CONFIG_HOME wins on every platform, Windows included: it is the explicit override, and a +// Windows-only branch that ignored it left the tests sharing one real %APPDATA% dir instead of a +// temp dir each, so state leaked between them. export function configDir(): string { + if (process.env.XDG_CONFIG_HOME) return join(process.env.XDG_CONFIG_HOME, 'plx'); if (platform() === 'win32') return join(process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming'), 'plx'); - return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'plx'); + return join(homedir(), '.config', 'plx'); } const file = () => join(configDir(), 'credentials.json');