NEXO 7.9.29 — Override path hardening

Published 2026-04-26. Patch release over v7.9.28.

v7.9.28 introduced the optional LLM endpoint and auth provider override path that lets third-party orchestrators redirect Brain’s Anthropic SDK calls and CLI children to a custom proxy. An external code audit caught four issues immediately after release that the SDK-mock unit tests could not detect. v7.9.29 fixes all of them and adds a real end-to-end wire-test suite to keep them fixed.

1. Bearer now lands in Authorization: Bearer

The 7.9.28 cut passed the bearer to the Anthropic SDK as the api_key kwarg. The SDK serialises that into the X-Api-Key header (Anthropic-style auth). Any compatible proxy that only accepts the standard OAuth Authorization: Bearer header was rejecting every override-mode request with 401. v7.9.29 passes the bearer through auth_token instead, which the SDK serialises into the standard header.

2. Config dir resolved on every call

In 7.9.28 the Brain config directory was cached in a module-level _BRAIN_CONFIG_DIR evaluated at import time. LaunchAgent crons that exported NEXO_HOME from a wrapper script were already past Brain’s import by the time their wrapper kicked in, so they kept reading the pre-launch ~/.nexo/config/. v7.9.29 calls _resolve_brain_config_dir() on every _read_versioned_config invocation, so post-import NEXO_HOME changes are honoured immediately.

3. Idempotency-Key is caller-controllable

The proxy dedups on (token_id, idempotency_key) for 24 hours so transparent retries do not double-bill the user. In 7.9.28 the key was generated freshly for every call into _call_anthropic_raw; if the application layer (enforcement_classifier retrying after a ClassifierUnavailableError) reissued the same logical request, it arrived with a brand-new key and the proxy treated it as a separate billable request. v7.9.29 adds an idempotency_key kwarg to call_model_raw: callers that reissue a request reuse their own key and the proxy correctly deduplicates.

4. Strict bearer source in override mode

The most important fix and the only one tagged security in the changelog. In 7.9.28 resolve_auth_token fell back to ANTHROPIC_API_KEY (and the legacy filesystem files) whenever auth_provider.json was missing or its helper failed. If override mode was active, that value was the operator’s real sk-ant-... Anthropic key — and Brain would send it as the bearer to the custom proxy, leaking the real Anthropic credential to whoever ran the proxy.

v7.9.29 makes override mode strict about its bearer source: if auth_provider.json is missing or fails, resolve_auth_token returns an empty string and call_model_raw raises ClassifierUnavailableError instead of authenticating with the operator’s real key. The CLI-spawn helper in agent_runner.py mirrors the same fail-closed contract: when no proxy bearer can be resolved, neither ANTHROPIC_BASE_URL nor ANTHROPIC_API_KEY is injected, so a child cron does not silently combine the proxy URL with a stale operator key inherited from the parent process.

Belt-and-braces inside the SDK call: the Anthropic SDK __init__ resolves api_key from os.environ["ANTHROPIC_API_KEY"] whenever the kwarg is None, and would then send both X-Api-Key (from the env-resolved key) and Authorization: Bearer (from auth_token) on every request. v7.9.29 pops ANTHROPIC_API_KEY around the constructor call and restores it afterwards, so override-mode requests never carry the operator’s real key alongside the proxy bearer.

End-to-end wire tests

The 7.9.28 unit tests passed because they replaced the entire anthropic module with a fake class — the wire never left the host, so a wrong header on the wire could not be caught. v7.9.29 adds tests/test_call_model_raw_overrides_e2e.py, a suite that stands up a local BaseHTTPRequestHandler on 127.0.0.1:auto, points the override files at it, and lets the real Anthropic SDK send a real HTTP POST. The captured request is asserted against directly: Authorization: Bearer <helper-token> present, X-Api-Key absent, body model is the wire alias, Idempotency-Key matches the caller-supplied value, no sk-ant- value anywhere in the request, missing auth_provider.json aborts before any HTTP traffic, post-import NEXO_HOME changes are honoured, and the bare-mode CLI spawn is covered.

Verification

192-test wider regression sweep across call_model_raw, agent_runner, resonance, semantic-router, semantic-reasoner, cortex, core-prompts, auto-update-relocate-resonance, run-automation-prompt, and bare-mode tests stays green. The eight new wire tests run against the real SDK and pass against a local capture server.

Full changelog entry →