{"record":{"id":"e4269747e89db29d","repo":"JuliusBrussee/caveman","slug":"upstream-proxy-q-must-be-env-off-or-a-proxy-url","errorCode":null,"errorMessage":"upstream_proxy %q must be \"env\", \"off\" or a proxy URL","messagePattern":"upstream_proxy %q must be \"env\", \"off\" or a proxy URL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/config/config.go","lineNumber":270,"sourceCode":"\t\treturn nil\n\t}\n\treturn fn\n}\n\nfunc parseUpstreamProxy(raw string) (func(*http.Request) (*url.URL, error), error) {\n\traw = strings.TrimSpace(raw)\n\tswitch strings.ToLower(raw) {\n\tcase \"\", \"env\":\n\t\t// ProxyFromEnvironment snapshots the proxy variables once per process\n\t\t// (sync.Once), so a test that t.Setenv's HTTPS_PROXY must build its own\n\t\t// httpproxy.Config selector instead — see ssrf_test.go.\n\t\treturn http.ProxyFromEnvironment, nil\n\tcase \"off\":\n\t\treturn nil, nil\n\t}\n\tu, err := url.Parse(raw)\n\tif err != nil || u.Host == \"\" {\n\t\treturn nil, fmt.Errorf(\"upstream_proxy %q must be \\\"env\\\", \\\"off\\\" or a proxy URL\", raw)\n\t}\n\tswitch u.Scheme {\n\tcase \"http\", \"https\", \"socks5\", \"socks5h\":\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"upstream_proxy %q: unsupported scheme %q\", raw, u.Scheme)\n\t}\n\t// Same selector semantics as env mode: localhost/loopback destinations (an\n\t// allowlisted Ollama) and NO_PROXY matches are dialed direct rather than\n\t// handed to a corporate proxy that cannot reach them.\n\tselector := (&httpproxy.Config{HTTPProxy: raw, HTTPSProxy: raw, NoProxy: env.String(\"NO_PROXY\", env.String(\"no_proxy\", \"\"))}).ProxyFunc()\n\treturn func(req *http.Request) (*url.URL, error) { return selector(req.URL) }, nil\n}\n\n// minAuthTokenBytes is the floor for the inbound shared secret. The token is the\n// only gate in front of every configured provider credential once the proxy is\n// reachable off-host, so a short one is not a weaker deployment, it is an open one.\nconst minAuthTokenBytes = 16\n","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/internal/config/config.go#L252-L288","documentation":"parseUpstreamProxy (proxy/internal/config/config.go:270) accepts exactly three kinds of upstream_proxy values: the literal \"env\" (use HTTPS_PROXY/NO_PROXY), \"off\" (direct, never proxied), or a full proxy URL with a host. Anything else — unparseable URL, missing host, empty string — is rejected with this error before any network traffic happens, so an operator cannot silently lose proxying.","triggerScenarios":"upstream_proxy in caveman.yaml or CAVE_UPSTREAM_PROXY set to a typo like 'htp://proxy:8080', a bare 'proxy:8080' with no scheme, an empty value, or whitespace-only text; any value besides env/off that url.Parse rejects or that lacks a Host.","commonSituations":"Writing 'proxy.corp.local:3128' without a scheme; YAML unquoting issues mangling the URL; setting the env var to empty in a shell profile; expecting 'system' or 'auto' to be valid keywords when only 'env' and 'off' exist.","solutions":["Use one of: upstream_proxy: env, upstream_proxy: off, or a full URL like http://proxy.corp.local:3128","Add the scheme to bare host:port values (http://, https://, socks5://, or socks5h://)","Quote the URL in YAML if it contains special characters (e.g. socks5h://user:pass@host:1080)","Unset empty CAVE_UPSTREAM_PROXY values; an empty string is not a valid setting"],"exampleFix":"// before (caveman.yaml)\nupstream_proxy: proxy.corp.local:3128\n// after\nupstream_proxy: http://proxy.corp.local:3128","handlingStrategy":"validation","validationCode":"func validUpstreamProxy(raw string) bool {\n    switch raw {\n    case \"\", \"env\", \"off\":\n        return raw != \"\" || true\n    }\n    u, err := url.Parse(raw)\n    if err != nil || u.Host == \"\" { return false }\n    switch u.Scheme {\n    case \"http\", \"https\", \"socks5\", \"socks5h\":\n        return true\n    }\n    return false\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only use the literal keywords env or off, or a fully-schemed URL","Always include a scheme (http://, https://, socks5://, socks5h://) on proxy addresses","Quote URLs with credentials/special chars in YAML","Unset (rather than empty-set) CAVE_UPSTREAM_PROXY when not using a proxy"],"tags":["config","proxy","url-validation","network"],"backgroundTag":"invalid-config-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}