TLS capture
When tyrd --tls-capture is enabled, outbound TLS handshakes get tagged with the Server Name Indication (SNI) hostname. This is how Tyr distinguishes api.openai.com from random egress.
What it does
flowchart LR Proc["process"] --> SSL["SSL_write()"] SSL --> Kernel["kernel"] SSL -. "uprobe in tyrd" .-> Capture["extract ClientHello<br/>read SNI<br/>emit TLS event<br/>{ sni = 'api.openai.com' }"]The SNI is visible because it’s sent in the clear during the TLS handshake. No decryption is performed. No certificate is MITM’d. No secrets are captured.
Supported TLS stacks
| Library | Hook | Status |
|---|---|---|
| OpenSSL | uprobe on SSL_write | ✅ |
| BoringSSL | uprobe on SSL_write | ✅ |
| rustls | kprobe on socket layer + user-mode helpers | ✅ |
Go stdlib crypto/tls | Go’s TLS is static, not a shared library — hooked via socket-layer inspection | Partial |
| NSS (Firefox) | uprobe on PR_Write | Planned |
The vast majority of AI-agent TLS traffic goes through OpenSSL or rustls (Python requests, Node https, Rust reqwest, system curl) — so coverage is high in practice.
What you get
A TLS event for every outbound HTTPS handshake:
{ "kind": "TLS", "resource": "api.openai.com", "verdict": "ALLOW", "metadata": { "sni": "api.openai.com", "remote_ip": "104.18.6.192", "remote_port": "443", "provider": "openai" }}Policy implications
You can now write rules like:
- name: only-approved-llm-providers action: tls_connect sni_pattern: "*.openai.com|*.anthropic.com|api.mistral.ai" verdict: allow severity: low- name: block-unknown-llm-destinations action: tls_connect verdict: deny severity: highThe second rule, being a catch-all, fires if SNI doesn’t match the first.
Enforcement vs observation
For now, combine TLS SNI rules with network-layer CIDR rules for true enforcement:
- name: allow-openai-ips action: net_connect cidr_allow: [ "104.16.0.0/13", "104.18.0.0/15" ] sni_pattern: "*.openai.com" verdict: allow- name: deny-other-external action: net_connect cidr_deny: [ "0.0.0.0/0" ] verdict: denyCIDR deny is in-kernel, so connect() returns ECONNREFUSED.
Privacy
- Only the SNI hostname is captured, not the request body.
- Response bodies are never touched.
- If you need stricter privacy (no hostname logging), set
--tls-capture=falseand rely on CIDR rules only.
Known limitations
- TLS 1.3 Encrypted ClientHello (ECH) — when ECH is widely deployed, SNI will no longer be visible on the wire. Tyr will need userspace hooks in the TLS library directly (the uprobe path already works for OpenSSL today).
- QUIC / HTTP/3 — covered via UDP socket hooks; same SNI visibility in the QUIC handshake.
- Self-hosted LLMs — obviously, no SNI tagging helps if your agent is hitting an internal IP. Use
net_connectCIDR rules.
→ Next: Writing policies · AI detection