Skip to content

feat(tauri): support self-signed remote HTTPS via server-backed proxy#333

Merged
shantur merged 13 commits intoNeuralNomadsAI:devfrom
Pagecran:feat/linux-self-signed-proxy-spike
Apr 19, 2026
Merged

feat(tauri): support self-signed remote HTTPS via server-backed proxy#333
shantur merged 13 commits intoNeuralNomadsAI:devfrom
Pagecran:feat/linux-self-signed-proxy-spike

Conversation

@pascalandr
Copy link
Copy Markdown
Contributor

@pascalandr pascalandr commented Apr 18, 2026

Summary

  • add a server-backed HTTPS proxy flow for Tauri remote windows so self-signed remote HTTPS works with the local CLI TLS assets and desktop auth/cookie handling
  • manage remote proxy sessions through packages/server with per-session bootstrap, local-only cleanup, and explicit session lifecycle handling
  • support the Tauri desktop flow across environments, including packaged Windows builds, tauri dev, and updated Linux/macOS handling for the new local HTTPS proxy path

Testing

  • npm run build --workspace @neuralnomads/codenomad
  • cargo check
  • npm run build --workspace @codenomad/tauri-app
  • Windows smoke test for concurrent remote proxy bootstrap sessions
  • Windows manual validation of packaged Tauri remote connection flow

Notes

  • Windows was validated end-to-end.
  • Linux and macOS code paths were updated for the new proxy flow, but runtime validation on those platforms is still pending.

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 18, 2026

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot bot commented Apr 18, 2026

I reviewed the latest PR state as a gatekeeper pass for regressions and implementation quality. I interpreted the request as: review the current diff against dev, focus on correctness/regressions and stronger implementations, and report only substantive findings.

Findings

  1. High: the new Tauri proxy drops any configured base-path prefix after the first navigation, so remote servers hosted under a subpath will break on Windows/non-Linux.

    • packages/server/src/server/routes/remote-servers.ts:101-112 explicitly allows normalized remote URLs like https://host/some/prefix.
    • But the proxy rebuilds upstream requests with build_upstream_url() by cloning the base URL and then doing url.set_path(uri.path()) (packages/tauri-app/src-tauri/src/remote_proxy.rs:189-193). If the user connects to https://host/some/prefix, a navigation to /api/auth/status is forwarded to https://host/api/auth/status instead of https://host/some/prefix/api/auth/status.
    • The same prefix-loss exists in rewrite_referer_header() (packages/tauri-app/src-tauri/src/remote_proxy.rs:282-290).
    • This is a real regression versus the direct-navigation flow, because Electron and Linux keep loading the original URL directly, while this proxy path changes request semantics only on the proxied platforms.
  2. Medium: Windows trust-store failure is handled as "log and continue", but the code still starts an HTTPS localhost proxy with the untrusted cert, which likely leaves the user with a broken remote window and no actionable error.

    • In packages/tauri-app/src-tauri/src/main.rs:309-326, trust_cert_in_store() failure is swallowed with eprintln!, yet Some(ProxyTlsConfig { ... }) is still returned and used.
    • That means the app proceeds to serve the local proxy over HTTPS even when the cert was not trusted or the user declined the Windows prompt.
    • There is no Linux-style bypass path on Windows for that local proxy cert, so this should either fail fast with a user-facing error or explicitly fall back to HTTP. The current behavior is brittle and hard to diagnose.

Validation Notes

  • I checked the PR diff against dev, including the new remote_proxy.rs, linux_tls.rs, cert_manager.rs, and the main.rs integration.
  • I also ran cargo check in packages/tauri-app/src-tauri, but the build does not complete in this workspace because the existing build script cannot find resources/server; that prevented a full compile validation here.

--
Yours,
CodeNomadBot

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 18, 2026

@pascalandr - The packages/server already generates and manages the certificate, why not we use that instead of trying to create / manage the certificate in Tauri separately?

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 18, 2026

@CodeNomadBot

Can Tauri use the same certificate thats generated by packages/server ?

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot bot commented Apr 18, 2026

I interpreted the question as: can the Tauri localhost proxy reuse the TLS material already generated by packages/server, instead of generating and managing a separate cert in src-tauri?

Short answer: yes, technically it can, but not as a drop-in reuse of the current cert_manager.rs flow.

What I found:

  • packages/server already generates a CA + leaf cert pair under the config dir:
    • packages/server/src/server/tls.ts:60-64
    • ca-cert.pem, server-cert.pem, server-key.pem
  • That leaf cert is already valid for localhost and 127.0.0.1 because the server TLS generator always includes those SANs:
    • packages/server/src/server/tls.ts:231-260
  • So from a hostname/SAN perspective, the Tauri localhost proxy could serve that same leaf cert.

