Summary
Discovered during a post-merge sanity pass on main after #35/#37/#40/#41/#42 landed (not a regression from any of those — confirmed via git log -p that this line has never been present since the file was first staged in #3).
build_pool in src/client.rs builds a deadpool_postgres::Config (cfg) and, when needs_tls(params) is true, wires in a TLS-capable connector (MakeRustlsConnect via build_tls_connector) — but it never calls cfg.ssl_mode(...). deadpool_postgres::Config::ssl_mode is Option<tokio_postgres::config::SslMode>, and when left None, the underlying tokio_postgres::Config this deadpool config produces defaults its own ssl_mode to SslMode::Prefer (confirmed in tokio-postgres-0.7.18/src/config.rs:255).
SslMode::Prefer means: attempt TLS, but silently fall back to plaintext if the server doesn't offer it. So ssl_mode=require/verify-ca/verify-full currently:
Reproduction
Confirmed live against a local PostgreSQL 16 instance with ssl = off:
cargo build
echo '{"jsonrpc":"2.0","id":1,"method":"test_connection","params":{"params":{"host":"127.0.0.1","port":54320,"username":"postgres","password":"password","database":"testdb","ssl_mode":"require"}}}' \
| ./target/debug/postgresql-plugin
Expected: connection should fail (require means TLS is mandatory; the server has no SSL to offer).
Actual: {"id":1,"jsonrpc":"2.0","result":null} — succeeds over plaintext, silently.
This defeats the entire purpose of ssl_mode=require: a user who explicitly opts into "TLS or nothing" gets an unencrypted connection with no error or warning if the server can't/won't negotiate TLS — a security-relevant gap, not just a correctness one.
Fix
In build_pool (src/client.rs), set cfg.ssl_mode(...) based on params.ssl_mode, mapping this plugin's string values to tokio_postgres::config::SslMode:
"disable" → SslMode::Disable
"allow" / unset/other → SslMode::Prefer (current default behavior, so this stays correct for those modes)
"prefer" → SslMode::Prefer
"require" / "verify-ca" / "verify-full" → SslMode::Require (chain/hostname enforcement is handled separately, at the rustls-connector layer, exactly as it is now — only the protocol-level "was TLS actually negotiated" guarantee is missing)
This mirrors the builtin driver's own mapping in build_postgres_configurations (src-tauri/src/pool_manager.rs): disable→Disable, allow|prefer→Prefer, require|verify-ca|verify-full→Require.
Test plan suggestion
Add a live-DB test (tests/live_db.rs) against a non-SSL Postgres instance (matches this repo's existing demo-db/CI fixture, which run without SSL) asserting that test_connection with ssl_mode=require returns an error, not success — this is exactly the kind of behavior a live handshake is needed to prove; the existing unit tests in client_tests.rs only cover connector construction, not negotiation.
Summary
Discovered during a post-merge sanity pass on
mainafter #35/#37/#40/#41/#42 landed (not a regression from any of those — confirmed viagit log -pthat this line has never been present since the file was first staged in #3).build_poolinsrc/client.rsbuilds adeadpool_postgres::Config(cfg) and, whenneeds_tls(params)is true, wires in a TLS-capable connector (MakeRustlsConnectviabuild_tls_connector) — but it never callscfg.ssl_mode(...).deadpool_postgres::Config::ssl_modeisOption<tokio_postgres::config::SslMode>, and when leftNone, the underlyingtokio_postgres::Configthis deadpool config produces defaults its ownssl_modetoSslMode::Prefer(confirmed intokio-postgres-0.7.18/src/config.rs:255).SslMode::Prefermeans: attempt TLS, but silently fall back to plaintext if the server doesn't offer it. Sossl_mode=require/verify-ca/verify-fullcurrently:ClientConfig/verifier (this part is correct, and is exactly what fix: support client certificate authentication (mTLS) #35/fix: fold TLS params into pool cache key #37/fix: verify-ca incorrectly enforces hostname verification #40 fixed and tested).tokio_postgres's connection negotiation to require TLS at the protocol level.Reproduction
Confirmed live against a local PostgreSQL 16 instance with
ssl = off:Expected: connection should fail (
requiremeans TLS is mandatory; the server has no SSL to offer).Actual:
{"id":1,"jsonrpc":"2.0","result":null}— succeeds over plaintext, silently.This defeats the entire purpose of
ssl_mode=require: a user who explicitly opts into "TLS or nothing" gets an unencrypted connection with no error or warning if the server can't/won't negotiate TLS — a security-relevant gap, not just a correctness one.Fix
In
build_pool(src/client.rs), setcfg.ssl_mode(...)based onparams.ssl_mode, mapping this plugin's string values totokio_postgres::config::SslMode:"disable"→SslMode::Disable"allow"/ unset/other →SslMode::Prefer(current default behavior, so this stays correct for those modes)"prefer"→SslMode::Prefer"require"/"verify-ca"/"verify-full"→SslMode::Require(chain/hostname enforcement is handled separately, at the rustls-connector layer, exactly as it is now — only the protocol-level "was TLS actually negotiated" guarantee is missing)This mirrors the builtin driver's own mapping in
build_postgres_configurations(src-tauri/src/pool_manager.rs):disable→Disable,allow|prefer→Prefer,require|verify-ca|verify-full→Require.Test plan suggestion
Add a live-DB test (
tests/live_db.rs) against a non-SSL Postgres instance (matches this repo's existingdemo-db/CI fixture, which run without SSL) asserting thattest_connectionwithssl_mode=requirereturns an error, not success — this is exactly the kind of behavior a live handshake is needed to prove; the existing unit tests inclient_tests.rsonly cover connector construction, not negotiation.