You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
enable_dns_mode locates the privileged proxy binary via:
let proxy_path = std::env::current_exe().ok().and_then(|p| p.parent().map(|dir| dir.join(\"mhost-dns-proxy\"))).unwrap_or_else(|| PathBuf::from(\"mhost-dns-proxy\"));
Combined with a hand-rolled osascript elevation shell-script (platform.rs::enable_dns_mode), this has accumulated costs the 0.3.x branch is starting to trip over:
Bundle path discovery is fragile on macOS release builds: the .app contents are Tauri-determined; relying on current_exe().parent() works there but couples our code to whatever Tauri 2 currently does with main-bin placement. If Tauri changes the resolution (it has, between 1.x and 2.x), we break silently.
Cross-platform friction (改为支持本地 hosts 模式和本地 DNS 模式两种模式 #67): Windows / Linux non-macOS ports need the same privileged binary but cannot reuse the current osascript elevation. The cross-platform story gets cleaner if we go through Tauri's resource model.
Capability / security shape mismatch: launching a privileged binary via osascript is an out-of-band side-channel that the IPC layer cannot introspect or audit. A first-class Command::sidecar(...) invocation integrates with Tauri's permission model.
#156 (merged) made the existing path safe (pre-check + script quoting + transactional cleanup). This issue is the longer-term refactor that swaps the mechanism out.
Goal
Replace the manual osascript + current_exe().parent().join(...) flow with Tauri 2's first-class sidecar bundle mechanism. After this lands:
mhost-dns-proxy is declared in tauri.conf.json::bundle.externalBin with its target-triple suffix
The privileged launch happens via tauri::process::Command::new(\"mhost-dns-proxy\") from tauri-plugin-shell, with execute allowed by capabilities
scripts/dev.sh becomes unnecessary — pnpm tauri dev builds both the main bin and the sidecar up front
The privileged proxy no longer needs an embedded shell script at all; what used to be [ -x ] / kill -0 / trap becomes Rust control flow in the IPC handler
Cargo side — name the sidecar binary with target-triple suffix required by Tauri 2 sidecar lookup:
Add a build.rs or rename [[bin]] so output is mhost-dns-proxy-<target-triple> instead of mhost-dns-proxy
Or: configure tauri.conf.json::bundle.externalBin paths to point at the unsuffixed output and let Tauri rename (Tauri 2 actually accepts paths and computes the suffix automatically, but verify)
Add tauri-plugin-shell allowlist — Command::new(\"mhost-dns-proxy\").execute() needs shell:allow-execute or per-binary capability
Cargo.toml (root): add tauri-plugin-shell = \"2\" dep
capabilities/default.json: add the sidecar to the permitted executables list
Frontend / IPC: enable_dns_mode Rust command replaces osascript invocation with Command::new(\"mhost-dns-proxy\").args([\"--listen\", \"53\", \"--target\", \"1053\"]).spawn() (or .execute() depending on how we want sudo semantics)
Privilege boundary:
macOS still needs root for bind :53. Tauri's sidecar runs as the app's uid, same as the main process — i.e. not root.
For bind :53 we still need a privilege step. Two options:
a. Keep a tiny SMJobBless-style helper (only on macOS) and only that needs root
b. Use setcap (Linux) / service (Windows) / osascript once at install time to install the SUID bit
Practical answer: keep osascript for the bind-53 step but launch just the binary via Command::sidecar(\"mhost-dns-proxy\"). Net result: one less custom shell script, but the bind-53 privilege elevation stays where it has to.
Tests: cleaner — no more build_enable_script + shell_single_quote + fragile sh execution. Spawning a Command::sidecar from a test harness is testable directly.
scripts/dev.sh: can be retired entirely (or kept as a thin shim); pnpm tauri dev handles the sidecar build via bundle.externalBin
Open questions
Tauri 2 sidecar discovery on dev mode: does tauri build --debug build the sidecar automatically, or do we still need a manual cargo build step? If not, keep a stripped-down dev script.
macOS signing / notarization: sidecars must be signed with the same Developer ID as the main app; confirm whether Tauri 2's tauri-action handles this correctly out-of-the-box for cross-compiled targets (CI matrix).
Capabilities shape: do we need shell:allow-execute scoped to just mhost-dns-proxy, or a tighter per-binary capability?
Windows / Linux parity: this enables 改为支持本地 hosts 模式和本地 DNS 模式两种模式 #67 (cross-platform DNS mode). Should we scope this PR to macOS-only first (same as today) and add a follow-up for Windows / Linux privilege model?
Context
enable_dns_modelocates the privileged proxy binary via:Combined with a hand-rolled
osascriptelevation shell-script (platform.rs::enable_dns_mode), this has accumulated costs the 0.3.x branch is starting to trip over:scripts/dev.shwrapper ([Bug] DNS mode 在 pnpm tauri dev 下静默失效:mhost-dns-proxy binary 缺失导致所有查询卡死 #155) becausepnpm tauri devdoes not auto-build the sidecar; nothing tells the user this whenpnpm tauri devruns in stock form..appcontents are Tauri-determined; relying oncurrent_exe().parent()works there but couples our code to whatever Tauri 2 currently does with main-bin placement. If Tauri changes the resolution (it has, between 1.x and 2.x), we break silently.osascriptelevation. The cross-platform story gets cleaner if we go through Tauri's resource model.osascriptis an out-of-band side-channel that the IPC layer cannot introspect or audit. A first-classCommand::sidecar(...)invocation integrates with Tauri's permission model.#156 (merged) made the existing path safe (pre-check + script quoting + transactional cleanup). This issue is the longer-term refactor that swaps the mechanism out.
Goal
Replace the manual
osascript+current_exe().parent().join(...)flow with Tauri 2's first-class sidecar bundle mechanism. After this lands:mhost-dns-proxyis declared intauri.conf.json::bundle.externalBinwith its target-triple suffixtauri::process::Command::new(\"mhost-dns-proxy\")fromtauri-plugin-shell, withexecuteallowed by capabilitiesscripts/dev.shbecomes unnecessary —pnpm tauri devbuilds both the main bin and the sidecar up front[ -x ]/kill -0/trapbecomes Rust control flow in the IPC handlerApproach sketch
build.rsor rename[[bin]]so output ismhost-dns-proxy-<target-triple>instead ofmhost-dns-proxytauri.conf.json::bundle.externalBinpaths to point at the unsuffixed output and let Tauri rename (Tauri 2 actually accepts paths and computes the suffix automatically, but verify)tauri.conf.json:bundle.externalBin: [\"bin/mhost-dns-proxy\"](or absolute path)tauri-plugin-shellallowlist —Command::new(\"mhost-dns-proxy\").execute()needs shell:allow-execute or per-binary capabilityCargo.toml(root): addtauri-plugin-shell = \"2\"depcapabilities/default.json: add the sidecar to the permitted executables listenable_dns_modeRust command replacesosascriptinvocation withCommand::new(\"mhost-dns-proxy\").args([\"--listen\", \"53\", \"--target\", \"1053\"]).spawn()(or.execute()depending on how we want sudo semantics)bind :53. Tauri's sidecar runs as the app's uid, same as the main process — i.e. not root.bind :53we still need a privilege step. Two options:a. Keep a tiny SMJobBless-style helper (only on macOS) and only that needs root
b. Use
setcap(Linux) / service (Windows) /osascriptonce at install time to install the SUID bitosascriptfor the bind-53 step but launch just the binary viaCommand::sidecar(\"mhost-dns-proxy\"). Net result: one less custom shell script, but the bind-53 privilege elevation stays where it has to.validate_proxy_binary(fix(dns): surface silent failures when mhost-dns-proxy binary is missing (#155) #156) in favour of Tauri-resolved path +Command::sidecar().status()smoke check on startupDropon the supervisor task holding theChildhandlebuild_enable_script+shell_single_quote+ fragile sh execution. Spawning aCommand::sidecarfrom a test harness is testable directly.scripts/dev.sh: can be retired entirely (or kept as a thin shim);pnpm tauri devhandles the sidecar build viabundle.externalBinOpen questions
tauri build --debugbuild the sidecar automatically, or do we still need a manualcargo buildstep? If not, keep a stripped-down dev script.tauri-actionhandles this correctly out-of-the-box for cross-compiled targets (CI matrix).shell:allow-executescoped to justmhost-dns-proxy, or a tighter per-binary capability?Linked
Suggested acceptance criteria
pnpm tauri devon a fresh clone withoutscripts/dev.sh: DNS mode still workspnpm tauri buildproduces a signed bundle wheremhost-dns-proxyis bundled as a sidecar (not bundled-inside or copy-pasted at runtime)shell:allow-executewith*enable_dns_modeIPC handler still returns clearErron sidecar missing (no silent failure)disable_dns_modecleanup is idempotent and reaches port 53 cleanup via RustDrop, not via shell trapaarch64-apple-darwinandx86_64-apple-darwin