Why it is not a straight swap:

  1. The server cert is CA-signed, not self-signed.

    • The current Tauri code trusts a single self-signed leaf directly in the Windows root store.
    • If Tauri reuses packages/server certs, the correct thing to trust is the server CA (ca-cert.pem), not just the leaf. Otherwise rotation gets awkward and trust semantics are wrong.
  2. The server cert lifecycle is tied to CLI HTTPS, not the proxy.

    • resolveHttpsOptions() only generates TLS material when server HTTPS is enabled:
      • packages/server/src/server/tls.ts:39-57
    • So Tauri would now depend on the local CLI having generated certs already, which is a coupling the current proxy code does not have.
  3. Rotation behavior differs.

    • The server leaf rotates every ~30 days and the CA every ~365 days:
      • packages/server/src/server/tls.ts:34-38, 68-117
    • The current Tauri proxy cert is long-lived and tracked with its own .trusted marker.
    • If Tauri reuses server TLS, the trust marker should really track the CA identity, not a separate proxy leaf.

My gatekeeper take:

  • Reusing the packages/server CA/leaf is a valid design direction and is probably cleaner than maintaining two unrelated local cert systems.
  • But it should be done intentionally as a shared local TLS authority, not by just pointing Tauri at server-cert.pem/server-key.pem and keeping the existing trust logic.
  • If you go this route, the better implementation is:
    • Tauri serves the localhost proxy with the server-generated server-cert.pem + server-key.pem
    • Windows trust flow installs ca-cert.pem into the trust store
    • trust-state is based on the CA, not on a separate proxy-specific self-signed cert

So: yes, Tauri can use the same certificate chain generated by packages/server, and architecturally that is probably the better long-term direction, but it needs a small redesign of the trust flow rather than a simple file-path swap.

--
Yours,
CodeNomadBot

@pascalandr
Copy link
Copy Markdown
Contributor Author

pascalandr commented Apr 18, 2026

Addressed in efe5c45:

  • preserve the configured remote base-path when proxying requests and rewritten referers
  • fail fast with a user-facing error if the local HTTPS proxy certificate cannot be trusted, instead of continuing with a broken HTTPS proxy
  • added proxy unit tests covering the base-path cases

Validation:

  • npm run bundle:server && cargo test on the PR branch
  • new remote_proxy tests pass
  • still works on windows

About the CA-signed cert that would be a refactor of the local TLS architecture, not the goal of this PR, which is just to give Tauri a user-facing self-signed remote flow equivalent to Electron with the platform-specific mechanism each runtime actually supports.

@pascalandr
Copy link
Copy Markdown
Contributor Author

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot bot commented Apr 18, 2026

I reviewed the latest PR state as a fresh gatekeeper pass after efe5c45. I interpreted the request as: re-check the current diff against dev, verify whether the previous issues were actually fixed, and look for any remaining regressions or materially better implementation choices.

Findings

  1. Medium: the Windows/non-Linux fallback from local HTTPS proxy to local HTTP proxy is still behaviorally broken for HTTPS-authenticated remotes.
    • In packages/tauri-app/src-tauri/src/main.rs:321-324, if local cert generation fails, the code logs and falls back to None, which makes start_remote_proxy() serve plain HTTP on 127.0.0.1 (packages/tauri-app/src-tauri/src/remote_proxy.rs:82-84).
    • But the upstream CodeNomad server sets auth cookies as Secure when the request is HTTPS (packages/server/src/server/routes/auth.ts:132 and 159-164).
    • The proxy currently preserves cookie attributes except for Domain (packages/tauri-app/src-tauri/src/remote_proxy.rs:360-367), so those Secure cookies remain Secure on the local proxy response. Browsers/webviews will not send them back over the HTTP localhost proxy.
    • Result: this fallback path is not a real fallback for authenticated HTTPS remotes. It can silently degrade into a broken login/session flow instead of a recoverable user experience.
    • Better implementation: fail fast here too, with a user-facing error, instead of downgrading to HTTP for an HTTPS remote flow.

What I Re-checked

  • The base-path regression I reported earlier is fixed in remote_proxy.rs; the new rewrite_request_path() logic and the added unit tests cover the core subpath cases.
  • The Windows trust-store rejection path is improved: it now returns a user-facing error instead of continuing with an untrusted HTTPS localhost proxy.

Validation Notes

  • I reviewed the updated Tauri diff against dev, including main.rs, remote_proxy.rs, linux_tls.rs, and cert_manager.rs.
  • I also ran npm run bundle:server && cargo test remote_proxy -- --nocapture in packages/tauri-app; the new proxy tests pass in this workspace.

--
Yours,
CodeNomadBot

@pascalandr
Copy link
Copy Markdown
Contributor Author

