Skip to content

Commit 14b64d4

Browse files
committed
feat: add per-provider TLS and proxy configuration
Add support for configuring TLS options and HTTP proxy per provider, enabling use of corporate proxies, self-signed certificates, and mTLS. New provider options: - tls.rejectUnauthorized: Accept self-signed certificates - tls.cert: Client certificate path for mTLS - tls.key: Client private key path for mTLS - tls.ca: Custom CA certificate path(s) - proxy: HTTP proxy URL (overrides HTTPS_PROXY env var) The TLS config is passed to Bun's native fetch using Bun.file() for cert/key/ca paths. The proxy option uses Bun's built-in proxy support. For system-wide certificate trust, users can set: - NODE_OPTIONS="--use-system-ca" or BUN_OPTIONS="--use-system-ca" - NODE_EXTRA_CA_CERTS=/path/to/ca.pem Fixes anomalyco#1694 - Users with corporate CAs can now either use the system trust store via --use-system-ca flag, or specify their CA cert path in the provider's tls.ca option. Documentation added to packages/web/src/content/docs/network.mdx with examples for proxy configuration, custom CAs, and mTLS setup.
1 parent 65e267e commit 14b64d4

3 files changed

Lines changed: 171 additions & 1 deletion

File tree

‎packages/opencode/src/config/config.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,25 @@ export namespace Config {
872872
.describe(
873873
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
874874
),
875+
tls: z
876+
.object({
877+
rejectUnauthorized: z
878+
.boolean()
879+
.optional()
880+
.describe("Set to false to accept self-signed certificates. Default is true."),
881+
cert: z.string().optional().describe("Path to client certificate file (PEM format) for mTLS."),
882+
key: z.string().optional().describe("Path to client private key file (PEM format) for mTLS."),
883+
ca: z
884+
.union([z.string(), z.array(z.string())])
885+
.optional()
886+
.describe("Path(s) to CA certificate file(s) (PEM format) to trust."),
887+
})
888+
.optional()
889+
.describe("TLS configuration for secure connections, including mTLS and custom CA certificates."),
890+
proxy: z
891+
.string()
892+
.optional()
893+
.describe("Proxy URL for this provider (e.g., http://proxy.example.com:8080). Overrides environment variables."),
875894
})
876895
.catchall(z.any())
877896
.optional(),

‎packages/opencode/src/provider/provider.ts‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -982,9 +982,17 @@ export namespace Provider {
982982
if (existing) return existing
983983

984984
const customFetch = options["fetch"]
985+
const tlsConfig = options["tls"] as
986+
| {
987+
rejectUnauthorized?: boolean
988+
cert?: string
989+
key?: string
990+
ca?: string | string[]
991+
}
992+
| undefined
993+
const proxyUrl = options["proxy"] as string | undefined
985994

986995
options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
987-
// Preserve custom fetch if it exists, wrap it with timeout logic
988996
const fetchFn = customFetch ?? fetch
989997
const opts = init ?? {}
990998

@@ -1016,10 +1024,30 @@ export namespace Provider {
10161024
}
10171025
}
10181026

1027+
let tls: BunFetchRequestInit["tls"] | undefined
1028+
if (tlsConfig) {
1029+
tls = {}
1030+
if (tlsConfig.rejectUnauthorized !== undefined) {
1031+
tls.rejectUnauthorized = tlsConfig.rejectUnauthorized
1032+
}
1033+
if (tlsConfig.cert) {
1034+
tls.cert = Bun.file(tlsConfig.cert)
1035+
}
1036+
if (tlsConfig.key) {
1037+
tls.key = Bun.file(tlsConfig.key)
1038+
}
1039+
if (tlsConfig.ca) {
1040+
const caFiles = Array.isArray(tlsConfig.ca) ? tlsConfig.ca : [tlsConfig.ca]
1041+
tls.ca = caFiles.map((path) => Bun.file(path))
1042+
}
1043+
}
1044+
10191045
return fetchFn(input, {
10201046
...opts,
10211047
// @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
10221048
timeout: false,
1049+
...(tls && { tls }),
1050+
...(proxyUrl && { proxy: proxyUrl }),
10231051
})
10241052
}
10251053

