diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index cb93c0ebf..cbd3fb776 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -617,6 +617,25 @@ export namespace Config { .describe( "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.", ), + tls: z + .object({ + rejectUnauthorized: z + .boolean() + .optional() + .describe("Set to false to accept self-signed certificates. Default is true."), + cert: z.string().optional().describe("Path to client certificate file (PEM format) for mTLS."), + key: z.string().optional().describe("Path to client private key file (PEM format) for mTLS."), + ca: z + .union([z.string(), z.array(z.string())]) + .optional() + .describe("Path(s) to CA certificate file(s) (PEM format) to trust."), + }) + .optional() + .describe("TLS configuration for secure connections, including mTLS and custom CA certificates."), + proxy: z + .string() + .optional() + .describe("Proxy URL for this provider (e.g., http://proxy.example.com:8080). Overrides environment variables."), }) .catchall(z.any()) .optional(), diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index b11ca9368..b3864f26c 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1,5 +1,7 @@ import z from "zod" import fuzzysort from "fuzzysort" +import os from "os" +import path from "path" import { Config } from "../config/config" import { mapValues, mergeDeep, sortBy } from "remeda" import { NoSuchModelError, type Provider as SDK } from "ai" @@ -799,9 +801,17 @@ export namespace Provider { if (existing) return existing const customFetch = options["fetch"] + const tlsConfig = options["tls"] as + | { + rejectUnauthorized?: boolean + cert?: string + key?: string + ca?: string | string[] + } + | undefined + const proxyUrl = options["proxy"] as string | undefined options["fetch"] = async (input: any, init?: BunFetchRequestInit) => { - // Preserve custom fetch if it exists, wrap it with timeout logic const fetchFn = customFetch ?? fetch const opts = init ?? {} @@ -815,10 +825,39 @@ export namespace Provider { opts.signal = combined } + let tls: BunFetchRequestInit["tls"] | undefined + if (tlsConfig) { + tls = {} + if (tlsConfig.rejectUnauthorized !== undefined) { + tls.rejectUnauthorized = tlsConfig.rejectUnauthorized + } + if (tlsConfig.cert) { + const certPath = tlsConfig.cert.startsWith("~/") + ? path.join(os.homedir(), tlsConfig.cert.slice(2)) + : tlsConfig.cert + tls.cert = Bun.file(certPath) + } + if (tlsConfig.key) { + const keyPath = tlsConfig.key.startsWith("~/") + ? path.join(os.homedir(), tlsConfig.key.slice(2)) + : tlsConfig.key + tls.key = Bun.file(keyPath) + } + if (tlsConfig.ca) { + const caFiles = Array.isArray(tlsConfig.ca) ? tlsConfig.ca : [tlsConfig.ca] + tls.ca = caFiles.map((p) => { + const caPath = p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p + return Bun.file(caPath) + }) + } + } + return fetchFn(input, { ...opts, // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682 timeout: false, + ...(tls && { tls }), + ...(proxyUrl && { proxy: proxyUrl }), }) } diff --git a/packages/web/src/content/docs/network.mdx b/packages/web/src/content/docs/network.mdx index 2fcfd9f65..825d328f9 100644 --- a/packages/web/src/content/docs/network.mdx +++ b/packages/web/src/content/docs/network.mdx @@ -50,8 +50,131 @@ For proxies requiring advanced authentication like NTLM or Kerberos, consider us If your enterprise uses custom CAs for HTTPS connections, configure OpenCode to trust them. +### System certificate store + +Use certificates from your operating system's trusted certificate store (macOS Keychain, Windows Certificate Store, or Linux system certificates): + +```bash +# Option 1: Set for all Bun executions +export BUN_OPTIONS="--use-system-ca" + +# Option 2: Set via NODE_OPTIONS +export NODE_OPTIONS="--use-system-ca" +``` + +This is ideal for enterprise environments where IT has pre-installed certificates in the system store. + +### Extra CA certificates + +To add additional CA certificates without replacing the default bundle: + ```bash export NODE_EXTRA_CA_CERTS=/path/to/ca-cert.pem ``` This works for both proxy connections and direct API access. + +--- + +## Per-provider proxy + +Override the global proxy settings for a specific provider: + +```json +{ + "provider": { + "anthropic": { + "options": { + "proxy": "http://proxy.example.com:8080" + } + } + } +} +``` + +This takes precedence over the `HTTPS_PROXY` and `HTTP_PROXY` environment variables for that provider. + +--- + +## Per-provider TLS configuration + +For more granular control over TLS settings, you can configure TLS options per provider in your `opencode.json`: + +### Self-signed certificates + +Accept self-signed certificates for a specific provider: + +```json +{ + "provider": { + "my-proxy": { + "api": "@ai-sdk/openai-compatible", + "options": { + "baseURL": "https://my-internal-proxy.example.com", + "tls": { + "rejectUnauthorized": false + } + } + } + } +} +``` + +:::caution +Disabling certificate validation (`rejectUnauthorized: false`) bypasses TLS security checks. Only use this for trusted internal services. +::: + +### Custom CA certificates + +Trust a custom CA certificate for a provider: + +```json +{ + "provider": { + "my-proxy": { + "options": { + "baseURL": "https://my-internal-proxy.example.com", + "tls": { + "ca": "/path/to/ca-cert.pem" + } + } + } + } +} +``` + +You can also specify multiple CA certificates: + +```json +{ + "tls": { + "ca": ["/path/to/ca1.pem", "/path/to/ca2.pem"] + } +} +``` + +--- + +## mTLS (Mutual TLS) + +For services requiring client certificate authentication, configure both the certificate and private key: + +```json +{ + "provider": { + "mtls-proxy": { + "api": "@ai-sdk/anthropic", + "options": { + "baseURL": "https://llm-proxy-mtls.internal.example.com", + "tls": { + "cert": "/path/to/client-cert.pem", + "key": "/path/to/client-key.pem", + "ca": "/path/to/ca-cert.pem" + } + } + } + } +} +``` + +This is useful for enterprise environments with mTLS proxies that provide access to LLM APIs.