I removed the HTTP fallback for HTTPS remotes, so we now fail fast with a user-facing error instead of silently breaking secure-cookie auth.

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot bot commented Apr 18, 2026

I reviewed the latest PR state again. I interpreted the request as: gatekeep the current branch tip against dev, not just the original Tauri HTTPS work, and call out any remaining regressions, implementation risks, or scope problems.

Findings

  1. High: this PR now bundles unrelated WSL UNC workspace changes, which is a scope and reviewability failure for a PR titled feat(desktop): support self-signed remote HTTPS in Tauri.

    • The branch now includes e965754 and aa69d2c, plus changes in:
      • packages/server/src/workspaces/runtime.ts
      • packages/server/src/workspaces/spawn.ts
      • packages/ui/src/components/directory-browser-dialog.tsx
      • packages/ui/src/components/folder-selection-view.tsx
    • Those files implement Windows/WSL workspace-selection behavior, not Tauri remote HTTPS handling.
    • Bundling them here makes the PR materially harder to reason about, harder to validate, and increases merge risk for an otherwise targeted desktop/TLS change. This should be split out into its own PR.
  2. Medium: the claimed removal of the HTTP fallback is not reflected in the current code path for local cert-generation failure.

    • packages/tauri-app/src-tauri/src/main.rs:321-323 still logs failed to generate proxy cert, falling back to HTTP and passes None into open_remote_window_impl().
    • packages/tauri-app/src-tauri/src/remote_proxy.rs:82-84 still serves the local proxy over plain HTTP whenever tls_config is None.
    • So while the trust-store rejection path now fails fast, the cert-generation failure path is still a downgrade to HTTP, which is exactly the behavior I previously flagged as unsafe for HTTPS remote flows.
    • If the intended behavior changed, the implementation has not caught up with the comment thread.

Re-check Notes

  • The remote proxy base-path fix is still good.
  • The Windows trust-store rejection path is still better than before.
  • The new remote_proxy unit tests still pass.

Validation Notes

  • I checked the current PR diff against dev and verified that the branch now contains unrelated WSL UNC changes in addition to the Tauri HTTPS work.
  • I re-ran npm run bundle:server && cargo test remote_proxy -- --nocapture in packages/tauri-app; the proxy tests pass in this workspace.

--
Yours,
CodeNomadBot

@pascalandr
Copy link
Copy Markdown
Contributor Author

Sorry I mixed up some branches

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot bot commented Apr 18, 2026

I reviewed the latest PR state again. I interpreted the request as: gatekeep the current branch tip against dev, after dropping the mixed-branch noise, and look specifically for remaining regressions or materially better implementations in the Tauri self-signed HTTPS work.

No findings.

What I re-checked:

  • The unrelated WSL UNC changes are no longer part of the effective diff for this PR.
  • The previous base-path regression is fixed in remote_proxy.rs.
  • The previous HTTPS-to-HTTP fallback problem is fixed in packages/tauri-app/src-tauri/src/main.rs; the cert-generation failure path now fails fast with a user-facing error instead of downgrading the local proxy to HTTP.
  • The Linux WebKitGTK TLS-exception path is still scoped to the expected origin and only activates when skipTlsVerify is enabled.
  • I did not find evidence of websocket-dependent remote flows in the current UI code; the remote app traffic here appears to rely on regular HTTP plus EventSource, which the proxy path should continue to support.

Validation notes:

  • I re-checked the current PR diff against dev.
  • I re-ran npm run bundle:server && cargo test remote_proxy -- --nocapture in packages/tauri-app; the proxy tests pass.
  • The Tauri test build still emits dead_code warnings for some helper functions in cert_manager.rs, but I am not treating that as a gatekeeper issue because those helpers are used only on Windows-specific paths and the warning is occurring in this current test/build context.

Residual risk:

  • The proxy tests are focused on base-path rewriting. The end-to-end desktop behavior is still primarily validated manually rather than through automated integration coverage.

--
Yours,
CodeNomadBot

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 18, 2026

@pascalandr -

    // Generate a new self-signed certificate
    fs::create_dir_all(&dir)
        .map_err(|e| format!("Failed to create cert dir {}: {e}", dir.display()))?;

We shouldn't duplicate the certificate management when we already have in server. We should always use that and if not present, should error out rather than creating one in here too.

@pascalandr
Copy link
Copy Markdown
Contributor Author

I agree that reusing the packages/server TLS chain is a reasonable long-term direction, but that would be a separate refactor of the local TLS ownership/trust flow. This PR is narrower: it just gives Tauri a user-facing self-signed remote flow equivalent to Electron, using the platform-specific mechanism each runtime actually supports.

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 18, 2026