‎packages/web/src/content/docs/network.mdx‎

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,131 @@ For proxies requiring advanced authentication like NTLM or Kerberos, consider us
5050

5151
If your enterprise uses custom CAs for HTTPS connections, configure OpenCode to trust them.
5252

53+
### System certificate store
54+
55+
Use certificates from your operating system's trusted certificate store (macOS Keychain, Windows Certificate Store, or Linux system certificates):
56+
57+
```bash
58+
# Option 1: Set for all Bun executions
59+
export BUN_OPTIONS="--use-system-ca"
60+
61+
# Option 2: Set via NODE_OPTIONS
62+
export NODE_OPTIONS="--use-system-ca"
63+
```
64+
65+
This is ideal for enterprise environments where IT has pre-installed certificates in the system store.
66+
67+
### Extra CA certificates
68+
69+
To add additional CA certificates without replacing the default bundle:
70+
5371
```bash
5472
export NODE_EXTRA_CA_CERTS=/path/to/ca-cert.pem
5573
```
5674

5775
This works for both proxy connections and direct API access.
76+
77+
---
78+
79+
## Per-provider proxy
80+
81+
Override the global proxy settings for a specific provider:
82+
83+
```json
84+
{
85+
"provider": {
86+
"anthropic": {
87+
"options": {
88+
"proxy": "http://proxy.example.com:8080"
89+
}
90+
}
91+
}
92+
}
93+
```
94+
95+
This takes precedence over the `HTTPS_PROXY` and `HTTP_PROXY` environment variables for that provider.
96+
97+
---
98+
99+
## Per-provider TLS configuration
100+
101+
For more granular control over TLS settings, you can configure TLS options per provider in your `opencode.json`:
102+
103+
### Self-signed certificates
104+
105+
Accept self-signed certificates for a specific provider:
106+
107+
```json
108+
{
109+
"provider": {
110+
"my-proxy": {
111+
"api": "@ai-sdk/openai-compatible",
112+
"options": {
113+
"baseURL": "https://my-internal-proxy.example.com",
114+
"tls": {
115+
"rejectUnauthorized": false
116+
}
117+
}
118+
}
119+
}
120+
}
121+
```
122+
123+
:::caution
124+
Disabling certificate validation (`rejectUnauthorized: false`) bypasses TLS security checks. Only use this for trusted internal services.
125+
:::
126+
127+
### Custom CA certificates
128+
129+
Trust a custom CA certificate for a provider:
130+
131+
```json
132+
{
133+
"provider": {
134+
"my-proxy": {
135+
"options": {
136+
"baseURL": "https://my-internal-proxy.example.com",
137+
"tls": {
138+
"ca": "/path/to/ca-cert.pem"
139+
}
140+
}
141+
}
142+
}
143+
}
144+
```
145+
146+
You can also specify multiple CA certificates:
147+
148+
```json
149+
{
150+
"tls": {
151+
"ca": ["/path/to/ca1.pem", "/path/to/ca2.pem"]
152+
}
153+
}
154+
```
155+
156+
---
157+
158+
## mTLS (Mutual TLS)
159+
160+
For services requiring client certificate authentication, configure both the certificate and private key:
161+
162+
```json
163+
{
164+
"provider": {
165+
"mtls-proxy": {
166+
"api": "@ai-sdk/anthropic",
167+
"options": {
168+
"baseURL": "https://llm-proxy-mtls.internal.example.com",
169+
"tls": {
170+
"cert": "/path/to/client-cert.pem",
171+
"key": "/path/to/client-key.pem",
172+
"ca": "/path/to/ca-cert.pem"
173+
}
174+
}
175+
}
176+
}
177+
}
178+
```
179+
180+
This is useful for enterprise environments with mTLS proxies that provide access to LLM APIs.

0 commit comments

Comments
 (0)