Commit Graph

19 Commits

Author SHA1 Message Date
Julius Brussee ac32e98e58 Harden upstream proxy, SSRF guard, and CA bundle handling
Adds explicit upstream_proxy / ca_bundle configuration, an additive CA
bundle loader in shared/platform/cabundle, and tightens the SSRF guard
for proxied and self-hosted upstreams. Documents both in the technical
configuration and security pages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rs9RexUQkL3USsbdMVTpy8
2026-09-07 10:23:21 +02:00
Julius Brussee 3fe78fac20 Harden proxy stream and lifecycle reliability
Fix shared listener lifetime so a proxy keeps running across wrapped/native sessions and dead local routes are not exported. Remove the default upstream timeout, stop replaying ambiguous provider failures, preserve Content-Encoding on encoded requests, and flush streaming responses based on wire content-type instead of JSON-only flags. Adds regression tests covering listener lifetime, retry safety, and streaming/encoding edge cases.
2026-09-05 10:47:50 +02:00
Julius Brussee 695703c0d5 fix(pi): per-model originals and compat-mount routing for custom providers
- cache original base URLs per model, not per provider, so a same-provider
  model with its own endpoint stays direct (#973)
- proxy publishes its named compat mounts in the run-state file; the pi
  extension routes a custom provider through /w/pi/compat/<name> only when a
  mount with that name and a matching host is published (#952, #970)
- hostOf compares host:port so loopback relays on different ports differ

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2f7Lf7G9erA7YNn5SXPEj
2026-09-03 20:49:23 +02:00
Zsolt f73d98fc8e fix(proxy): send x-api-key on compat /v1/messages (#969)
* fix(proxy): send x-api-key on compat /v1/messages

A named compat mount mapped every credential to Authorization: Bearer.
The Anthropic client in Pi sends x-api-key to /v1/messages. OpenCode Go
rejects a Bearer header on that path with 401 "Missing API key.". Thus
the opencode-go anthropic-messages models failed through the proxy.

The named adapter now maps the credential by the wire protocol of the
path. A request to /compat/<name>/v1/messages gets x-api-key and a
default anthropic-version. Every other path keeps Bearer. The gateway
env fallback puts the key in the header that the adapter emitted, so
the upstream never gets two credentials.

New tests cover the header mapping at the adapter, the gateway fallback,
and the full standalone handler with the /w/pi prefix. A test also
shows that a user compat.opencode-go entry replaces the built-in
upstream on the adapter path. The docs describe the header rule.

Fixes #967
Fixes #968

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>

* docs(proxy): add the compat header rule to AGENTS.md

proxy/AGENTS.md carries the same auth bullet as proxy/CLAUDE.md. This
commit adds the same header rule to both files.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>

* fix(proxy): keep a real bearer on compat /v1/messages

Review of #969 found two risks. The Anthropic-path remap relabeled a
real inbound Bearer token as x-api-key on every named compat mount.
This broke the rule that an inbound credential keeps its auth scheme.
The remap now skips a real Bearer token. The gateway placeholder is not
a real credential, so the remap still moves it to x-api-key, and the
env fallback replaces it in that header.

The fallback test for x-api-key used a direct map lookup that depends
on canonical header keys. It now uses Header.Get. The openai and
openai_compatible fallback cases are separate, so each guard list
names only the headers that its adapter can emit.

New tests show that a real Bearer stays Bearer on /v1/messages at the
adapter, in the gateway fallback, and through the standalone handler.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>

---------

Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
Co-authored-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 20:29:36 +02:00
Julius Brussee d6c1cd39af fix(cli): launch direct when a foreign process owns the proxy port
A foreign listener on 127.0.0.1:8787 used to get the banner but still
receive the routed launch, which would send the operator's provider
keys to a process caveman does not own (#945). Treat it like a gate
mismatch and fall back to a direct launch.

Also: Claude Code refuses Remote Control behind any non-first-party
ANTHROPIC_BASE_URL, so `caveman claude remote-control` launches direct
and the limitation is documented (#947); caveman-compress reads the
first text block instead of content[0] so a leading tool_use block no
longer crashes after a paid call (closes #932's report); hook checksum
manifest regenerated after #794.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 15:25:28 +02:00
Julius Brussee c24123dd2b Merge remote-tracking branch 'origin/main'
# Conflicts:
#	README.md
#	docs/technical/agent-profile-registry.md
#	packages/cli/src/index.ts
#	packages/cli/tests/subscription-recovery.runtime.mjs
#	proxy/internal/gateway/chatgpt.go
#	proxy/internal/gateway/proxy.go
#	proxy/internal/gateway/retrieve_tool.go
#	proxy/internal/gateway/server.go
#	proxy/internal/gateway/subscription_live_zone_test.go
2026-09-02 15:19:57 +02:00
Julius Brussee 41819dcba0 feat(proxy): prove MCP recovery from the request tool list
A bare proxy serves whichever client connects, so a machine-level MCP
install marker can never bind to one request. Read the namespaced
caveman retrieve tool out of the request's own tool list instead, so
subscription and OAuth sessions on a standalone proxy reach the live
zone when their agent carries the tool (#908).

Cache-breakpoint planning and the provider-native cache optimizers now
default on in optimization modes; explicit off and unknown values still
fail closed, and record mode never runs them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 15:18:19 +02:00
Zsolt Gaspar 455f462407 docs(proxy): document built-in opencode-go compat mount
The proxy now registers /compat/opencode-go/ with a default upstream
and the OPENCODE_API_KEY credential variable. Add the mount and the
user override to the provider docs. Add the variable to the
credential list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
2026-09-01 21:49:47 +02:00
Julius Brussee be2f788161 fix(cli): guard Kilo managed routing 2026-08-30 06:07:35 +02:00
Julius Brussee ca51a04ed2 fix(cli): guard Kilo route overrides 2026-08-30 05:46:42 +02:00
Julius Brussee facc597029 fix(cli): guard Qwen route overrides 2026-08-30 05:29:20 +02:00
Julius Brussee 9d5028bfae docs(proxy): document Qwen Code wrapping 2026-08-30 01:44:53 +02:00
Julius Brussee d11cce7354 docs(proxy): document Kilo Code wrapping 2026-08-30 01:05:12 +02:00
Julius Brussee e11d0029f9 fix: harden weekly integrations and prepare v2.4.0 2026-08-29 21:57:23 +02:00
Julius Brussee aaef42dbe9 Merge remote-tracking branch 'origin/pr/904' into codex/weekly-triage-20260829 2026-08-29 21:31:33 +02:00
Julius Brussee 81536f57b3 Cleanup 2026-08-25 01:31:19 +02:00
Pavel Filippenko f436843949 fix: scope caveman mode to the session
Mode was applied per session but stored in one file per machine
($CLAUDE_CONFIG_DIR/.caveman-active). Four bugs followed from that one
fact: parallel windows shared a mode; deactivation was spelled "no flag
file", so SessionStart re-derived the configured default and undid an
explicit "stop caveman" after every auto-compaction; the statusline badge
rendered whichever window wrote last; and "off" could not survive a
SessionStart at all.

payload's `source`, but it read the legacy flag, where off is absence — a
deactivated session found nothing stored and fell back to getDefaultMode()
regardless. The other three are consequences of the storage shape, not of
when the hook re-derives.

State now lives in .caveman-sessions/<session_id>.mode, keyed by the
session_id Claude Code puts in every hook payload and in the statusline's
stdin JSON. The legacy flag stays as a last-write-wins compat mirror and
never receives the literal 'off': an older hook reading that would inject
"CAVEMAN MODE ACTIVE (off)" and an older statusline would render
[CAVEMAN:OFF]. Both spellings of off are read everywhere — a missing file
(old) and a literal 'off' (new, durable).

caveman-config.js gains the primitives: validateSessionId (a whitelist,
because a session id becomes part of a path), resolveActiveMode,
readSessionModeRaw, writeSessionMode, per-session prev for one-shot skills
(#599), gcSessionStore, and a session_id-tagged recordModeChange (#601).
Every one takes a sessionId that may be null or malformed and degrades to
the previous machine-wide behavior — the old code path IS the fallback
branch, which is why the existing suites, nonid,
pass unchanged.

SessionStart re-derives the default only on `startup` and `clear`; compact,
resume, fork, an unrecognized source and the n`
read the stored value. Grouping `clear` with the resets deliberately
differs from #691's comment: per-session storage makes the distinction
cheap, and nothing else in the conversation survives /clear either. The
watchdog must not reset, or a slow payload on a compaction re-arms a
session the user turned off.

The three entrypoints resolve the new helpers individually instead of
demanding them in their requireSibling shape checks (#848): a
caveman-config.js from before this change satisfies those checks, so
hard-failing would trade "machine-wide mode, no
state at all" on exactly the plugin-cache drift that guard exists for.

Both statusline ports take session_id from the stdin JSON with bounded,
TTY-guarded reads (integer timeout — bash 3.2 rejects fractional read -t)
and render nothing for a deactivated session. verify_repo.py greps them
against SESSIONS_DIRNAME and SESSION_ID_RE, tws
badge. caveman-stats filters the transition l
window's switches no longer skew another's es
(bin/install.js, uninstall.sh, uninstall.ps1) removes the session store;
.caveman-history.jsonl is kept on purpose. opide
behaviour — it writes its own flag in its own config dir and never sees a
Claude Code session_id.

Tests: 25 unit cases in tests/hooks/caveman-config.test.mjs,
SessionStartSourceTests, SessionScopedModeTests, 5 statusline cases, and
tests/manual/session-mode-smoke.sh (22 checks, end-to-end against a
throwaway config dir). docs/testing-session-modes.md carries the full plan
including the live-Claude-Code checks. Hook ced.

npm test 197/197 · tests/*.js clean · unittesss
· smoke 22/22

Signed-off-by: Pavel Filippenko <filippenko.ps@phystech.edu>
2026-08-24 09:54:58 +03:00
Julius Brussee 5e3d6b8c27 feat(learn): add multi-source outcome tracking
Upgrade `caveman learn` from a Claude/Codex setup linter into a broader local optimizer. It now scans Gemini, opencode, and opt-in aider history, uses catalog-backed context windows and measured turn-1 prefix evidence, adds new behavioral detectors, and ranks grouped portfolio moves without mixing historical totals into forward rates.

The CLI/proxy/TUI now support `learn applied`, `learn simulate`, `--repo`, per-repo reporting, and confirmed longitudinal fix verdicts (`improved`, `unchanged`, `regressed`, `insufficient_data`). Docs, the embedded `caveman-learn` skill, and tests were updated to cover the new learn v2 behavior and planning artifacts.
2026-08-17 16:59:35 +02:00
Julius Brussee ee3199bca8 docs: add technical manual, cross-link existing docs
Twenty-chapter manual under docs/technical/ with a docs/README.md index;
existing docs and package READMEs now link into it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bn1A1mU7TX9dsDb5mbSBE3
2026-08-16 21:21:25 +02:00