Skip to content

fix(desktop/dev): auto-inject NODE_EXTRA_CA_CERTS for portless TLS trust#646

Merged
jeevanpillay merged 2 commits intomainfrom
fix/desktop-portless-ca-injection
May 6, 2026
Merged

fix(desktop/dev): auto-inject NODE_EXTRA_CA_CERTS for portless TLS trust#646
jeevanpillay merged 2 commits intomainfrom
fix/desktop-portless-ca-injection

Conversation

@jeevanpillay
Copy link
Copy Markdown
Member

Summary

Auto-inject NODE_EXTRA_CA_CERTS=$HOME/.portless/ca.pem into the Electron child env when LIGHTFAST_APP_ORIGIN resolves to a *.localhost host and the CA file exists. Mirrors the injection lightfast-dev proxy app-runtime already does for the next-dev child.

Why

Electron main inherits Node's undici fetch as the global fetch. Node's bundled CA list does not include portless's local root, so HTTPS calls from desktop main to the dev aggregate (https://*.localhost) fail TLS handshake with SELF_SIGNED_CERT_IN_CHAIN.

Concrete failure: the PKCE exchange POST in apps/desktop/src/main/auth-flow.ts (/api/desktop/auth/exchange) silently fails for any contributor running pnpm dev:desktop from a fresh shell with no NODE_EXTRA_CA_CERTS exported, surfacing only as auth_signin_failed{reason:"exchange_failed"} with no other diagnostic.

This was surfaced by PR #627 post-merge live verification (commit bf1699fa5). Standalone reproducer from a strictly scrubbed env:

env -i HOME=$HOME PATH=$PATH \
  node -e 'fetch("https://lightfast.localhost/...").catch(e => console.error(e.cause?.code))'
# → SELF_SIGNED_CERT_IN_CHAIN

env -i HOME=$HOME PATH=$PATH NODE_EXTRA_CA_CERTS=$HOME/.portless/ca.pem \
  node -e 'fetch("https://lightfast.localhost/...").then(r => console.log(r.status))'
# → 404 (TLS succeeded, request reached server)

Behavior

scripts/with-desktop-env.mjs --print results across three cases:

Parent env Resolved origin Output
empty https://lightfast.localhost injects NODE_EXTRA_CA_CERTS=~/.portless/ca.pem
NODE_EXTRA_CA_CERTS=/tmp/x.pem https://lightfast.localhost passes through /tmp/x.pem (no override)
empty https://app.lightfast.ai no-op (hostname not *.localhost)

Also no-op when ~/.portless/ca.pem does not exist (contributor without portless installed).

Future work

The architecturally correct fix is to switch the desktop main-process fetch (currently only one call site, auth-flow.ts:248) to Electron's net.fetch, which routes through Chromium's networking and trusts the macOS keychain. That would obviate this env injection entirely. Tracked in thoughts/shared/research/2026-05-06-desktop-exchange-tls-portless.md (added in this PR).

Test plan

  • node scripts/with-desktop-env.mjs --print from clean shell → injects
  • Same with NODE_EXTRA_CA_CERTS already set in parent → respects parent
  • Same with LIGHTFAST_APP_ORIGIN=https://app.lightfast.ai → no-op
  • Live PKCE flow on a fresh shell without NODE_EXTRA_CA_CERTS exported reaches auth_signed_in (covered by PR 627 verification + this script change)

🤖 Generated with Claude Code

Electron's main process inherits Node's undici fetch as the global
`fetch`, which doesn't honor the macOS keychain. portless mints dev
HTTPS certs from a local root at ~/.portless/ca.pem, so any Node-side
fetch from desktop main against the dev aggregate (https://*.localhost)
fails the TLS handshake with SELF_SIGNED_CERT_IN_CHAIN. Concretely:
the PKCE exchange POST in apps/desktop/src/main/auth-flow.ts
(`/api/desktop/auth/exchange`) silently fails for any contributor
running `pnpm dev:desktop` from a fresh shell, with no diagnostic
beyond `auth_signin_failed{reason:"exchange_failed"}`.

Mirror the injection lightfast-dev's `proxy app-runtime` already does
for the next-dev child: when the resolved LIGHTFAST_APP_ORIGIN points
at a `*.localhost` host AND ~/.portless/ca.pem exists, set
NODE_EXTRA_CA_CERTS on the spawned Electron env. Respect any
pre-existing parent NODE_EXTRA_CA_CERTS so contributors with a custom
trust setup aren't overridden.

A more architecturally correct fix is to switch the main-process fetch
to Electron's net.fetch (Chromium net stack, trusts the OS keychain),
which would obviate this injection entirely. Tracked in
thoughts/shared/research/2026-05-06-desktop-exchange-tls-portless.md.

Verified --print across three cases:
- clean shell           → NODE_EXTRA_CA_CERTS=~/.portless/ca.pem injected
- parent override set   → parent value respected, not overridden
- production-ish origin → no-op (hostname is not *.localhost)

Standalone TLS reproducer from a strictly scrubbed env confirms the
underlying bug: node fetch against https://lightfast.localhost throws
SELF_SIGNED_CERT_IN_CHAIN without the env var, returns server bytes
with it.
Diagnoses the SELF_SIGNED_CERT_IN_CHAIN failure during PKCE exchange,
documents the fix paths (NODE_EXTRA_CA_CERTS injection vs. switching
to Electron net.fetch), and pairs with the script-level fix in this
branch.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lightfast-app Ready Ready Preview, Comment May 6, 2026 9:05am
lightfast-www Ready Ready Preview, Comment May 6, 2026 9:05am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
lightfast-platform Ignored Ignored Preview May 6, 2026 9:05am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (2)
  • scripts/with-desktop-env.mjs is excluded by !scripts/**
  • thoughts/shared/research/2026-05-06-desktop-exchange-tls-portless.md is excluded by !thoughts/**

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 43797733-7c66-4501-a204-a2a64142d4d5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/desktop-portless-ca-injection
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/desktop-portless-ca-injection

Comment @coderabbitai help to get the list of available commands and usage tips.

@jeevanpillay jeevanpillay merged commit 1311884 into main May 6, 2026
14 checks passed
@jeevanpillay jeevanpillay deleted the fix/desktop-portless-ca-injection branch May 6, 2026 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant