LocalAI's outbound HTTP clients used Go's default redirect policy, which
follows up to 10 redirects. On a cross-host redirect Go forwards custom
request headers — including credential headers such as Anthropic's
x-api-key — to the redirect target (Go strips Authorization, Cookie and
WWW-Authenticate cross-host, but NOT arbitrary custom headers). An
attacker able to elicit a redirect from an upstream (a hijacked or
spoofed upstream, DNS trickery, or a malicious upstream_url) then
harvests the operator's provider API key.
This was first reported against the cloud-proxy / MITM PII path
(GHSA-3mj3-57v2-4636); the same class affects every other outbound
client. Rather than patch each call site, add pkg/httpclient as the one
sanctioned constructor for outbound HTTP and route everything through it.
pkg/httpclient:
- New(...) refuses redirects, TLS 1.2 floor, no body
deadline (streaming/SSE safe)
- NewWithTimeout(d) simple request/response calls
- WithFollowRedirects opt-in following that still strips credential
headers on any cross-host hop; different
scheme/host/port == different origin, guarding
the curl CVE-2022-27774 port-confusion class
- WithTransport(rt) keep a custom transport (IP-pin, HTTP/2, a
credential-injecting RoundTripper)
- HardenedTransport() base transport with the TLS floor + bounded setup
- Harden(c) apply the policy to a library-supplied *http.Client
- NoRedirect the CheckRedirect policy; wraps ErrRedirectBlocked
Lint: a forbidigo rule flags http.DefaultClient and http.Get/Post/
PostForm/Head, pointing at pkg/httpclient (.golangci.yml,
.agents/coding-style.md). forbidigo cannot match the &http.Client{}
composite literal without also flagging legitimate *http.Client type
references, so that form is enforced by review.
Migrates every non-test outbound call site across core/, pkg/, cmd/, and
the Go backend (backend/go/cloud-proxy). Credential-bearing and
internal-RPC clients refuse redirects; download / CDN / registry clients
use WithFollowRedirects so they keep working while stripping secrets
cross-host. The only credential-bearing client that follows redirects is
the gated-download path (pkg/downloader/uri.go), which strips the token
on the cross-host hop to the CDN. Hardening this closes, in passing:
- MCP remote-server bearer token leaking via a redirect (the
RoundTripper re-injected Authorization on every hop)
- agent multimedia/webhook clients leaking user-supplied auth headers
- cors_proxy following redirects, bypassing its SSRF IP-pin
- downloader's authorized read path leaking the token cross-host
Fixes: GHSA-3mj3-57v2-4636 (cloud-proxy leaks operator provider API key
(x-api-key) to attacker host on cross-host redirect)
Reported-by: tonghuaroot
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
Signed-off-by: Richard Palethorpe <io@richiejp.com>
4.0 KiB
Coding Style
The project has the following .editorconfig:
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.go]
indent_style = tab
[Makefile]
indent_style = tab
[*.proto]
indent_size = 2
[*.py]
indent_size = 4
[*.js]
indent_size = 2
[*.yaml]
indent_size = 2
[*.md]
trim_trailing_whitespace = false
- Use comments sparingly to explain why code does something, not what it does. Comments are there to add context that would be difficult to deduce from reading the code.
- Prefer modern Go e.g. use
anynotinterface{}
Logging
Use github.com/mudler/xlog for logging which has the same API as slog.
Go tests
All Go tests — including backend tests — must use Ginkgo (v2) with Gomega matchers, not the stdlib testing package with t.Run / t.Errorf. A test file should register a suite with RegisterFailHandler(Fail) in a TestXxx(t *testing.T) bootstrap and use Describe/Context/It blocks for the actual cases. Look at any existing *_test.go under core/ or pkg/ for a template.
Do not mix styles within a package. If you are extending tests in a package that already uses Ginkgo, keep using Ginkgo. If you find stdlib-style Go tests in the tree, treat them as tech debt to be migrated rather than as a pattern to follow.
This is enforced by golangci-lint via the forbidigo linter (see .golangci.yml); calls like t.Errorf / t.Fatalf / t.Run / t.Skip / t.Logf are flagged. Run make lint locally before submitting; the same check runs in CI (.github/workflows/lint.yml).
Outbound HTTP
All outbound HTTP must go through github.com/mudler/LocalAI/pkg/httpclient rather than the standard library's default client. Use httpclient.New(...) (no body deadline — safe for streaming/SSE) or httpclient.NewWithTimeout(d, ...) (simple request/response). Both refuse redirects by default and set a TLS 1.2 floor.
The reason is GHSA-3mj3-57v2-4636: the std default client follows redirects, and on a cross-host redirect Go forwards custom credential headers (e.g. Anthropic's x-api-key) to the redirect target, leaking the secret. httpclient fails closed instead.
- Need to follow redirects (download CDNs, registry blobs, GitHub asset URLs)? Pass
httpclient.WithFollowRedirects()— it still strips credential headers on any cross-host hop. - Have a custom transport (IP-pinned dialer, HTTP/2 tuning, a credential-injecting
RoundTripper)? Passhttpclient.WithTransport(rt), basing the transport onhttpclient.HardenedTransport()to keep the TLS floor. Handed a*http.Clientby a library?httpclient.Harden(c)applies the policy in place.
This is enforced by forbidigo (see .golangci.yml): http.DefaultClient and http.Get/Post/PostForm/Head are flagged. The &http.Client{} composite literal can't be matched precisely by forbidigo without also flagging legitimate *http.Client type references, so that form is caught by review — don't construct raw clients.
Documentation
The project documentation is located in docs/content. When adding new features or changing existing functionality, it is crucial to update the documentation to reflect these changes. This helps users understand how to use the new capabilities and ensures the documentation stays relevant.
- Feature Documentation: If you add a new feature (like a new backend or API endpoint), create a new markdown file in
docs/content/features/explaining what it is, how to configure it, and how to use it. - Configuration: If you modify configuration options, update the relevant sections in
docs/content/. - Examples: providing concrete examples (like YAML configuration blocks) is highly encouraged to help users get started quickly.
- Shortcodes: Use
{{% notice note %}},{{% notice tip %}}, or{{% notice warning %}}for callout boxes. Do not use{{% alert %}}— that shortcode does not exist in this project's Hugo theme and will break the docs build.