* fix(start-scripts): find bundled privacy_core.dll next to script start.bat and start.sh only checked the source-tree DLL path (``privacy-core/target/release/privacy_core.dll``), not the bundled location where MSI/AppImage/DMG installers stage the library directly next to the script in backend-runtime/. Users running start.bat from inside an MSI install dir (a documented workaround when the desktop shell crashes) saw a scary "install Rust" warning even though the DLL was sitting right next to them. See issue #319 for the user-reported confusion. Fix: add a fallback check for the bundled location before falling through to the "build privacy-core from source" warning. Source-tree behavior unchanged — the source path is still preferred when present. Also re-stamps the v0.9.81 source archive: ``release_digests.json`` v0.9.81 zip hash updated to point at the rebuilt source archive that contains these script changes. MSI/EXE/sig hashes are unchanged (the scripts live at the repo root, not inside the desktop bundle). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(#319): bundle start.bat + start.sh into the MSI/EXE installers Follow-up to the start-script DLL fallback fix in the prior commit. ChrisMTheMan's report on #319 made it clear the workaround flow was: 1. MSI install crashes on launch (different bug, fixed in v0.9.81) 2. User goes looking for start.bat to launch the backend manually 3. start.bat isn't in their install dir, so they go fetch it from GitHub 4. They get a working script but it doesn't know about the bundled privacy_core.dll layout, so they see a scary "install Rust" warning The prior commit fixed step 4. This commit fixes step 3 — start.bat and start.sh now ship inside the MSI/EXE installers (staged into backend-runtime/ next to the privacy_core.dll they expect to find). After the rebuild lands, an MSI user looking for these scripts finds them right inside their install dir, already pointing at the correct bundled DLL location. What changed ------------ * ``build-backend-runtime.cjs`` now has a ``stageStartScripts()`` step that copies start.bat and start.sh from the repo root into the staged backend-runtime/. Preserves the executable bit on .sh under POSIX. * ``release_digests.json`` v0.9.81 block hashes refreshed for the rebuilt MSI / EXE / source-zip (the scripts being bundled changed the MSI/EXE contents; the source zip also includes the start-script fix from the prior commit). ShadowBroker_v0.9.81.zip 6.06 MB af8c87ccdece8fbb9aadc6be63cce10d3fcba74e6d87ef83289dda6d555fd270 ShadowBroker_0.9.81_x64_en-US.msi 122.4 MB 8977c9a1c54e1f0d030436be9c4e3d81d766cc0080699eb747649095f360c7ff ShadowBroker_0.9.81_x64-setup.exe 76.5 MB 4e866fa0423c0c2470ed32f4809167a7815dc23ee7762b69e95681c1f3a28250 Post-merge plan --------------- Force-move the v0.9.81 tag to this commit and replace ALL release assets on the GitHub release: zip, msi, exe, both .sig files, latest.json, SHA256SUMS.txt, release-manifest.json. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Desktop Shell
Native-side scaffold for the ShadowBroker desktop boundary.
Purpose
This package owns the accepted desktop track:
- native privileged control routing through Rust
- authoritative policy enforcement and audit
- packaged managed local backend ownership
- packaged desktop runtime with same-origin
/api/* - tray/menu-bar lifecycle
- optional reduced-trust browser companion mode
- desktop packaging/release tooling
Browser mode remains intact; the desktop path layers on top of it.
Source of truth
The shared desktop control contract still lives in:
frontend/src/lib/desktopControlContract.tsfrontend/src/lib/desktopControlRouting.ts
The native side imports that contract instead of redefining it.
Layout
desktop-shell/
├── package.json
├── scripts/
│ └── run-desktop-build.cjs # Cross-platform npm build wrapper
├── src/
│ ├── runtimeBridge.ts
│ ├── nativeControlRouter.ts
│ ├── nativeControlAudit.ts
│ └── handlers/
└── tauri-skeleton/
├── dev.sh
├── build.sh
├── build.ps1
├── RELEASE.md
├── scripts/
│ ├── generate-icons.cjs
│ └── write-release-manifest.cjs
└── src-tauri/
├── Cargo.toml
├── tauri.conf.json
├── icons/ # Generated branded bundle assets
└── src/
├── main.rs
├── bridge.rs
├── policy.rs
├── tray.rs
├── companion.rs
├── companion_server.rs
├── handlers.rs
└── http_client.rs
Desktop runtime model
Native privileged path
The accepted 27-command privileged path remains native-only:
- frontend bridge detection builds
window.__SHADOWBROKER_LOCAL_CONTROL__ - privileged requests go through Tauri IPC
- Rust policy enforces capability/profile rules before dispatch
- Rust audit ring records all outcomes
- the native admin key never reaches webview JavaScript
Packaged main window
Packaged builds now own a bundled local backend runtime by default, then use an
app-level loopback server as the native window origin so ordinary
non-privileged /api/* fetches resolve same-origin instead of dying on static
asset serving.
Browser companion
Browser companion remains:
- optional
- loopback-only
- explicitly enabled
- reduced-trust
It never receives the native bridge injection, and it is not a drop-in replacement for standalone browser mode.
Packaging / release flow
Use any of these entrypoints:
./desktop-shell/tauri-skeleton/build.sh
./desktop-shell/tauri-skeleton/build.ps1
npm --prefix desktop-shell run build:desktop
Use --clean to remove the previous export, generated icons, and old installer
artifacts before rebuilding.
The release flow now:
- generates branded desktop icons
- stages a desktop-only frontend export tree without Next server-only route handlers / middleware
- stages a managed backend runtime bundle from
backend/ - builds the frontend export for Tauri packaging
- copies the export to
companion-www - runs
cargo tauri build - writes
SHA256SUMS.txtandrelease-manifest.jsonnext to the bundle output
If the Tauri CLI is missing, the build scripts now fail immediately with the
correct cargo install tauri-cli@^2 instruction.
The repo also now has a no-secrets desktop matrix workflow at
../.github/workflows/desktop-release.yml
that builds unsigned desktop artifacts on Windows, macOS, and Linux and turns
v*.*.* tags into downloadable GitHub release assets.
See tauri-skeleton/RELEASE.md for release-path
details and tauri-skeleton/RELEASE_INPUTS.md
for the future inputs that only matter once public distribution trust becomes a
goal.
Current status
This is a runnable desktop foundation with a repeatable packaging path.
What works:
- native desktop window with full app UI
- packaged desktop ownership of a bundled local backend runtime
- packaged desktop auto-generates and persists its local backend admin/private-plane secrets on first run
- packaged desktop-managed backend blocks legacy
16-hex node-ID compat and directlegacy_agent_idlookup by default - packaged same-origin
/api/*path for non-privileged data - Rust-side policy enforcement and audit
- tray/menu-bar background lifecycle
- macOS dock reopen
- optional reduced-trust browser companion opener
- branded Tauri/Windows/macOS bundle icons
- release manifest + checksum generation
What is still not done:
- code signing / notarization
- auto-update mechanism
- final installer copy / splash polish
- DM/data-plane native migration
- standalone-browser-equivalent companion parity
Managed backend defaults
The packaged desktop-managed backend now defaults to the hardened posture for compatibility sunset work:
MESH_BLOCK_LEGACY_NODE_ID_COMPAT=trueMESH_ALLOW_LEGACY_NODE_ID_COMPAT_UNTIL=unless an operator sets a dated temporary migration overrideMESH_BLOCK_LEGACY_AGENT_ID_LOOKUP=true
That default applies to the app-owned managed backend created under
%LOCALAPPDATA%. Source/server deployments remain operator-controlled and can
set those flags independently.
If a managed desktop operator leaves MESH_BLOCK_LEGACY_NODE_ID_COMPAT=false
in the managed backend .env, bootstrap now normalizes it back to true.
The only supported escape hatch for legacy 16-hex node IDs is a dated
MESH_ALLOW_LEGACY_NODE_ID_COMPAT_UNTIL=YYYY-MM-DD override.
MESH_BLOCK_LEGACY_AGENT_ID_LOOKUP=false is still preserved if an operator
intentionally needs that separate migration path.