agap-mcp: authenticate the :3100 listener (kb#180), pin bw CLI, add capture/classifier
Listener auth (kb#180, DESIGN-a2a-agents.md §4)
-----------------------------------------------
agap-mcp binds :3100 on every interface (network_mode: host) and the LAN
carries VPN-terminated peers, so an unauthenticated JSON-RPC listener handed
ha_call_service / gitea_wiki_write / wiki_edit / radicale+todoist writes and
POST /capture-idea to any LAN peer. Only vw_* was gated before (kb#147), and
only at ENFORCE=1.
src/listener-auth.js now requires `Authorization: Bearer <token>` resolving to
a known agent id on every route except /health, which stays open so a
misconfigured token map is still diagnosable. Two gates stay deliberately
layered and independently switchable: "are you an agent at all?" (this file)
vs "are you trusted enough for the vault?" (trust-gate.js), both reading the
same token map.
Also closes an SSE session-hijack hole: /messages previously trusted any
sessionId with no credential, so a guessed or leaked id was full tool access.
Sessions are now pinned to the caller identity captured at the /sse handshake,
comparing agent id *and* token.
Auth defaults ON, and boot fails loudly if the token map is empty rather than
serving 401 to everyone while /health reports ok. Rollback is
AGAP_MCP_REQUIRE_AUTH=0.
Verified live: unauthenticated and bad-token /mcp -> 401, unauthenticated
/capture-idea -> 401, /health -> 200, both real agent tokens -> 200 with 36
tools, including from inside the adolf container.
Pin the bw CLI
--------------
The Dockerfile installed @bitwarden/cli unpinned. Rebuilding jumped
2026.2.0 -> 2026.7.0, whose WASM cipher deserializer rejects any stored login
carrying `"uri": null` ("invalid type: JsValue(Object({...})), expected a
string") -- 33 of 49 items in this vault have that shape. `bw list` then exits
1, server init fails, and the container crash-loops. Pinned to 2026.2.0.
Do not unpin: 2026.7.0 cannot authenticate against this Vaultwarden
(2025.12.0) at all -- it refuses plain HTTP outright and 404s on the identity
endpoint over HTTPS. Updating the CLI requires upgrading Vaultwarden first.
capture / classifier
--------------------
Adds the POST /capture-idea REST endpoint and the idea classifier behind it
(consumed by the todoist-capture plugin), with tests. Carried in the same
commit because server.js wires both this and the auth boot path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -36,9 +36,40 @@ services:
|
||||
# inlined in this committed file. Empty object = no caller resolves to
|
||||
# any agent, i.e. fail-closed once ENFORCE is turned on.
|
||||
- AGAP_MCP_AGENT_TOKENS=${AGAP_MCP_AGENT_TOKENS:-{}}
|
||||
# kb#180 (DESIGN-a2a-agents.md §4) — authentication of the LISTENER
|
||||
# itself, a strictly larger gate than the vw_*-only one above. With
|
||||
# this on (the default in code), /mcp, /sse, /messages and
|
||||
# /capture-idea all require `Authorization: Bearer <token>` resolving
|
||||
# to an agent id in AGAP_MCP_AGENT_TOKENS; /health stays open for this
|
||||
# healthcheck. :3100 is bound on every interface (network_mode: host)
|
||||
# and the LAN carries VPN-terminated peers, so an open listener means
|
||||
# any peer can call ha_call_service / gitea_wiki_write / wiki_edit /
|
||||
# todoist writes.
|
||||
#
|
||||
# ACTIVATION IS NOT AUTOMATIC-SAFE: with auth on and
|
||||
# AGAP_MCP_AGENT_TOKENS empty, the process REFUSES TO START (loud
|
||||
# crash instead of denying every caller while /health says ok). So
|
||||
# AGAP_MCP_AGENT_TOKENS must be populated in this directory's .env
|
||||
# BEFORE the next restart of this service, and every caller
|
||||
# (Adolf/shared-mcp.json, Claude Code .claude.json, the
|
||||
# todoist-capture-plugin) must be given its token — see the kb#180
|
||||
# migration list. Set AGAP_MCP_REQUIRE_AUTH=0 in .env only as a
|
||||
# deliberate emergency rollback to the old open listener.
|
||||
- AGAP_MCP_REQUIRE_AUTH=${AGAP_MCP_REQUIRE_AUTH:-1}
|
||||
volumes:
|
||||
- /home/alvis/.config/Bitwarden CLI:/bw-data
|
||||
# Read-only: agent-registry.yaml is the version-controlled source of
|
||||
# truth for trust classes (kb#134/kb#147) — mounted, never copied, so
|
||||
# a registry edit takes effect on container restart with no rebuild.
|
||||
- /home/alvis/agap_git/openai/agent-registry.yaml:/agent-registry.yaml:ro
|
||||
# kb#190: /health responds 200 with no auth/side effects (confirmed).
|
||||
# This is a SEPARATE compose project from openai/docker-compose.yml
|
||||
# (network_mode: host, reached from adolf-llm etc. via
|
||||
# host.docker.internal), so it cannot be wired into that file's
|
||||
# depends_on/condition chain -- this only gives it its own status.
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "node -e \"fetch('http://localhost:3100/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 20s
|
||||
|
||||
Reference in New Issue
Block a user