9daf1ec5ba
Closes out single-host before the cross-host work. Sequenced BEFORE CB-308 deliberately: federation's own gating concern is the trust model, and it inherits whatever identity shape lands here. The finding this stage is built around: bridged had exactly ONE security control, the loopback bind. ConnectionIdentity resolves a worker from its connection (unforgeable), but every caller that was not a recognised worker pane fell through to being treated as the PRIMARY -- the most privileged role on the bus. Latent today; load-bearing the moment a bind widens. CB-501 auth: - Role/Principal/CallerResolver: connection identity first, bearer token second, ANONYMOUS third. Inverts the old default so absence of identity means nothing, not everything. - Worker identity is never token-gated, so enabling auth cannot lock the fleet out of bridge_reply. - Constant-time token compare (MessageDigest.isEqual). - validateAuthExposure(): a non-loopback bind under loopback-trust now REFUSES TO START. Makes the dangerous config unrepresentable rather than merely documented. - TLS terminates at a reverse proxy by design (D3), not in the JVM. CB-505 authz + audit, enforced on BOTH entry paths: - The docs describe MCP as "a thin adapter over the REST core"; at code level it is not. BridgeMcp calls MessageService directly, and /mcp is a raw servlet on Jetty's context handler that never traverses Javalin's before filter. Enforcing only at REST would have left /mcp open. - Load-bearing rule is own-session-only: a worker may reply/ask only as itself. Structurally true over MCP already; over REST the session id in the URL path had simply been trusted. - Audit: JSON lines to a dedicated appender, additivity=false. Never records message content -- this bus carries source and prompts. CB-502 metrics: zero new dependencies. A ~150-line Prometheus text renderer instead of the specced Micrometer, because this pom already hand-pins jackson-annotations to reconcile Jackson 2/3, imports a Jetty BOM against skew, and carries four accepted-CVE advisories -- and CLAUDE.md's mandated dependency CVE gate could not be run (no JetBrains MCP server connected). Instrumented at MessageService, the single funnel both surfaces share. CB-503 CI: .gitea/workflows/ci.yml against the already-running Gitea runner. Needs no contract-exclusion flag -- the pom's default-excludes profile already sets excludedGroups=contract, so plain `mvn clean install` IS the mock-socket surface. Provisions JDK 25 explicitly (runner default-jdk is older). CB-504 supervision: launchd agent (the real target -- this host is macOS, there is no systemd) plus a systemd unit for the Linux gateways CB-308 adds. Ordering directives are advisory, so the actual fix is that startup now waits up to 30s for the herdr socket and then serves degraded, instead of crashing into a restart loop on a boot-order race. Also fixes drift found while surveying: - bridged.example.yaml documented spawn_ready_timeout_ms in snake_case; config binds via plain Jackson with ignoreUnknown, so uncommenting it would have been silently dropped and the default kept. Now camelCase, with a test that loads the shipped example and one that pins every documented knob's spelling -- no test had ever loaded that file. - Added the 6 shipped-but-undocumented knobs (worktreeRoot, parityOverlay, gitTokenEnv, gitHostEnv, configDir, primary:). - README "Next" listed bridge_ask and session lifecycle as upcoming; both shipped long ago. - docs/CB-301-ext and docs/CB-402 status headers said "design"/"pre- implementation" for work already merged. 307 unit/acceptance tests green (was 266), mvn clean install BUILD SUCCESS. Note: CLAUDE.md's per-file ide_diagnostics gate and the pom Mend.io CVE check could not be run -- no JetBrains/intellij-index MCP server is connected this session. mvn clean install is the only gate that ran.
58 lines
2.1 KiB
Desktop File
58 lines
2.1 KiB
Desktop File
# CB-504 — systemd unit for bridged (Linux).
|
|
#
|
|
# The macOS launchd agent (deploy/dev.ltms.bridged.plist) is the supervision target for the
|
|
# current single-host deployment. This unit exists for the per-host gateways CB-308 introduces,
|
|
# which will run on Linux.
|
|
#
|
|
# Install (user service — bridged drives the user's herdr, not a system daemon):
|
|
# mkdir -p ~/.config/systemd/user
|
|
# cp deploy/bridged.service ~/.config/systemd/user/
|
|
# # edit ExecStart / WorkingDirectory / Environment below, then:
|
|
# systemctl --user daemon-reload
|
|
# systemctl --user enable --now bridged
|
|
# journalctl --user -u bridged -f
|
|
|
|
[Unit]
|
|
Description=bridged — claude-bridge message server
|
|
Documentation=https://git.ltms.dev/lms/claude-bridge/wiki
|
|
# Ordering only: herdr is a user process and its socket may appear after us. This is advisory —
|
|
# bridged retries the herdr socket rather than exiting, which is what actually makes a late
|
|
# socket survivable. Do NOT add Requires=: a herdr restart must not take bridged down with it.
|
|
After=herdr.service
|
|
Wants=herdr.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=%h/src/claude-bridge/bridged
|
|
ExecStart=/usr/lib/jvm/temurin-25-jdk/bin/java -jar target/bridged.jar bridged.yaml
|
|
|
|
Environment=HERDR_SOCKET_PATH=%h/.config/herdr/herdr.sock
|
|
# Secrets are NOT set here — this file is committed. Put the API/worker tokens in a private
|
|
# drop-in that systemd reads with restrictive permissions:
|
|
# systemctl --user edit bridged → [Service] / Environment=BRIDGED_API_TOKEN=...
|
|
# or point EnvironmentFile at a 0600 file:
|
|
# EnvironmentFile=%h/.config/bridged/env
|
|
|
|
Restart=on-failure
|
|
RestartSec=10s
|
|
# A bad config (e.g. a non-loopback bind without token auth) makes bridged fail fast by design.
|
|
# Give up rather than restart-loop on a permanent error.
|
|
StartLimitBurst=5
|
|
StartLimitIntervalSec=120
|
|
|
|
# The daemon reads the repo, writes worktrees, and talks to a Unix socket — it needs no more.
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
ProtectSystem=strict
|
|
ProtectHome=read-write
|
|
ProtectKernelTunables=true
|
|
ProtectControlGroups=true
|
|
RestrictSUIDSGID=true
|
|
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=bridged
|
|
|
|
[Install]
|
|
WantedBy=default.target
|