@pascalandr - It won't be wise to take on a PR with known technical debt.
Can you implement using the packages/server's managed certificates and remove any creation of certificate from Tauri app as to allow Self Signed certificate, it needs to handle certificates.

Also just to understand if this is currently expected to only work in Windows and Linux or macOS too?

@pascalandr
Copy link
Copy Markdown
Contributor Author

Ok I will rework this to reuse the packages/server's managed certificates.
MacOS is still unverified in this PR. It may be similar to Windows, but I can’t claim support there yet. I still need to test it.

@pascalandr
Copy link
Copy Markdown
Contributor Author

pascalandr commented Apr 18, 2026

@shantur sorry I was skeptical about this refactor at first, but it does work on Windows!!
Tauri now reuses the managed TLS assets from \packages/server instead of generating a separate proxy cert. Linux still uses the explicit WebKitGTK path from this PR. Looking at macOS now.

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 18, 2026

@CodeNomadBot

How does it look now?

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot bot commented Apr 18, 2026

I re-checked the latest PR state against dev.

Findings

  1. High: the current implementation still does not reuse packages/server managed TLS assets; it still creates and manages a separate Tauri proxy certificate.
    • packages/tauri-app/src-tauri/src/main.rs:309-319 still calls cert_manager::ensure_local_cert() and then trust_cert_in_store(&local_cert.cert_der).
    • packages/tauri-app/src-tauri/src/cert_manager.rs is still the source of the proxy certificate lifecycle (proxy.crt, proxy.key, proxy.der, .trusted).
    • Meanwhile the server-managed TLS chain still lives in packages/server/src/server/tls.ts:62-64 as ca-cert.pem, server-key.pem, and server-cert.pem.
    • So the PR is improved compared to earlier passes, but it does not match the later review direction in the thread to reuse the server-managed certificates instead of duplicating certificate ownership in Tauri.

Current Assessment

  • The earlier correctness issues I flagged are resolved:
    • base-path preservation is fixed
    • the insecure HTTP fallback for HTTPS remotes is gone
    • unrelated WSL UNC changes are no longer part of the effective diff
  • But from a gatekeeper standpoint, there is still a substantive architecture/scope issue relative to the latest review direction: certificate ownership is duplicated instead of shared.

Validation Notes

  • I re-checked the current diff against dev.
  • I verified the current code still references ensure_local_cert() / trust_cert_in_store() on the Tauri side and still keeps the server TLS assets separate.

So: better than before, but not yet aligned with the latest requested certificate-management approach.

--
Yours,
CodeNomadBot

@pascalandr
Copy link
Copy Markdown
Contributor Author

pascalandr commented Apr 18, 2026

@shantur sorry I forgot to update the PR

@CodeNomadBot

How does it look now?

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot bot commented Apr 18, 2026

I re-checked the latest PR state against dev.

No findings.

What changed materially since the previous pass:

  • Tauri now loads the server-managed TLS assets instead of creating a separate proxy cert lifecycle.
    • packages/tauri-app/src-tauri/src/cert_manager.rs:30-48 now reads the existing server TLS files.
    • The default paths line up with the server-managed assets in packages/server/src/server/tls.ts:60-64 (ca-cert.pem, server-cert.pem, server-key.pem).
    • The Windows trust flow now trusts the CA certificate (ca_cert_der) rather than a separate self-signed leaf.
  • The earlier correctness fixes are still intact:
    • proxy base-path preservation remains fixed
    • HTTPS remotes no longer degrade to an HTTP localhost fallback
    • the unrelated WSL UNC changes are not part of the effective PR diff

Validation notes:

  • I re-checked the current diff against dev.
  • I re-ran npm run bundle:server && cargo test remote_proxy -- --nocapture in packages/tauri-app; the proxy tests pass.

Residual risk:

  • macOS still appears unverified in the thread, so I would not claim macOS support from this PR yet.
  • Automated coverage is still mainly around proxy path rewriting rather than end-to-end desktop integration.
  • The current Rust test build emits some dead_code warnings in cert_manager.rs, but I am not treating those as a gatekeeper issue here because they are specific to the current build/test context and not a behavioral defect.

--
Yours,
CodeNomadBot

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 18, 2026

@pascalandr - Can we discuss this on Discord please, I think it can be made even simpler

Route remote windows through a trusted local HTTPS proxy so WebView2 accepts secure cookies with self-signed servers. Preserve remote base paths, rewrite origin headers for proxied requests, and keep the certificate helper buildable outside Windows.
Use WebKitGTK TLS exceptions for remote windows so Linux no longer depends on system CA installation or sudo-managed trust stores.
Keep proxied requests under the configured remote base path and fail fast when the local HTTPS proxy certificate cannot be trusted, instead of starting a broken HTTPS proxy.
pascalandr and others added 2 commits April 19, 2026 18:27
Load server-managed TLS certificates (server-cert.pem, server-key.pem,
ca-cert.pem) from the server's TLS directory instead of generating a
separate proxy certificate in Tauri. Also trust the server CA in the
Windows trust store instead of a self-signed proxy cert.

