Skip to content

fix(desktop): route updater manifest through OS trust and guard bare fetch - #3127

Merged
benjaminshafii merged 2 commits into
devfrom
fix/trust-path-guard
Jul 25, 2026
Merged

fix(desktop): route updater manifest through OS trust and guard bare fetch#3127
benjaminshafii merged 2 commits into
devfrom
fix/trust-path-guard

Conversation

@benjaminshafii

Copy link
Copy Markdown
Member

What broke

resolveCorrectArchitectureDownloadUrl in apps/desktop/electron/main.mjs fetched the electron-updater manifest from github.com with bare Node fetch, twenty lines away from six correct electronNet.fetch calls in the same file.

Bare fetch only trusts bundled Mozilla roots and ignores system proxy configuration. On a corporate network that inspects TLS, that one call fails while the whole rest of the app works — the hardest class of bug to diagnose. It also had no timeout.

What this does

  1. Fixes the leak. Routes it through electronNet.fetch (Chromium stack: OS trust store + system proxy) with a 10s abort. Failure behavior is unchanged — still warns and returns null, so the banner can never block startup.

  2. Stops the next one. apps/server/src already bans bare fetch via no-bare-fetch.test.ts. This extends the same enforcement to the two remaining surfaces that talk to the network:

Surface Sanctioned call Guard
apps/server/src externalFetch / loopbackFetch already enforced
apps/desktop/electron electronNet.fetch new
apps/installer/src fetchWithSystemCa new

Genuine loopback call sites carry an explicit // loopback-fetch: <reason> marker naming why 127.0.0.1 is provable, so the exemption is reviewable rather than invisible. Four in electron (sidecar health, runtime JSON, daemon shutdown, CDP discovery), one in the installer (its own local server).

Compatibility

No breaking changes for self-hosted deployments. No configuration, environment variable, API, Helm chart, or database surface is touched. The only behavior change is that one manifest request now honors OS trust and system proxy — strictly more permissive than before, so networks that worked continue to work and inspected networks start working.

Tests run

```
pnpm --filter @openwork/desktop test # 117 tests, 116 pass, 0 fail, 1 skipped
cd apps/installer && bun test # 53 pass, 0 fail, 132 expect() calls
pnpm --filter @openwork/desktop typecheck:electron # pass
pnpm typecheck # pass
```

Negative controls — each guard was proven to actually catch the bug, in files this PR does not otherwise modify:

Reverting the fix in `main.mjs`:
```
Error: Unmarked bare fetch is banned in apps/desktop/electron because bare fetch
bypasses the OS certificate trust store and system proxy; use electronNet.fetch
for external requests, or add // loopback-fetch: if the target is
provably 127.0.0.1/localhost. Offenders:
main.mjs:300
```

Injecting `fetch("https://github.com/x")\` into `electron/updater.mjs` → caught at `updater.mjs:434`.
Injecting the same into `installer/src/config-sources.ts` → caught at `config-sources.ts:331`.
Adding a marked loopback fetch to the same file → passes, confirming the marker path works.
Bun.serve's `async fetch(request)` handler in `installer/src/server.ts` is correctly recognized as a definition, not a call.

Fraimz

Skipped, stated explicitly per AGENTS.md. This change has no user-visible surface: it is two CI guards plus one internal request that swaps its network stack. The observable effect only appears on a TLS-inspecting network, which is already covered end-to-end by the existing `installer-tls-trust` and `desktop-fetch-os-trust` flows. The executable proof for this PR is the negative controls above — each guard demonstrably fails on the exact bug it exists to prevent.

…fetch

The architecture-mismatch banner fetched the electron-updater manifest from
github.com with bare Node fetch, so it only trusted bundled Mozilla roots and
ignored system proxy configuration. On a TLS-inspecting corporate network that
single call failed while the rest of the app worked, with no timeout to bound
it. Route it through electronNet.fetch (Chromium: OS trust store + system
proxy) with a 10s abort, keeping the warn-and-return-null failure behavior.

apps/server/src already bans bare fetch through a test. Extend the same
enforcement to the two remaining surfaces that talk to the network:

- apps/desktop/electron: unmarked bare fetch fails; the four genuine loopback
  call sites (sidecar health, runtime JSON, daemon shutdown, CDP discovery)
  carry a // loopback-fetch: marker naming why 127.0.0.1 is provable.
- apps/installer/src: bare fetch fails; external requests must use
  fetchWithSystemCa. system-ca.ts (defines the wrapper) and ui-html.ts
  (browser-side template) are exempt; Bun.serve's `async fetch(request)`
  handler is recognized as a definition, not a call.

No configuration, API, or chart surface changes.
@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
openwork-app Ready Ready Preview, Comment Jul 25, 2026 6:49pm
openwork-den Ready Ready Preview, Comment Jul 25, 2026 6:49pm
openwork-den-worker-proxy Ready Ready Preview, Comment Jul 25, 2026 6:49pm
openwork-landing Ready Ready Preview, Comment, Open in v0 Jul 25, 2026 6:49pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
openwork-diagnostics Skipped Skipped Jul 25, 2026 6:49pm

@benjaminshafii

Copy link
Copy Markdown
Member Author

Daytona end-to-end validation — Linux complete, Windows build running

Tested the actual PR in a clean Daytona Linux Electron sandbox (Electron 35.7.5), on a combined branch containing all six enterprise-network PRs.

Real app / real changed function

From the live renderer through the preload bridge into the changed Electron main-process function:

await window.__OPENWORK_ELECTRON__.system.getArchitectureInfo()

Observed:

{
  "appArch":"x64",
  "appArchLabel":"Intel",
  "systemArch":"x64",
  "systemArchLabel":"Intel",
  "mismatch":false,
  "platform":"linux",
  "version":"0.0",
  "downloadUrl":"https://github.com/different-ai/openwork/releases/latest/download/openwork-linux-x86_64-0.18.1.AppImage",
  "releaseUrl":"https://github.com/different-ai/openwork/releases/latest"
}

This is the exact changed path: resolveCorrectArchitectureDownloadUrl() used electronNet.fetch, followed GitHub's real redirects (currently through release-assets.githubusercontent.com), parsed latest-linux.yml, selected the x64 file, and returned the correct live release URL.

Enterprise-CA differential on a real machine

Created a private CA + leaf for poc.openwork.test, installed the CA into Debian's system store, and served a real HTTPS updater manifest:

[server]                     UP
curl pinned to private CA    version: 9.9.9
curl using OS store          OK — version: 9.9.9
OLD bare Node fetch          FAIL — UNABLE_TO_VERIFY_LEAF_SIGNATURE
NODE_EXTRA_CA_CERTS          OK — version: 9.9.9
node --use-system-ca         OK — version: 9.9.9

This reproduces the original bug exactly: the old bare-fetch code path rejects the enterprise CA even though the operating system trusts it.

Important Linux boundary discovered

Calling the real Electron __fetch bridge to that same private-CA URL returned:

net::ERR_CERT_AUTHORITY_INVALID

On this Debian image Chromium uses an NSS/user trust database rather than /etc/ssl. The CA is genuinely in the OS store (curl and Node --use-system-ca prove that), but it was not in Chromium NSS and this image has no certutil package available to seed it. Therefore the original PR wording saying electronNet.fetch always means "OS trust store" was too broad on Linux.

I corrected the new guard wording in commit 6cb4b81ac to the accurate cross-platform statement: Chromium's certificate trust path and system proxy. No runtime behavior changed. Windows/macOS are still the intended OS-enterprise-store paths.

Clean-box tests

@openwork/desktop test: 116 pass, 0 fail, 1 skipped (117 total)
apps/installer bun test: 53 pass, 0 fail, 132 assertions
outbound manifest: 35 hosts covered, 31 observed, 0 stale
semi-airgapped-den-contract: 4/4 frames passed

Windows proof in progress

The real repository Windows build workflow is building this combined branch now:
https://github.com/different-ai/openwork/actions/runs/30170381965

I will use its Windows x64 artifact in a Daytona Windows VM with the fake corporate root installed in Cert:\LocalMachine\Root, so the next comment can prove the actual binary's Windows system-store behavior rather than extrapolating from Linux.

@benjaminshafii

Copy link
Copy Markdown
Member Author

Daytona Windows E2E — Passed

Built the exact combined branch containing this PR with the repository's real build-electron-desktop.yml workflow, then ran that Windows x64 artifact in a Daytona Windows VM.

Build workflow (all five jobs passed, including Windows x64 + ARM64):
https://github.com/different-ai/openwork/actions/runs/30170381965

Environment

Windows NT 10.0 x64
OpenWork 0.18.1
Electron 35.7.5 / Chrome 134
Real packaged artifact from the branch build (not a released binary)

Machine-store differential

Installed the checked-in fake corporate root through scripts/support/setup-openwork-tls-repro.ps1, then ran the branch's own OpenWork.exe in Node mode with the checked-in CA probe:

{
  "systemCount": 42,
  "reproInSystem": 6,
  "defaultCount": 150,
  "reproInDefault": 0
}

This is the exact enterprise/GPO case: Windows Cert:\LocalMachine\Root contains the corporate CA; Electron's bundled Mozilla roots do not.

Real packaged app / real Electron main process

Launched the branch artifact in the interactive Administrator console session, connected through its real CDP target, then called the actual preload → IPC → Electron-main __fetch bridge.

Healthy chain, signed by the corporate root:

await window.__OPENWORK_ELECTRON__.invokeDesktop(
  "__fetch",
  "https://poc.openwork.test:8443/",
  { timeoutMs: 10000 }
)

Observed:

{"ok":true,"status":200,"body":"{\"workspaces\":[]}"}

Broken chain (intermediate deliberately removed):

{
  "ok": false,
  "error": "Error invoking remote method 'openwork:desktop': Error: net::ERR_CERT_AUTHORITY_INVALID"
}

So the packaged app accepts the Windows machine-store corporate root, but still rejects an actually broken certificate chain. It is not bypassing verification.

Actual changed function against production GitHub

Called the exact changed function through the real app:

await window.__OPENWORK_ELECTRON__.system.getArchitectureInfo()

Observed:

{
  "appArch":"x64",
  "systemArch":"x64",
  "mismatch":false,
  "platform":"windows",
  "version":"0.18.1",
  "downloadUrl":"https://github.com/different-ai/openwork/releases/latest/download/openwork-win-x64-0.18.1.exe",
  "releaseUrl":"https://github.com/different-ai/openwork/releases/latest"
}

This proves resolveCorrectArchitectureDownloadUrl() used the new electronNet.fetch path, followed GitHub's current redirects, parsed the live updater YAML, selected the Windows x64 file, and returned the correct standard installer URL.

Screenshot

Verdict

Passed on Windows. The runtime change works on the platform where enterprise machine-store/GPO roots matter most, and verification remains strict for broken chains.

The Linux boundary from the prior comment remains intentional and documented: Electron/Chromium may require NSS/user trust in addition to /etc/ssl; the PR's guard wording was corrected in 6cb4b81ac to say "Chromium's certificate trust path and system proxy" rather than overclaim universal OS-store behavior.

@benjaminshafii
benjaminshafii merged commit 72fb04e into dev Jul 25, 2026
11 checks passed
@benjaminshafii
benjaminshafii deleted the fix/trust-path-guard branch July 25, 2026 20:26
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