I'd be happy to implement this, but as per the guidelines, I've created this issue first in order to discuss the idea before proceeding with the implementation.
Describe the problem
I would like to protect a Tauri application against HTTPS MITM attacks where the attacker has installed a trusted root certificate on the user's machine, for example through corporate TLS inspection, antivirus HTTPS interception, malware, or a manually installed local CA.
For normal WebView window.fetch() calls, the request uses the platform/WebView trust store. If the operating system trusts a malicious or inspecting root CA, the WebView will also trust certificates issued by that CA. JavaScript does not get access to the peer certificate chain or SPKI information, so the application cannot reject the connection itself.
The current workaround is to avoid native window.fetch() entirely and move all security-sensitive requests into custom Rust commands or a custom plugin. That works, but it means every application that needs this protection has to implement its own fetch replacement, request/response serialization, streaming support, header handling, redirects, upload/download handling, and TLS pin verification.
This is not meant to provide tamper-proof protection against a user who can patch the application binary. The goal is to protect the app's network traffic against network-level MITM attacks caused by locally trusted root certificates, assuming the application binary itself has not been modified.
Describe the solution you'd like
I would like Tauri to provide an official, fetch-compatible HTTP API that supports certificate/public-key pinning, ideally SPKI pinning.
Ideally this would be part of @tauri-apps/plugin-http, for example:
import { fetch } from "@tauri-apps/plugin-http";
const response = await fetch("https://api.example.com/v1/data");
The pin configuration could be declarative, for example in tauri.conf.json or plugin configuration:
{
"plugins": {
"http": {
"scope": ["https://api.example.com/**"],
"pinnedPublicKeys": {
"api.example.com": [
"sha256/base64-spki-pin-current",
"sha256/base64-spki-pin-backup"
]
}
}
}
}
However, the enforcement should happen in the Rust-side HTTP implementation, not in frontend JavaScript. The pin values themselves do not need to be secret, but bypassing the check should require modifying the native application/plugin code, not merely changing frontend code.
The important properties would be:
- Requests are made by the Rust-side HTTP client, not native WebView
window.fetch().
- The API remains as close as possible to standard
fetch().
- The plugin validates normal TLS first.
- The plugin additionally verifies the leaf certificate SPKI/public key hash against configured pins.
- Multiple pins per host are supported for safe key rotation.
- URL scope restrictions still apply, so the plugin cannot be abused as an open proxy.
- Pinning enforcement cannot be disabled from frontend JavaScript.
- The feature does not require applications to expose a production
disablePinning style option.
- The API works consistently across Windows, macOS, and Linux.
- Ideally, it supports common fetch features such as headers, methods, request bodies, response headers, redirects, binary responses, uploads, downloads, and streaming where possible.
This would allow applications to protect API requests against locally trusted MITM root certificates without each project having to roll its own security-sensitive HTTP bridge.
Alternatives considered
-
Use native window.fetch(): this does not solve the problem because JavaScript cannot inspect or reject the TLS certificate/SPKI when the WebView/platform trust store accepts it.
-
Use custom Tauri commands: this works, but every app has to build and maintain its own fetch replacement. It is easy to miss edge cases around headers, redirects, streaming, request bodies, binary responses, error behavior, and security restrictions.
-
Use @tauri-apps/plugin-http today: this gives a fetch-like API from the frontend and uses the Rust side, but I could not find documented support for certificate pinning or SPKI/public-key pinning.
-
Monkey-patch window.fetch: this can help prevent accidental direct calls, but it does not provide real TLS pinning unless the patched implementation delegates to a pinned Rust-side transport.
-
Use CSP to block direct connections: this is a useful guardrail, but it still requires a secure Rust-side replacement for the actual API calls.
-
App-layer response signing: this can protect response integrity, but it is not a full replacement for transport-level protection. It does not hide request/response contents from a MITM and does not protect all API interactions by default.
-
Put pins in frontend code: this is not sufficient because frontend JavaScript is easier to inspect or modify and still cannot control the TLS validation performed by native WebView fetch().
Additional context
This is especially relevant for desktop applications where the user's machine may have additional trusted root certificates installed. In those environments, normal HTTPS validation can be insufficient for applications that need stronger assurance that they are communicating with their real backend.
A common deployment setup is a backend behind nginx with automatically renewed Let's Encrypt certificates, for example using nginx-proxy and acme-companion. Because the certificate itself rotates, pinning the whole certificate is operationally fragile. SPKI/public-key pinning is a better fit because it can survive certificate renewal as long as the server keypair remains stable.
A safe rotation model would be to configure at least two pins per host:
current server key pin
backup server key pin
Then applications can ship support for the next key before the server rotates to it.
This feature would give Tauri applications a practical way to keep the developer ergonomics of fetch() while providing stronger protection against MITM attacks caused by locally trusted root certificates.
The expected security boundary would be:
Protected against:
- Network-level MITM
- Corporate TLS inspection
- Antivirus HTTPS interception
- Malicious or unwanted locally trusted root CAs
- Accidental frontend use of unpinned transport, if combined with CSP/scope restrictions
Not protected against:
- A local attacker patching the application binary
- Process injection or runtime hooking
- A malicious rebuild of the application with pinning removed
- A fully compromised endpoint
That limitation is acceptable and expected. The request is for a robust transport-level mitigation against MITM through trusted root certificates, not for a general anti-tamper system.
I'd be happy to implement this, but as per the guidelines, I've created this issue first in order to discuss the idea before proceeding with the implementation.
Describe the problem
I would like to protect a Tauri application against HTTPS MITM attacks where the attacker has installed a trusted root certificate on the user's machine, for example through corporate TLS inspection, antivirus HTTPS interception, malware, or a manually installed local CA.
For normal WebView
window.fetch()calls, the request uses the platform/WebView trust store. If the operating system trusts a malicious or inspecting root CA, the WebView will also trust certificates issued by that CA. JavaScript does not get access to the peer certificate chain or SPKI information, so the application cannot reject the connection itself.The current workaround is to avoid native
window.fetch()entirely and move all security-sensitive requests into custom Rust commands or a custom plugin. That works, but it means every application that needs this protection has to implement its own fetch replacement, request/response serialization, streaming support, header handling, redirects, upload/download handling, and TLS pin verification.This is not meant to provide tamper-proof protection against a user who can patch the application binary. The goal is to protect the app's network traffic against network-level MITM attacks caused by locally trusted root certificates, assuming the application binary itself has not been modified.
Describe the solution you'd like
I would like Tauri to provide an official, fetch-compatible HTTP API that supports certificate/public-key pinning, ideally SPKI pinning.
Ideally this would be part of
@tauri-apps/plugin-http, for example:The pin configuration could be declarative, for example in
tauri.conf.jsonor plugin configuration:{ "plugins": { "http": { "scope": ["https://api.example.com/**"], "pinnedPublicKeys": { "api.example.com": [ "sha256/base64-spki-pin-current", "sha256/base64-spki-pin-backup" ] } } } }However, the enforcement should happen in the Rust-side HTTP implementation, not in frontend JavaScript. The pin values themselves do not need to be secret, but bypassing the check should require modifying the native application/plugin code, not merely changing frontend code.
The important properties would be:
window.fetch().fetch().disablePinningstyle option.This would allow applications to protect API requests against locally trusted MITM root certificates without each project having to roll its own security-sensitive HTTP bridge.
Alternatives considered
Use native
window.fetch(): this does not solve the problem because JavaScript cannot inspect or reject the TLS certificate/SPKI when the WebView/platform trust store accepts it.Use custom Tauri commands: this works, but every app has to build and maintain its own fetch replacement. It is easy to miss edge cases around headers, redirects, streaming, request bodies, binary responses, error behavior, and security restrictions.
Use
@tauri-apps/plugin-httptoday: this gives a fetch-like API from the frontend and uses the Rust side, but I could not find documented support for certificate pinning or SPKI/public-key pinning.Monkey-patch
window.fetch: this can help prevent accidental direct calls, but it does not provide real TLS pinning unless the patched implementation delegates to a pinned Rust-side transport.Use CSP to block direct connections: this is a useful guardrail, but it still requires a secure Rust-side replacement for the actual API calls.
App-layer response signing: this can protect response integrity, but it is not a full replacement for transport-level protection. It does not hide request/response contents from a MITM and does not protect all API interactions by default.
Put pins in frontend code: this is not sufficient because frontend JavaScript is easier to inspect or modify and still cannot control the TLS validation performed by native WebView
fetch().Additional context
This is especially relevant for desktop applications where the user's machine may have additional trusted root certificates installed. In those environments, normal HTTPS validation can be insufficient for applications that need stronger assurance that they are communicating with their real backend.
A common deployment setup is a backend behind nginx with automatically renewed Let's Encrypt certificates, for example using
nginx-proxyandacme-companion. Because the certificate itself rotates, pinning the whole certificate is operationally fragile. SPKI/public-key pinning is a better fit because it can survive certificate renewal as long as the server keypair remains stable.A safe rotation model would be to configure at least two pins per host:
Then applications can ship support for the next key before the server rotates to it.
This feature would give Tauri applications a practical way to keep the developer ergonomics of
fetch()while providing stronger protection against MITM attacks caused by locally trusted root certificates.The expected security boundary would be:
That limitation is acceptable and expected. The request is for a robust transport-level mitigation against MITM through trusted root certificates, not for a general anti-tamper system.