This aligns with the reviewer feedback to avoid duplicating certificate
management across the codebase.
Run remote windows through a server-backed HTTPS proxy so desktop reuses the CLI TLS assets and keeps auth, cleanup, and platform-specific TLS handling in one place. This also hardens Windows/Linux/macOS behavior, restores tauri-dev support, and removes the obsolete in-process Tauri proxy.

Co-authored-by: Shantur Rathore <i@shantur.com>
@pascalandr pascalandr changed the title feat(desktop): support self-signed remote HTTPS in Tauri fix(tauri): support self-signed remote HTTPS via server-backed proxy Apr 19, 2026
@pascalandr pascalandr changed the title fix(tauri): support self-signed remote HTTPS via server-backed proxy feat(tauri): support self-signed remote HTTPS via server-backed proxy Apr 19, 2026
@pascalandr pascalandr force-pushed the feat/linux-self-signed-proxy-spike branch from 4dddd60 to 07345ce Compare April 19, 2026 16:37
@github-actions
Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/24633912717

Artifacts expire in 7 days.
Artifacts: (none found on this run)

@pascalandr
Copy link
Copy Markdown
Contributor Author

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 19, 2026

@pascalandr - I just reviewed old state of PR (before force push), not sure if these are changed. We need to handle the following

  1. User should be asked before trying to install certificate, explaining them what and why are we doing it when they are trying to connect to the remote server that needs skipping TLS verification.
  2. If skip SSL verification is selected, then remote url is used directly, instead of just enabling TLS verification in local proxy
  3. macOS implementation wasn't working, below patch fixes it.
Index: /Users/shantur/Coding/opencode-stuff/codenomad/packages/tauri-app/src-tauri/src/cert_manager.rs
===================================================================
--- /Users/shantur/Coding/opencode-stuff/codenomad/packages/tauri-app/src-tauri/src/cert_manager.rs
+++ /Users/shantur/Coding/opencode-stuff/codenomad/packages/tauri-app/src-tauri/src/cert_manager.rs
@@ -151,8 +151,12 @@
 fn trusted_marker_value(cert_der: &[u8]) -> String {
     cert_der.iter().map(|byte| format!("{byte:02x}")).collect()
 }
 
