Skip to content

fix: auth/CLI hardening v0.8.43 — closes #5, closes #6#7

Merged
ARHAEEM merged 13 commits into
mainfrom
fix/0.8.43-auth-cli-hardening
May 11, 2026
Merged

fix: auth/CLI hardening v0.8.43 — closes #5, closes #6#7
ARHAEEM merged 13 commits into
mainfrom
fix/0.8.43-auth-cli-hardening

Conversation

@ARHAEEM
Copy link
Copy Markdown
Member

@ARHAEEM ARHAEEM commented May 11, 2026

Closes #5 — Headed bootstrap reads cookies as anonymous despite successful login
Closes #6 — CLI symlink exit / shebang / keychain prompts / login persistence


What changed

Issue #5 — daemon anonymous mode after successful login

Bug 1 (fix/#5.1)PERPLEXITY_HEADLESS_ONLY=1 leaked from the launcher's IDE-config env block into the daemon's spawn env via ...process.env. The daemon's PerplexityClient.init() then skipped the headed bootstrap entirely — the only phase that can solve CF challenges. Fixed by stripping PERPLEXITY_HEADLESS_ONLY and PERPLEXITY_NO_DAEMON from the spawn env in all three spawn sites (daemon/launcher.ts, extension/src/daemon/runtime.ts, stdio-daemon-proxy.ts).

Bug 2 (fix/#5.2) — Phase 2 (headless search) used chromium.launch() + ephemeral context. The fresh cf_clearance written to browser-data/ by Phase 1 was never loaded. Phase 2 now uses chromium.launchPersistentContext(browserData, ...) — same directory as Phase 1, so the CF clearance is already on disk when Phase 2 starts. Vault cookies are injected selectively (only cookies not already on disk), so a freshly-written cf_clearance is never overwritten by a stale vault copy.

Diagnostic gap (fix/#5.3)getSavedCookies() returned [] silently for four distinct failure modes. Now logs a specific message for each path: no vault.enc, cookies key absent, non-array value, JSON parse failure. An unsealFailed flag prevents the "key absent" message from firing when the real cause is an unseal error already logged by the catch path.

Issue #6 — CLI/auth UX bugs (all four confirmed bugs)

Bug 1 (fix/#6.1) — CLI and daemon entrypoints silently exited 0 on symlinked invocations (npm global install, Homebrew Cellar, node_modules/.bin/). The import.meta.url === pathToFileURL(process.argv[1]).href guard fails when process.argv[1] is a symlink. Extracted isMainModule() helper (src/is-main-module.js) that uses realpathSync on both sides, with a defensive fallback to the old comparison when realpathSync rejects.

Bug 2 (fix/#6.2)dist/cli.mjs had no shebang or executable mode. tsup intentionally strips shebangs (they trip vitest/esbuild during test imports). New scripts/post-build-shebang.mjs post-build script prepends #!/usr/bin/env node and chmod 755s the file. No-op on Windows where npm uses .cmd wrappers.

Bug 3 (fix/#6.3) — Repeated macOS Keychain permission prompts from multiple keytar.getPassword() calls per session. vault.js now caches the keytar module reference in _keytarModuleCache (reset on __resetKeyCache()). probeKeychainState() is exported and shared across cli.js and checks/vault.js, removing three duplicate inline probe implementations. PERPLEXITY_DISABLE_KEYCHAIN=1 escape hatch added for headless/CI environments.

Bug 4 (fix/#6.4) — Login runners (login-runner.js, manual-login-runner.js) used a non-persistent browser context, discarding Google SSO and Cloudflare state between login sessions. Now use chromium.launchPersistentContext(paths.loginBrowserData, ...) with a dedicated login-browser-data/ directory (separate from the daemon's browser-data/ to avoid Chromium singleton-lock collisions).

Build fix

tsup's rollup-dts worker OOMs on this package's 47+ entry points under Node 22+. Build is now split: tsup --no-dts + tsc --allowJs --emitDeclarationOnly + post-build shebang script.

Review polish (added during code review pass)

  • getSavedCookies: unsealFailed flag prevents misleading double-log when vault unsealing throws
  • Login runners: ctx.browser()?.close() (optional chain) so a null browser() return degrades gracefully

Test coverage

  • test/is-main-module.test.js — exact match, non-match, symlink, missing argv[1]
  • test/vault.test.jsprobeKeychainState caching + PERPLEXITY_DISABLE_KEYCHAIN escape hatch
  • test/config-getSavedCookies.test.js — "no vault.enc" and "key absent" diagnostic log paths
  • extension/tests/transports/stdio-daemon-proxy.test.ts — assert PERPLEXITY_HEADLESS_ONLY no longer injected
  • extension/tests/auto-config.test.ts — same assertion for OpenCode transport

130/130 test files pass. Full typecheck clean.


Smoke required before merge

Per docs/release-process.md: Win11 + macOS 14+ + Ubuntu 22+. Issue #6 bugs 1+2 specifically need the macOS row (Homebrew bin layout is the canonical symlink reproducer). Issue #5 bugs need the daemon-proxy transport path on any OS.

A.R. and others added 11 commits May 11, 2026 21:03
`import.meta.url === pathToFileURL(process.argv[1]).href` silently
returned false when the CLI was invoked through a symlink (npm .bin,
Homebrew Cellar, etc.), causing the CLI to exit 0 with no output.

Extract a shared `isMainModule()` helper that resolves both sides
through `realpathSync` before comparing, with a graceful fallback
to the old behavior if realpath fails.

Files: cli.js, index.ts, is-main-module.js(+d.ts)
npm's bin linker creates symlinks (not wrappers) on POSIX, so the
kernel needs a shebang in `dist/cli.mjs` for direct `./dist/cli.mjs`
execution to work.

Add `scripts/post-build-shebang.mjs` that prepends `#!/usr/bin/env node`
and chmods to 755, wired into the `build` script after tsup.
Update the tsup.config.ts comment to document the new pipeline.

Refs issue #6
…ape hatch

macOS users reported repeated Keychain permission prompts because four
independent code paths each did an uncached `import("keytar")` call.

- vault.js: Cache the keytar module load (both success and failure) in
  `_keytarModuleCache` per-process. Add `isKeychainDisabled()` helper
  that respects `PERPLEXITY_DISABLE_KEYCHAIN=1`.
- Export `probeKeychainState()` from vault.js so cli.js and
  checks/vault.js can share the cached probe instead of doing their own
  inline imports.
- extension/src/auth/vault-passphrase.ts: Check the env var before
  spawning the keytar probe child process, and also inside the child
  inline code.

Fixes issue #6 bug 3.
The manual and auto login runners used `chromium.launch()` +
`browser.newContext()`, which created a fresh ephemeral context every
run. Any Cloudflare clearance cookies acquired during the login flow
were lost on browser close, forcing the user through CF again on the
next login.

Switch both runners to `chromium.launchPersistentContext()` using a
dedicated `login-browser-data/` directory under the profile (separate
from the daemon's `browser-data/` to avoid singleton-lock collisions).

Add `loginBrowserData` path to `getProfilePaths()` in profiles.js.

Fixes issue #6 bug 4.
getSavedCookies returned `[]` silently in three situations:
1. vault.enc missing for the resolved profile
2. vault.enc exists but the `cookies` key is absent
3. JSON parse failure of the stored cookies

Without log lines, users and support can't distinguish scenario 1
(run login first) from scenario 2 (data corruption or wrong profile).

Log each empty path with the profile name and the specific cause so
that daemon logs and extension output channels show the real reason.
Issue #5 root cause: `PERPLEXITY_HEADLESS_ONLY=1` leaked from the IDE
config env block into the daemon process. When the daemon spawned, it
inherited `...process.env`, so `PerplexityClient.init()` saw the flag
and skipped the headed bootstrap entirely — authenticated Pro features
remained unavailable.

Changes:
- stdio-daemon-proxy.ts: remove `PERPLEXITY_HEADLESS_ONLY` from the
  auto-generated env block (new configs won't carry it).
- launcher.ts spawnDetachedDaemon: delete both `PERPLEXITY_HEADLESS_ONLY`
  and `PERPLEXITY_NO_DAEMON` from the env before spawning.
- runtime.ts spawnBundledDaemon: same stripping.

This also fixes pre-existing IDE configs that still have the flag:
the spawn-time strip removes it regardless of config age.

Refs issue #5
…njection

Phase 1 (headed bootstrap) wrote fresh cf_clearance to disk via
`launchPersistentContext(browserData)`, but Phase 2 used
`chromium.launch()` + `getOrCreateContext()` — a non-persistent
context — so the fresh clearance was never loaded. Only stale vault
cookies were injected.

Fix: Phase 2 now uses `launchPersistentContext(activePaths.browserData)`
so it inherits the disk cookies from Phase 1 automatically.

To avoid overwriting the fresh cf_clearance with the stale vault copy,
inject vault cookies ONLY for cookie names not already present on disk.

Remove now-unused `getOrCreateContext` import from client.ts.

Refs issue #5
- Add is-main-module.test.js covering symlink resolution (issue #6.1)
- Add vault.test.js cases for probeKeychainState caching and
  PERPLEXITY_DISABLE_KEYCHAIN=1 escape hatch (issue #6.3)
- Add config-getSavedCookies.test.js for diagnostic logging (issue #5.3)
- Update stdio-daemon-proxy.test.ts to expect empty env (issue #5.1)
- Update auto-config.test.ts to expect empty env for OpenCode (issue #5.1)
- Fix checks/vault.js to import probeKeychainState from vault.js
- Harden tryKeytar() against vitest mock proxies: validate getPassword
  is a function inside the try-catch so proxy-access errors are caught.

Refs issues #5, #6
…llowJs --emitDeclarationOnly

The 47-entry-point mcp-server bundle causes tsup's rollup-dts worker to
hit ERR_WORKER_OUT_OF_MEMORY on Node 22 / Windows (pre-existing; reproducible
on commits before 0.8.43).  Split the build so tsup handles the ESM bundle
and a post-step `tsc --allowJs --emitDeclarationOnly` generates the .d.ts
files.  This unblocks the full extension build pipeline.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… optional-chain ctx.browser()

getSavedCookies was logging both the real unseal error AND a spurious
"'cookies' key is absent" message because the existsSync branch fired
regardless of whether the null came from a vault error or a missing key.
Added an unsealFailed flag to skip the second diagnostic when the catch
path already explained the failure.

login-runner.js / manual-login-runner.js: ctx.browser().close()
→ ctx.browser()?.close() so a null browser() return degrades gracefully
instead of throwing a TypeError swallowed by .catch().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee8568f8b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/mcp-server/package.json
- shared: add DaemonAuthStatus + daemonAuth field on AccountSnapshot
- profiles.js: add daemonStatus + loginBrowserData to ProfilePaths (impl + .d.ts)
- client.ts: writeDaemonStatus() after every init/reinit/shutdown; fix bare throw→throw err
- session.ts: read daemon-status.json into AccountSnapshot
- DashboardProvider: FSWatcher on daemon-status.json with 300ms debounce + dispose cleanup; dashboard:refresh touches .reinit when daemon is anonymous with stored login
- views.tsx: daemonAuth status dot in hero panel (ok/warn; hidden in stdio mode)
- logout.js: clear loginBrowserData on softLogout so next login starts fresh
- checks/profiles.js: daemon-status.json auth/tier report + loginBrowserData presence note

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a196df728b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2120 to +2123
if (!this.daemonStatusWatcher) {
if (fs.existsSync(statusFile)) {
this.startDaemonStatusWatch();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Re-arm daemon status watcher on profile switch

ensureDaemonStatusWatch() only starts a watcher when this.daemonStatusWatcher is null, so once a watcher is attached for profile A it remains active after profile:switch and never retargets profile B's daemon-status.json. In that state, daemon auth changes for the newly active profile will not auto-refresh the dashboard (the exact behavior this watcher was added to provide), and users only see updates after a manual refresh.

Useful? React with 👍 / 👎.

…ofile switch

- .gitignore: add packages/mcp-server/scripts/ to allowlist (blanket * rule was
  excluding the scripts dir, so post-build-shebang.mjs was never tracked)
- post-build-shebang.mjs: add the missing script that CI was failing on
  (Cannot find module: post-build-shebang.mjs across all 8 matrix jobs)
- DashboardProvider: track daemonStatusWatchedProfile so ensureDaemonStatusWatch()
  re-arms when the active profile changes (addresses P2 review comment)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cfcd8ff4f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/mcp-server/src/client.ts
Comment thread packages/extension/src/webview/DashboardProvider.ts
@ARHAEEM ARHAEEM merged commit 0860ed9 into main May 11, 2026
8 checks passed
@ARHAEEM ARHAEEM deleted the fix/0.8.43-auth-cli-hardening branch May 11, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant