Skip to content

fix(desktop): preference ownership, update-policy and fuse hardening from the 2026-10 security scan - #2138

Open
paustint wants to merge 4 commits into
mainfrom
sec/desktop-hardening
Open

paustint wants to merge 4 commits into
mainfrom
sec/desktop-hardening

Conversation

@paustint

@paustint paustint commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Desktop-app fixes for 4 findings from the 2026-10-08 Claude Security scan (report in CLAUDE-SECURITY-20261008-124758/, gitignored). Each finding was re-verified against the code first. One commit per finding.

Finding Severity Commit
F2 / F7 Renderer can set dataHistoryFolder (via the /api/me/profile desktop route) and fileDownload.downloadPath (via setPreferences), giving arbitrary file read/write/delete Medium / Medium fix(desktop): stop the renderer from choosing the download and history folders
F20 Update "managed policy" trusts a %ProgramData% JSON file any local user can create Low fix(desktop): ignore a Windows update policy file unless an administrator owns it
F21 Full /desktop-app/auth/verify payload (session JWT + org encryption key) logged on a zod mismatch Low fix(desktop): stop logging the auth payload when the response fails validation
F25 Packaged app ships with the runAsNode fuse enabled Low fix(desktop): disable the runAsNode Electron fuse

Behavior changes worth a look

  • electronAPI.selectFolder is removed and replaced by pickDownloadFolder, which shows the folder dialog and persists the choice in the main process (same shape as pickDataHistoryFolder). Settings.tsx was the only caller. Main and renderer ship together, so no shim.
  • Every renderer entry point that accepts preferences (setPreferences IPC and the /api/me/profile desktop route) now runs through withoutMainProcessOwnedPreferences, which drops dataHistoryFolder and keeps the stored fileDownload.downloadPath. The one renderer-settable value is an empty download path, which only turns the save-as prompt back on ("Ask every time").
  • On Windows, an existing C:\ProgramData\Jetstream\policy.json created by a standard user is now ignored (owner SID must be S-1-5-18 SYSTEM or S-1-5-32-544 Administrators, read via Get-Acl). Files created by an elevated process or MDM keep working. Docs updated.
  • Packaged builds no longer honor ELECTRON_RUN_AS_NODE; the afterPack smoke test still strips it from the spawn env. A repo grep found no re-execution of process.execPath as Node.

Test plan

  • nx run-many -t typecheck -p jetstream-desktop jetstream-desktop-client desktop-types
  • nx run jetstream-desktop:test (6 files, 138 tests; 5 new preference-stripping cases, 2 new owner-SID cases)
  • pnpm --dir apps/docs lint
  • Manual: Settings → Downloads → Choose folder picks and persists; "Ask every time" still clears it
  • Manual (Windows): admin-created policy.json still applies, user-created one is ignored (no Windows box here; the owner-SID decision is unit-tested, the PowerShell read is best-effort fail-closed)
  • CI

Copilot AI balanced review requested due to automatic review settings October 8, 2026 17:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Policy validation remains vulnerable to path races, and earlier auth-response branches can still log secrets.

3 open findings
What changed in this PR

Hardens desktop preference ownership, update policy validation, auth logging, and Electron fuses.

Changes:

  • Moves folder selection and persistence into the main process.
  • Restricts Windows policy files by owner SID and redacts validation logs.
  • Disables Electron’s runAsNode fuse.
File Description
libs/​desktop-types/​src/​lib/​desktop-app.types.ts Updates folder-picker IPC types.
electron-builder.config.js Disables runAsNode.
apps/​jetstream-desktop/​src/​services/​persistence.service.ts Sanitizes renderer preferences.
apps/​jetstream-desktop/​src/​services/​ipc.service.ts Adds main-process download picker.
apps/​jetstream-desktop/​src/​services/​api.service.ts Redacts schema-mismatch logs.
apps/​jetstream-desktop/​src/​services/​__tests__/​persistence.service.spec.ts Tests preference ownership.
apps/​jetstream-desktop/​src/​preload.ts Exposes the replacement picker API.
apps/​jetstream-desktop/​src/​controllers/​user.desktop.controller.ts Sanitizes profile preference updates.
apps/​jetstream-desktop/​src/​config/​update-policy.ts Checks Windows policy ownership.
apps/​jetstream-desktop/​src/​config/​__tests__/​update-policy.spec.ts Tests trusted owner SIDs.
apps/​jetstream-desktop-client/​src/​app/​components/​settings/​Settings.tsx Uses main-process folder selection.
apps/​docs/​docs/​getting-started/​desktop-app-updates.mdx Documents Windows ownership requirements.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/jetstream-desktop/src/config/update-policy.ts Outdated
Comment thread apps/jetstream-desktop/src/services/api.service.ts
Comment thread apps/jetstream-desktop-client/src/app/components/settings/Settings.tsx Outdated
…y folders

Both paths aim file I/O at a directory: history reads, writes and deletes
go under one, every auto-saved download under the other. The IPC handler
stripped dataHistoryFolder but not fileDownload.downloadPath, and the
/api/me/profile desktop route stripped neither, so renderer code could
point either at any directory. The folder dialog now runs and applies its
choice in the main process (pickDownloadFolder replaces selectFolder), and
every renderer entry point drops both fields.
Copilot AI balanced review requested due to automatic review settings October 8, 2026 19:09
@paustint
paustint force-pushed the sec/desktop-hardening branch from 5073775 to fdf12e0 Compare October 8, 2026 19:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The Windows policy check can reject legitimate administrator-created files, trust writable ACLs, and read untrusted files before validation.

3 open findings
3 resolved since last review

🧠 Review effort: Balanced

Comment thread apps/jetstream-desktop/src/config/update-policy.ts Outdated
Comment thread apps/jetstream-desktop/src/config/update-policy.ts
Comment thread apps/jetstream-desktop/src/config/update-policy.ts Outdated
Copilot AI balanced review requested due to automatic review settings October 8, 2026 21:41
@paustint
paustint force-pushed the sec/desktop-hardening branch from fdf12e0 to d39e14f Compare October 8, 2026 21:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Windows ACL aliases can bypass write-right detection, and non-JSON auth responses may still leak secrets into logs.

1 open finding
3 resolved since last review

🧠 Review effort: Balanced

Comment thread apps/jetstream-desktop/src/config/update-policy.ts Outdated
Copilot AI balanced review requested due to automatic review settings October 10, 2026 15:06
@paustint
paustint force-pushed the sec/desktop-hardening branch from d39e14f to 126f9bc Compare October 10, 2026 15:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The Windows policy trust check can still accept writable ACL configurations.

2 open findings
1 resolved since last review

🧠 Review effort: Balanced

Comment thread apps/jetstream-desktop/src/config/update-policy.ts
Comment thread apps/jetstream-desktop/src/config/update-policy.ts
…ator owns it

The default ACL on %ProgramData% lets a standard user create the Jetstream
folder and policy.json, which marked every other user's install as managed
and removed their update checks. The file now counts only when its owner
SID is SYSTEM or Administrators; the macOS and Linux locations already
require administrator rights to write.
…alidation

A successful verify response carries the rotated session token and the org
file encryption key; on a schema mismatch (server ahead of an un-updated
desktop build) the whole payload went to main.log, which is less protected
than the files it unlocks and is routinely attached to support emails.
Only the payload keys and zod issues are logged now.
Nothing in the app re-executes itself as Node, and the fuse let any local
process run arbitrary JavaScript under the signed, notarized Jetstream
binary via ELECTRON_RUN_AS_NODE=1.
Copilot AI balanced review requested due to automatic review settings October 10, 2026 16:17
@paustint
paustint force-pushed the sec/desktop-hardening branch from 126f9bc to 53e6386 Compare October 10, 2026 16:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Windows policy validation remains bypassable, and auth bodies can still be logged on content-type mismatches.

2 open findings
2 resolved since last review

🧠 Review effort: Balanced

Comment thread apps/jetstream-desktop/src/config/update-policy.ts
Comment thread apps/jetstream-desktop/src/config/update-policy.ts

This branch has not been deployed

No deployments
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.

2 participants