328 more routes used a JSON body as a dict straight from
request.get_json(), so a body of null, [] or a bare string crashed them
into a 500. Bare reads become json_object(); the
'get_json(silent=True) or {}' reads of action routes become
json_object(optional=True), which still treats a missing body as {}.
Routes that take arrays or pass the body through untouched are left
alone. The full backend suite shows no new failures.
Also carries three files' bare section citations (apps, backups,
notifications) and the extended citation guard, which needed them.
Comments, docstrings and a few strings cited plan numbers ("plan 29
#9", "plan 86 §B2") from a git-ignored folder, so they pointed at
nothing; one reached users in a bottleneck hint. Code and frontend
strings are unchanged apart from those citations (checked by AST and
token comparison). A vhost still carrying the old asset comment reads
as undrifted, and a guard test keeps new citations out.
agent_registry.authenticate_and_register owns the full handshake
(rate-limit -> HMAC -> allowed-IPs/anomaly -> new-IP -> version ->
register) and ingest_agent_state owns heartbeat/sysinfo/capability
side-effects; the WS namespace and long-poll endpoints become thin
adapters translating error codes to emit/disconnect vs jsonify/status.
The per-IP auth limiter moves to the registry (re-exported from the
gateway for existing callers/tests). Gate: parametrized ws/poll matrix
(bad HMAC, blocked IP, rate limit, new-IP anomaly) + a word-anchored
ratchet that the adapters never re-grow the sequence.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Introduces a new `.coderabbit.yaml` with ServerKit-specific review guardrails, and expands env/security documentation for trusted proxy headers and per-IP login brute-force throttling. It also adds inline backend notes clarifying `request.remote_addr` behavior under ProxyFix for auth, cron, and pairing paths, and updates the frontend plugin manifest to remove the Tramo and K8s plugin entries.
Two correctness defects from the deep-dive review.
B. register_agent left a stale in-memory "online" entry when the DB write
failed. The except block rolled back the session but kept the
ConnectedAgent in _agents, so the gateway still emitted auth_ok and
command routing targeted an agent with no backing AgentSession row
(online/offline desync). register_agent now returns Optional[str]: on DB
failure it removes its own in-memory entry (guarded on identity so a
concurrent reconnect isn't clobbered) and returns None. Both callers —
the WS gateway (on_auth) and the poll /connect endpoint — now emit
auth_fail / HTTP 500 instead of reporting a connected agent. A stale
'online' server row with no in-memory agent is self-corrected by the
heartbeat reaper.
C. send_command swallowed the final command-record DB update on the
timeout/result path: a failed commit left the row stuck in 'running'
with no log line, making it invisible. Now logs the exception (the row
is still left for a future sweep, but it's now diagnosable).
Adds a regression test that register_agent returns None and rolls back the
in-memory registration when the session write fails.
Full backend suite: 89 passed, 12 skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The WebSocket auth path (agent_gateway.on_auth) throttles auth attempts
per-IP via _check_auth_rate_limit, but the REST long-poll equivalent,
POST /api/v1/agent/connect, did full HMAC auth with no throttle. That left an
unthrottled credential-stuffing surface against agent auth while the WS path
stayed protected.
connect() now calls the same shared limiter before verify_agent_auth and
returns 429 when the per-IP window is exhausted. Adds a regression test that
drives the real endpoint through the limit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Refresh button on the System Status card was returning 403
because the per-server permissions allowlist (e.g. 'docker_manager'
profile) doesn't include 'agent:recapabilities' and the scope
expander has no rule for the agent: namespace. The action is purely
panel-driven discovery — it doesn't mutate the host — so it
shouldn't be subject to the same ACL as install/exec actions.
- Add Server.CONTROL_PLANE_ACTIONS bypass set: agent:recapabilities,
agent:update. has_permission() returns True for these unconditionally;
JWT auth at the API layer is what gates access.
- /poll endpoint now logs the keys it received in metrics / system_info
/ capabilities so we can debug "panel says N/A" failure modes from
the panel logs without instrumenting the agent.
- Agent's sendCapabilities now logs at INFO with sudo mode + capability
count + runtime count so the agent.log shows whether the periodic
resend is firing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Probe and report agent feature capabilities (cron, docker, systemd, nginx, etc.) so the panel can gate target pickers without extra round-trips.
Key changes:
- Add agent/internal/capabilities: probes host for features and platform/distro, returns protocol.CapabilitiesMessage cached at agent startup.
- Agent: store probed capabilities, send them on every transport reconnect (sendCapabilities) and include them when using poll transport.
- Protocol: introduce TypeCapabilities and CapabilitiesMessage (map[string]bool + platform/distro fields).
- Poll transport: accept CapabilitiesMessage in Send(), serialize to raw payload and queue for next /poll; support one-shot capabilities in poll body.
- Backend: handle capabilities from socket (on_capabilities) and poll endpoint; persist capabilities in in-memory ConnectedAgent (agent_registry) with helpers get_capabilities/has_capability; include capabilities/platform info in servers API.
- New fleet API (GET /api/v1/fleet/targets): list runnable targets (local + connected agents) and optionally filter agents by a requested feature using the advertised capability map.
Rationale: make agent feature availability explicit and discoverable to the panel so UI pickers can show only applicable agents without asking agents on every UI interaction.
Introduce a fallback polling transport and manager: add internal/transport (Manager + poll client) so the agent can fall back from WebSocket to REST long-poll when tunnels mangle WS frames. Wire the Manager into the Agent and expose transport mode in IPC status.
Windows/service fixes: add service-scoped mutex to prevent concurrent service starts, probe IPC port on start to fail fast if occupied, and mirror main.Version into agent package at init. Fix spawned wizard visibility by setting HideWindow=false and add focus helpers that force foreground on Windows.
UI/asset changes: enable DevTools by default (env to disable), inject a small JS shim to forward console errors/navigation readiness to Go logs, hide WebView host until bundle ready (with timeout), and wrap static assets with no-store headers to avoid stale cached bundles. Add local /local/status endpoint so the UI can detect registration when IPC is unreachable.
Pairing/service handling: ensure service stop/start errors are surfaced, wait for service to reach RUNNING after pairing and surface helpful error messages if it doesn't. Implement platform-specific waitForServiceRunning and service control stubs.
Auth/backend alignment: expand API key prefix from 8 to 12 chars to match backend panel expectations and avoid authentication mismatches.
Misc: add focus_windows.go (Windows-only), various small refactors and logging improvements to make blank-page and pairing failures debuggable.