8000 Verify TLS peers by default by neilberkman · Pull Request #144 · dominicletz/websockex · GitHub
[go: up one dir, main page]

Skip to content

Verify TLS peers by default - #144

Merged
dominicletz merged 3 commits into
dominicletz:masterfrom
neilberkman:verify-tls-by-default
Oct 22, 2026
Merged

dominicletz merged 3 commits into
dominicletz:masterfrom
neilberkman:verify-tls-by-default

Conversation

@neilberkman
@neilberkman neilberkman commented Oct 22, 2026 •
Copy link
Copy Markdown
Contributor

Fixes #1.

What changes

wss:// connections now verify the server certificate and hostname by default. WebSockex.Conn defaults insecure to false, and ssl_connection_options/1 covers every case in this order:

  1. ssl_options given: unchanged, the caller controls the TLS options.
  2. insecure: true: unchanged, verify: :verify_none. This is the explicit opt-out.
  3. cacerts given: verify: :verify_peer with those certificates, plus the :https hostname match function so wildcard certificates match.
  4. Otherwise: verify: :verify_peer with the OS trust store from :public_key.cacerts_get/0 (OTP 25+), falling back to :castore when it is present. When neither is available, open_socket/1 returns {:error, %WebSockex.ConnError{}} whose reason explains the options instead of raising.

:castore is added as an optional dependency. The :insecure, :cacerts, and :ssl_options docs are rewritten, the README gets a short TLS section, and the changelog gets a breaking-change entry.

Why now

The current default was set in 2017 when getting Erlang ssl verification right was hard (see the commit history around #1). Since then OTP 25 added :public_key.cacerts_get/0, OTP 26 made verify_peer the ssl client default, and other Elixir HTTP and WebSocket clients verify by default. The library default is the remaining gap, and it has consequences downstream: req_llm shipped GHSA-6vww-5m28-f26v (CVSS 7.4) because it used WebSockex with the defaults, sending API keys and OAuth tokens over unverified TLS.

Compatibility

This is a breaking default and suits a 0.6.0 release. Existing callers that pass insecure: true are unaffected. Callers on OTP 25+ get verification against the OS trust store with no change. Callers on older OTP without :castore and without cacerts or ssl_options get a connection error that names the fix. Callers who already pass insecure: false with cacerts keep working and gain hostname wildcard matching.

Two preparatory commits

  • Regenerate test certificates with a subjectAltName. OTP 29 rejects a certificate whose only name is the CN (hostname_check_failed, missing_subject_altnames), so the three TLS tests that verify the peer already fail on master under current OTP. The server certificate now carries DNS:localhost; generate_certs.sh is updated accordingly and subjects are unchanged.
  • Pin size variables in bitstring matches. Elixir 1.19+ warns on bytes-size(len) with len bound outside the match, and mix lint runs with --warnings-as-errors, so CI on elixir-version: latest fails without this. This duplicates the open Pin operators added to matching inside the frame module #142 and Pin variables used in match (for elixir 1.20) #143; if either lands first I will drop this commit.

Tests

New in conn_test.exs: the default rejects the test CA with unknown_ca; insecure: false alone takes the default trust-store path; supplied cacerts with a host of 127.0.0.1 fails the hostname check with bad_certificate. Existing tests that relied on the insecure default now pass cacerts: WebSockex.TestServer.cacerts().

Verification

On Elixir 1.20.4 / OTP 29: mix lint (compile with warnings as errors, format check, credo, dialyzer) passes and mix test reports 216 passed. The OTP < 25 and :castore fallback paths were reviewed but not run.

🤖 Generated with Claude Code

OTP 29 no longer accepts a certificate whose only name is the CN, so the
three TLS tests that verify the peer fail on master with
hostname_check_failed / missing_subject_altnames. The server certificate
now carries DNS:localhost.
Elixir 1.19 warns when a variable bound outside a match is used in
size/1 without a pin, and mix lint runs with --warnings-as-errors.
wss:// connections now verify the server certificate and hostname
unless `insecure: true` is passed. The trust store comes from
:public_key.cacerts_get/0 on OTP 25+, or :castore when present;
`cacerts:` and `ssl_options:` still override it.

Fixes dominicletz#1
@neilberkman
Copy link
Copy Markdown
Contributor Author

CI for this exact commit (b8f8604) on the latest OTP/Elixir, run on my fork since upstream Actions wait for approval: https://github.com/neilberkman/websockex/actions/runs/39855740601 (Lint and test: success).

@dominicletz
dominicletz merged commit d0ee8ce into dominicletz:master Oct 22, 2026
1 check passed
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.

SSL peers aren't verfied

2 participants

0