+fn trusted_marker_file_suffix(cert_der: &[u8]) -> String {
+    trusted_marker_value(cert_der).chars().take(16).collect()
+}
+
 fn has_matching_trusted_marker(cert_der: &[u8]) -> bool {
     trusted_marker_path()
         .ok()
         .and_then(|path| fs::read_to_string(path).ok())
@@ -216,22 +220,24 @@
 #[cfg(target_os = "macos")]
 pub fn trust_cert_in_store(cert_der: &[u8]) -> Result<(), String> {
     use std::process::Command;
 
-    if has_matching_trusted_marker(cert_der) {
+    if has_matching_trusted_marker(cert_der) && macos_cert_is_trusted(cert_der)? {
         return Ok(());
     }
 
     let temp_path = env::temp_dir().join(format!(
         "codenomad-server-ca-{}.cer",
-        trusted_marker_value(cert_der)
+        trusted_marker_file_suffix(cert_der)
     ));
     fs::write(&temp_path, cert_der)
         .map_err(|e| format!("Failed to write temporary certificate {}: {e}", temp_path.display()))?;
 
+    let keychain_path = resolve_macos_user_keychain()?;
+
     let mut command = Command::new("/usr/bin/security");
-    // Let macOS target the current user default keychain instead of assuming login.keychain-db.
-    command.args(["add-trusted-cert", "-r", "trustRoot"]);
+    command.args(["add-trusted-cert", "-r", "trustRoot", "-k"]);
+    command.arg(&keychain_path);
 
     let output = command.arg(&temp_path).output().map_err(|e| {
         format!(
             "Failed to launch macOS security tool to trust the local CA certificate: {e}"
@@ -251,12 +257,118 @@
             "Failed to add the local CodeNomad CA certificate to the macOS trust settings: {detail}"
         ));
     }
 
+    if !macos_cert_is_trusted(cert_der)? {
+        return Err(format!(
+            "Added the local CodeNomad CA certificate to {} but could not verify that macOS trusts it",
+            keychain_path.display()
+        ));
+    }
+
     write_trusted_marker(cert_der)?;
     Ok(())
 }
 
+#[cfg(target_os = "macos")]
+fn resolve_macos_user_keychain() -> Result<PathBuf, String> {
+    let output = std::process::Command::new("/usr/bin/security")
+        .args(["default-keychain", "-d", "user"])
+        .output()
+        .map_err(|e| format!("Failed to resolve macOS default user keychain: {e}"))?;
+
+    if output.status.success() {
+        let stdout = String::from_utf8_lossy(&output.stdout);
+        let trimmed = stdout.trim().trim_matches('"');
+        if !trimmed.is_empty() {
+            return Ok(PathBuf::from(trimmed));
+        }
+    }
+
+    let home = dirs::home_dir().or_else(|| env::var("HOME").ok().map(PathBuf::from));
+    let home = home.ok_or_else(|| "Cannot determine home directory for macOS keychain lookup".to_string())?;
+    Ok(home.join("Library/Keychains/login.keychain-db"))
+}
+
+#[cfg(target_os = "macos")]
+fn macos_cert_is_trusted(cert_der: &[u8]) -> Result<bool, String> {
+    use std::process::Command;
+
+    let temp_path = env::temp_dir().join(format!(
+        "codenomad-server-ca-verify-{}.cer",
+        trusted_marker_file_suffix(cert_der)
+    ));
+    fs::write(&temp_path, cert_der)
+        .map_err(|e| format!("Failed to write temporary certificate {}: {e}", temp_path.display()))?;
+
+    let keychain_path = resolve_macos_user_keychain()?;
+    let fingerprint = macos_cert_sha256(&temp_path)?;
+    let find_output = Command::new("/usr/bin/security")
+        .args(["find-certificate", "-a", "-Z", "-c", "CodeNomad Local CA"])
+        .arg(&keychain_path)
+        .output()
+        .map_err(|e| format!("Failed to query macOS keychain certificates: {e}"))?;
+
+    if !find_output.status.success() {
+        let _ = fs::remove_file(&temp_path);
+        let stderr = String::from_utf8_lossy(&find_output.stderr).trim().to_string();
+        let detail = if stderr.is_empty() {
+            format!("security exited with status {}", find_output.status)
+        } else {
+            stderr
+        };
+        return Err(format!(
+            "Failed to inspect the macOS keychain for the local CodeNomad CA certificate: {detail}"
+        ));
+    }
+
+    let stdout = String::from_utf8_lossy(&find_output.stdout);
+    if !stdout.to_ascii_uppercase().contains(&fingerprint) {
+        let _ = fs::remove_file(&temp_path);
+        return Ok(false);
+    }
+
+    let verify_output = Command::new("/usr/bin/security")
+        .args(["verify-cert", "-q", "-L", "-l", "-p", "basic", "-c"])
+        .arg(&temp_path)
+        .args(["-k"])
+        .arg(&keychain_path)
+        .output()
+        .map_err(|e| format!("Failed to verify macOS trust for the local CodeNomad CA certificate: {e}"))?;
+
+    let _ = fs::remove_file(&temp_path);
+    Ok(verify_output.status.success())
+}
+
+#[cfg(target_os = "macos")]
+fn macos_cert_sha256(cert_path: &Path) -> Result<String, String> {
+    let output = std::process::Command::new("/usr/bin/shasum")
+        .args(["-a", "256"])
+        .arg(cert_path)
+        .output()
+        .map_err(|e| format!("Failed to compute SHA-256 for {}: {e}", cert_path.display()))?;
+
+    if !output.status.success() {
+        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
+        let detail = if stderr.is_empty() {
+            format!("shasum exited with status {}", output.status)
+        } else {
+            stderr
+        };
+        return Err(format!(
+            "Failed to compute SHA-256 for {}: {detail}",
+            cert_path.display()
+        ));
+    }
+
+    let stdout = String::from_utf8_lossy(&output.stdout);
+    let hash = stdout
+        .split_whitespace()
+        .next()
+        .ok_or_else(|| format!("Failed to parse SHA-256 output for {}", cert_path.display()))?;
+    Ok(hash.to_ascii_uppercase())
+}
+
 #[cfg(all(not(windows), not(target_os = "macos")))]
 pub fn trust_cert_in_store(_cert_der: &[u8]) -> Result<(), String> {
     // Non-Windows platforms use native webview-specific handling instead of OS trust-store writes.
     Ok(())

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot bot commented Apr 19, 2026

I reviewed the latest PR state as a fresh gatekeeper pass for the current server-backed proxy design. I interpreted the request as: check the newest diff against dev for regressions, architecture problems, and stronger implementations, and only report substantive findings.

Findings

  1. Medium: the move from Tauri-side proxying into packages/server dropped the automated coverage for the proxy behavior that previously had dedicated tests.
    • The complex logic now lives in packages/server/src/server/remote-proxy.ts, including bootstrap-token activation, subpath rewriting, cookie name rewriting, redirect rewriting, and idle session cleanup.
    • I do not see any replacement tests for that module under packages/server/src/server/__tests__/ or elsewhere in the server package.
    • The old validation command still succeeds, but it is no longer exercising this behavior: cargo test remote_proxy -- --nocapture now reports running 0 tests because the proxy implementation is no longer in the Rust crate.
    • That is a verification regression relative to the earlier PR state, especially because the new server-side proxy is larger and now owns more correctness-sensitive behavior than before.
    • Better implementation: add focused server-side tests for at least the previously fragile cases and the new session lifecycle, especially:
      • base-path preservation and redirect rewriting
      • cookie prefix rewrite round-trip (Set-Cookie -> proxied Cookie)
      • bootstrap gating before activation
      • idle/dispose session behavior

What I Re-checked

  • The proxy now correctly lives in packages/server and Tauri is reusing the server-managed TLS assets.
  • The prior correctness issues I flagged earlier still look resolved:
    • no duplicated Tauri certificate lifecycle
    • no HTTPS-to-HTTP downgrade for proxied HTTPS remotes
    • subpath/base-path handling is still present in the new proxy implementation
  • I did not find a new behavioral bug strong enough to call out beyond the missing replacement coverage.

Validation Notes

  • I re-checked the current diff against dev.
  • I ran npm run bundle:server && cargo test remote_proxy -- --nocapture in packages/tauri-app.
    • The build completed successfully.
    • The remote_proxy Rust test invocation now runs 0 tests, which is exactly why I am treating the missing server-side replacement coverage as a gatekeeper issue.

Residual risk:

  • macOS still appears unverified in the PR thread, so I would still treat that platform as pending runtime confirmation even if the implementation direction now looks cleaner.

--
Yours,
CodeNomadBot

Verify the installed local CA against the active macOS user keychain before persisting trust state, and add focused server-side tests for remote proxy activation, path rewriting, cookie rewriting, and session cleanup.
@pascalandr
Copy link
Copy Markdown
Contributor Author

@shantur
I'm not sure if I understand your point 2. Right now Tauri always uses the local proxy, and skipTlsVerify only affects TLS inside that proxy. If you want skipTlsVerify to bypass the proxy and open the remote URL directly, I can rework that, but I want to make sure auth/cookies still behave correctly in Tauri.

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 19, 2026

Hey @pascalandr

If you want skipTlsVerify to bypass the proxy and open the remote URL directly, I can rework that, but I want to make sure auth/cookies still behave correctly in Tauri.

That's correct. If there is no TLS verification then the remote origin will be exactly what server is talking to so the auth/cookies should work without any re-writing. This is how electron implementation works for remote urls.

@pascalandr
Copy link
Copy Markdown
Contributor Author

@shantur How should we handle macOS here, since Tauri/Wry does not seem to expose a simple per-window TLS error hook like Electron does?

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 19, 2026

@shantur How should we handle macOS here, since Tauri/Wry does not seem to expose a simple per-window TLS error hook like Electron does?

Current macOS implementation in your PR is to install the certificate in OS store similar to Windows to trust the local CodeNomad cert. This works in macOS too. I have tested it

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 19, 2026

@pascalandr - Seems like this PR also reverts the shell rc changes that were merged

function wrapCommandForShell(command: string, shellPath: string): string {
  const shellName = path.basename(shellPath)

  if (shellName.includes("bash")) {
    return 'if [ -f ~/.bashrc ]; then source ~/.bashrc >/dev/null 2>&1; fi; ' + command
  }

  if (shellName.includes("zsh")) {
    return 'if [ -f ~/.zshrc ]; then source ~/.zshrc >/dev/null 2>&1; fi; ' + command
  }

  return command
}

@pascalandr
Copy link
Copy Markdown
Contributor Author

pascalandr commented Apr 19, 2026

@shantur How should we handle macOS here, since Tauri/Wry does not seem to expose a simple per-window TLS error hook like Electron does?

Current macOS implementation in your PR is to install the certificate in OS store similar to Windows to trust the local CodeNomad cert. This works in macOS too. I have tested it

@shantur sorry but I'm still confused about what your mean :
when Tauri skip TSL do you want us to :
1 bypass the local proxy and open the remote URL directly on the 3 platforms ?
or
2 keep the current local-proxy/local-certificate flow ?

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 19, 2026

@pascalandr

when Tauri skip TSL do you want us to :
1 bypass the local proxy and open the remote URL directly on the 3 platforms ?

Yes, when Tauri doesn't need to escape SSL verification, it should bypass the local proxy and open the remote URL on all 3 platforms.
I am on Discord for now if you would like to discuss

Open remote HTTPS URLs directly in Tauri when certificate verification remains enabled, and keep the local HTTPS proxy path only for tlsSkipVerify flows. Also restore bash/zsh interactive shell startup and verify actual trust-store state before skipping local certificate installation prompts on Windows.
@pascalandr
Copy link
Copy Markdown
Contributor Author

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot bot commented Apr 19, 2026

I reviewed the latest PR state as a fresh gatekeeper pass after the new fix(tauri): align remote TLS flow with verification settings update. I interpreted the request as: re-check the current branch tip against dev, verify whether the new follow-up actually matches the review-thread direction, and call out only substantive regressions or implementation mismatches.

Findings

  1. High: the latest branch still does the opposite of the explicitly agreed skipTlsVerify flow for Tauri.
    • In the thread, the agreed direction was: when skipTlsVerify is enabled, Tauri should bypass the local proxy and open the remote URL directly, matching Electron’s remote-window behavior.
    • But the UI still creates a remote proxy session exactly when skipTlsVerify is true for an HTTPS remote: packages/ui/src/components/folder-selection-view.tsx:336-341.
    • That means Tauri still routes the self-signed/skipTlsVerify case through the local proxy and local certificate flow, which is the inverse of the requested behavior.
    • Electron remains the reference implementation for the requested flow: packages/electron-app/electron/main/main.ts:427-459 opens the remote URL directly and only marks that origin as insecure when skipTlsVerify is true.
    • The Tauri side also confirms that the non-Linux trust/install path is still tied to proxy_session_id rather than a direct remote skip-verify flow: packages/tauri-app/src-tauri/src/main.rs:374-397.
    • So the PR has improved internals, but the latest branch tip still does not implement the specific behavior agreed in the PR thread.

What Changed Since My Previous Pass

  • The previous missing-coverage finding is resolved.
    • The branch now includes server-side proxy tests in packages/server/src/server/__tests__/remote-proxy.test.ts covering bootstrap gating, base-path/redirect rewriting, cookie rewriting, and session cleanup.
  • The shell-wrapper change that was mentioned in-thread is still present in the effective diff, but it is an additive change relative to dev, not a revert of the already-merged shell-rc behavior.

Validation Notes

  • I re-checked the current diff against dev.
  • I ran the new server-side proxy tests with:
    • npx tsx --test "src/server/__tests__/remote-proxy.test.ts"
    • Result: 4 tests passed.

Residual risk:

  • Because the branch still proxies the skipTlsVerify case in Tauri, the current implementation continues to rely on cookie/header rewriting and local certificate handling where the agreed direction was to preserve the real remote origin instead.

--
Yours,
CodeNomadBot

@pascalandr
Copy link
Copy Markdown
Contributor Author

@shantur
CodeNomadBot was also confused :)

@pascalandr
Copy link
Copy Markdown
Contributor Author

@CodeNomadBot

sorry for the confusing discussion, the true asked behavior is :
We have 2 TLS states
1 Remote SSL Verification Enabled : tlsSkipVerify=false,
2 Remote SSL Verification Skipped : tlsSkipVerify=true,

So when tlsSkipVerify=true - Verification is skipped then we need to go via Local Proxy

When tlsSkipVerify=false - We need to use the Remote URL directly.

with these updated information can you perform a review again ?

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot bot commented Apr 19, 2026

I reviewed the latest PR state again using the clarified TLS contract from this thread. I interpreted the intended behavior as:

  • tlsSkipVerify=true: go through the local proxy
  • tlsSkipVerify=false: open the remote URL directly

With that updated understanding, I do not have any gatekeeper findings on the current branch tip.

What I Re-checked

  • The Tauri UI now only creates a proxy session for HTTPS remotes when skipTlsVerify is enabled:
    • packages/ui/src/components/folder-selection-view.tsx:336-341
  • When no proxy session is created, Tauri falls back to the remote URL directly:
    • packages/tauri-app/src-tauri/src/main.rs:374-401
  • The local CA trust/install path on non-Linux is now correctly scoped to proxy-backed HTTPS windows only (proxy_session_id.is_some()), which matches the clarified behavior.
  • The previously missing replacement coverage for the moved proxy logic is now present in:
    • packages/server/src/server/__tests__/remote-proxy.test.ts

Validation Notes

  • I ran the new server-side proxy tests:
    • npx tsx --test "src/server/__tests__/remote-proxy.test.ts"
    • Result: 4 tests passed.
  • I also ran:
    • cargo check
    • Result: passed.

Residual risk:

  • Linux/macOS runtime behavior still appears to rely mainly on manual validation rather than automated end-to-end desktop coverage, but with the clarified TLS behavior I am not treating that as a blocking issue for this PR.

--
Yours,
CodeNomadBot

@shantur shantur merged commit 04fc28c into NeuralNomadsAI:dev Apr 19, 2026
11 of 12 checks passed
@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 19, 2026

Thanks for awesome work @pascalandr

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