Repository navigation
Description
Problem
The TLS bridge shows each client a leaf certificate that the Cortex CA signs. A client that does not trust that CA refuses the leaf, and its call fails. That makes Cortex visible to programs that have nothing to do with an agent.
This issue is the home for every client of that kind. Each client gets one supported result, and the result must be the same on every call:
- Bridged: the client trusts the CA, and plugins read its traffic.
- Passed through: Cortex tunnels the connection and does not forge a leaf.
- Unsupported: the call fails with a message that says what to do.
A client must never fail some of the time and work at other times.
Why a client does not trust the CA
A client trusts the CA only if its trust store contains it. Clients get their trusted CAs in one of three ways:
| Trust store | Examples | How Cortex can reach it |
|---|---|---|
| A CA file named by an environment variable | Node (NODE_EXTRA_CA_CERTS), Python requests (REQUESTS_CA_BUNDLE), curl (CURL_CA_BUNDLE), git (GIT_SSL_CAINFO), Go on Linux (SSL_CERT_FILE) |
Set the variable. It reaches only the processes that get the environment. |
| The OS store only | Go on macOS, Chromium's network service (every Electron app: IBM Bob, VS Code, Cursor, Slack) | Only the keychain, NSS or the Windows store. We do not want Cortex to depend on that. |
| A pinned or built-in store | Clients that pin a certificate, or ship their own roots | None. |
The variable names do not mean the same thing. NODE_EXTRA_CA_CERTS adds to Node's store, so it gets ca.crt. SSL_CERT_FILE, GIT_SSL_CAINFO, REQUESTS_CA_BUNDLE and CURL_CA_BUNDLE replace the store, so they get bundle.crt. If a client gets the wrong file, every TLS call that does not go through Cortex fails.
What Cortex does today
| Mechanism | What it covers | Issue |
|---|---|---|
tlsbridge.DefaultPassthroughHosts |
Tunnels GitHub, the Go module proxy, package registries and Docker Hub for all clients. No plugin reads that traffic. | #946 |
os-trust-only prediction (tlsbridge.ClientTrust) |
Does not forge for a Go executable on macOS when the OS does not trust the CA. | #1348 |
Program memory (program-refused) |
After a client refuses a leaf, tunnels that program, with a backoff. After 3 rejections, stops forging for it until Cortex restarts. | #912 |
agentop configure <agent> |
Writes the CA variables into the agent's settings. They reach only the processes that the agent starts. | |
agentop exec |
Runs one command with the proxy and CA variables set. |
The gaps
-
A tool that the user runs in their own shell gets no CA variable. The variables reach only the processes that an agent starts. A user's
go get,npm,pip,curlor Go CLI hasHTTPS_PROXYonly if the user exported it, and then it has no CA. -
Cortex has no supported answer for Go on macOS.
SSL_CERT_FILEhas no effect on darwin. Theos-trust-onlyprediction covers Go executables, but onlydarwinGoNotetells the user what to do. Decide the supported answer: passthrough, or a documented limit. -
Chromium's network service trusts only the OS store, and Cortex does not predict this. Seen with IBM Bob 2.2.3:
- Bob's network helper (
IBM Bob Helper.app,--utility-sub-type=network.mojom.NetworkService) sends an hourly CONNECT toapi.us-east.bob.ibm.com:443. Cortex forges a leaf and the helper refuses it. 45 failures from 30 Oct to 2 Nov. - A host passthrough is not possible. Bob's model calls go to the same host from the Plugin helper (
IBM Bob Helper (Plugin).app), which trusts the CA throughNODE_EXTRA_CA_CERTS. - The two helpers are in one app bundle. Only their
--utility-sub-typeargument tells them apart.
- Bob's network helper (
-
Cortex counts only 2 of the 6 alerts that mean "I refuse your certificate".
handshakeFailureReason(core/listener/forwardproxy/server.go) matchesbad certificate(42) andunknown certificate authority(48) asclient-rejected-ca. BoringSSL sendscertificate_unknown(46) by default, so Chromium's refusals becomehandshake-failed:handshake-failedis transient. It gives a 30-second skip, so Cortex forges a leaf again for a client that calls once an hour, and each call fails.- The program never reaches the stop after 3 rejections.
- The log line has no CA advice.
- The other certificate alerts, 43 to 45, have the same problem. No test covers this function.
-
OpenSSL 3 sends
unknown_caas a plaintext record during the TLS 1.3 handshake. Go cannot decrypt it and reportslocal error: tls: bad record MAC. We saw this from the podman VM (fwupdmgr). Cortex reports it ashandshake-failed.bad record MAChas other real causes, so it is not safe to treat it as a CA rejection without more evidence. -
The program memory is lost on each restart. After each Cortex restart, each program that refuses the CA fails up to 3 more calls before Cortex learns again.
Proposed solution
- Classify a CA rejection by the alert type, not by the error text: peer alerts 42 to 46 and 48, after a forged leaf, become
client-rejected-ca. Add a table test, with negative cases such ashandshake failure(gap 4). - Extend the
os-trust-onlyprediction to Chromium's network service. Detect it from the process arguments (kern.procargs2on macOS,/proc/<pid>/cmdlineon Linux), and use the existing check of OS trust. Leave Electron's Node processes bridged (gap 3). - Decide the supported answer for Go on macOS: passthrough by the prediction, or a documented limit (gap 2).
- Decide how a tool that the user runs in their own shell gets the CA, or document that
agentop execis the supported way (gap 1). Cover:- Go module fetch /
go get,gh, and other Go CLIs -
npm/pnpm(Node),pip/uv(Python requests) -
curlandwget, with and without proxy variables set - Shell scripts that inherit
HTTP_PROXY,HTTPS_PROXY,ALL_PROXY,NO_PROXY
- Go module fetch /
- Find a safe way to recognise OpenSSL's plaintext
unknown_ca, or document it (gap 5). - Decide if the program memory must survive a restart, keyed by the executable and the CA fingerprint (gap 6).
- Document the result for each client: bridged, passed through, or unsupported.
- Each unsupported combination fails with a message that says what to do, and never fails only some of the time.
Constraints
- No dependency on the macOS keychain or another OS store. A keychain step can stay as an option that the user selects.
- Never pass through a host that carries inference. That removes parsing, cost and token savings for every client of that host.
Related
- feature: git, gh and ssh keep working with Cortex running #946: git, gh and ssh (OS-store tools, host passthrough). Closed.
- helm (Go) fails TLS through Cortex proxy on macOS — CA not in System keychain #1348: Go on macOS (
os-trust-only). Closed. - TLS bridge auto-skip turns a CA trust error into 10-minute intermittent failures #912: backoff and program memory after a refusal. Closed.
- feature: IBM Bob support — detection, settings, parsed events, token and cost accounting #943: IBM Bob support. It covers the extension host, not the network helper.
Part of the Cortex v0.9.0 release (#962). Gates exit criterion 2: Cortex is invisible to everything else.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNew/ToDo
Activity
[-]feature: language toolchains and scripted HTTPS clients keep working[/-][+]feature: every client that cannot trust the Cortex CA keeps working — one supported result per client[/+]