Summary
In crates/deno_facade/cert_provider.rs, the "mozilla" match arm assigns to root_cert_store, overwriting it, while the "system" arm correctly appends. As a result, the documented value DENO_TLS_CA_STORE="system,mozilla" silently discards every cert that was just loaded from the system trust store — leaving only the embedded webpki bundle.
This is order-dependent ("mozilla,system" works, "system,mozilla" does not), and the _ match arm even advertises "system,mozilla" as the allowed format in its error message — so the value most users will copy from the error string is the broken one.
Repro
Corporate proxy (in our case Cato Networks) doing TLS MITM. Steps:
- Set
DENO_TLS_CA_STORE=system,mozilla in supabase/functions/.env
docker exec into the running supabase_edge_runtime_* container, copy the corporate CA into /usr/local/share/ca-certificates/, run update-ca-certificates. The bundle at /etc/ssl/certs/ca-certificates.crt now contains the corporate CA.
- Restart the container so the runtime re-runs
get_root_cert_store_provider().
- From inside the container,
openssl s_client -connect api.openai.com:443 -showcerts successfully verifies the chain via the system store (proving the cert is correctly installed).
- A Deno fetch to the same host from a function still fails with:
TypeError: error sending request for url (https://api.openai.com/...): client error (Connect): invalid peer certificate: UnknownIssuer
- Change the env var to
DENO_TLS_CA_STORE=system (or mozilla,system) and re-create the container — fetch succeeds.
Root cause
cert_provider.rs:
for store in ca_stores.iter() {
match store.as_str() {
"mozilla" => {
root_cert_store = deno_tls::create_default_root_cert_store(); // ← REPLACES the store
}
"system" => {
let roots = load_native_certs().expect("could not load platform certs");
for root in roots {
root_cert_store.add((&*root.0).into())...; // ← APPENDS
}
}
_ => bail!("... (allowed: \"system,mozilla\")"), // ← suggests the broken value
}
}
The comment in the file links to denoland/deno@v1.37.0 cli/args/mod.rs#L467 as the reference implementation. Upstream Deno is append-only on both arms:
"mozilla" => {
root_cert_store.add_trust_anchors( // ← appends, not assigns
webpki_roots::TLS_SERVER_ROOTS.iter().map(...)
);
}
"system" => {
let roots = load_native_certs().expect(...);
for root in roots {
root_cert_store.add(...)...;
}
}
So the regression was introduced when porting to use deno_tls::create_default_root_cert_store() — that helper builds a fresh store, but the surrounding loop assumed append semantics like upstream.
Suggested fix
Either:
(a) Extend rather than assign in the mozilla arm:
"mozilla" => {
let mozilla_store = deno_tls::create_default_root_cert_store();
root_cert_store.roots.extend(mozilla_store.roots);
}
(b) Mirror upstream by calling add_trust_anchors directly with webpki_roots (would need to depend on webpki-roots directly rather than going through deno_tls's helper).
Either way, order-independence should be a test: "system,mozilla" and "mozilla,system" must produce the same effective trust set.
Affected version
Reproduced on supabase-edge-runtime-1.73.13 (also visually confirmed present on main at the time of this report). Supabase CLI v2.95.4 on Windows 11 + Docker Desktop.
Summary
In
crates/deno_facade/cert_provider.rs, the"mozilla"match arm assigns toroot_cert_store, overwriting it, while the"system"arm correctly appends. As a result, the documented valueDENO_TLS_CA_STORE="system,mozilla"silently discards every cert that was just loaded from the system trust store — leaving only the embedded webpki bundle.This is order-dependent (
"mozilla,system"works,"system,mozilla"does not), and the_match arm even advertises"system,mozilla"as the allowed format in its error message — so the value most users will copy from the error string is the broken one.Repro
Corporate proxy (in our case Cato Networks) doing TLS MITM. Steps:
DENO_TLS_CA_STORE=system,mozillainsupabase/functions/.envdocker execinto the runningsupabase_edge_runtime_*container, copy the corporate CA into/usr/local/share/ca-certificates/, runupdate-ca-certificates. The bundle at/etc/ssl/certs/ca-certificates.crtnow contains the corporate CA.get_root_cert_store_provider().openssl s_client -connect api.openai.com:443 -showcertssuccessfully verifies the chain via the system store (proving the cert is correctly installed).DENO_TLS_CA_STORE=system(ormozilla,system) and re-create the container — fetch succeeds.Root cause
cert_provider.rs:
The comment in the file links to denoland/deno@v1.37.0 cli/args/mod.rs#L467 as the reference implementation. Upstream Deno is append-only on both arms:
So the regression was introduced when porting to use
deno_tls::create_default_root_cert_store()— that helper builds a fresh store, but the surrounding loop assumed append semantics like upstream.Suggested fix
Either:
(a) Extend rather than assign in the mozilla arm:
(b) Mirror upstream by calling
add_trust_anchorsdirectly withwebpki_roots(would need to depend onwebpki-rootsdirectly rather than going throughdeno_tls's helper).Either way, order-independence should be a test:
"system,mozilla"and"mozilla,system"must produce the same effective trust set.Affected version
Reproduced on
supabase-edge-runtime-1.73.13(also visually confirmed present onmainat the time of this report). Supabase CLI v2.95.4 on Windows 11 + Docker Desktop.