Skip to content

Security posture — auth boundary and accepted risk

Confirms and documents the read-auth boundary raised as the "unauth-precedent context note" finding (ARGUS-814, batch 2026-07-19-argus, artifact #1108). This is a contract doc like API conventions — it records a deliberate decision, not a TODO.

The gap

GET /tasks.json (and the rest of the hub's *.json read surface — /runs.json, /artifacts.json, /projects.json, /models.json, and similar) returns 200 with the full payload to an unauthenticated caller. Confirmed live post-ARGUS-810 (the /ui/* auth-bypass fix): an authed and an unauthed GET /tasks.json return identical 200 bodies, while unauthenticated /ui/* now correctly 404s. Authorization: Bearer <ARGUS_CAPTURE_TOKEN> is checked on mutations and a short list of non-public reads (see Auth in the API conventions) but is decorative on the general *.json surface — every client that sends it (the app, watchers, monitors) does so voluntarily, not because the server demands it.

Decision: accepted, not a bug

The hub's ingress is Tailscale-only (homelab-infra convention — no public exposure; Funnel is reserved for external webhooks). Anything that can reach argus.aaronbrazier.com is already a device on Aaron's tailnet, the same trust boundary every other homelab exporter (/metrics, unauthenticated Prometheus scrape) relies on. Within that boundary:

  • The *.json read surface carries task/run/artifact/config state, not secrets — no credentials, no tokens, no PII cross a GET on this surface.
  • Every one of these routes is already commented Public read like the rest of the *.json surface at its registration in internal/hub/server.go; this has been the consistent, intentional shape of the API since the phase-3 app surfaces multiplied .json endpoints (ARGUS-263), not an oversight ARGUS-810 missed.
  • Requiring a bearer on every read would not remove a real attacker capability inside the tailnet (an unauthenticated process on the tailnet can already reach the Postgres-backed state other ways), but it would add a hard failure mode to every current and future read client for no corresponding safety gain.

Aaron's ruling (2026-07-19, in response to ARGUS-814): accept the open-read-on-tailnet stance for the *.json surface. Mutations, the human-only approve group, and the report/events bearer group stay gated as documented in Auth — this decision only covers reads that were already public in practice.

When this changes

Revisit this decision (open a new ADR, don't just edit this file) if any of the following becomes true:

  • Tailscale-only ingress is relaxed (a Funnel route added for something other than webhooks, or the tailnet gains an untrusted member class).
  • A *.json read surface starts carrying a secret, credential, or personally identifying payload.
  • The read surface needs per-caller authorization (not just "on the tailnet or not") — e.g. multi-tenant use, which USER.md's single-user assumption currently rules out.