A Local Admin Panel Still Needs Auth in APX
A local admin panel should feel convenient, not anonymous.
That boundary matters for APC and APX.
APC is the portable context layer. It keeps project meaning in the repo through AGENTS.md, .apc/, agent files, skills, and other committed artifacts. APX is the daily-use runtime and tooling layer. It exposes that project through a daemon, CLI, web admin, pairing flow, and local state under ~/.apx/.
Because APX owns runtime state, it also has to protect it.
That is why the web admin in APX does not treat localhost like a permission model.
At first glance, a local-only UI can look harmless. If the browser and daemon are on the same machine, it is tempting to assume the panel can just trust every request. But the panel can read projects, sessions, messages, MCPs, config, and other runtime-only data. APC keeps portable project context in the repository, but APX keeps operational state outside the repo for a reason. That state still needs an access boundary.
APX handles this with a small bootstrap path instead of making the whole HTTP surface public.
In src/host/daemon/api/shared.js, auth is bearer-token based. Data routes require Authorization: Bearer ... unless they are on a short allowlist. The important exception is /admin/web-token, which is intentionally unauthenticated only so the local admin bundle can bootstrap itself. The comments are explicit: /admin/web-token is for the same-origin admin panel, and that endpoint performs its own localhost checks.
The allowlist stays narrow on purpose:
-
/healthstays public for liveness checks. -
/pair/*stays open so a fresh device can bootstrap a token. -
/admin/web-tokenstays open so the local browser can fetch its bearer. - Static assets and known SPA routes can load without a token, but data GETs do not.
That last point is the important hardening step.
Older daemon designs often used prefix denylists and accidentally leaked new data routes when somebody forgot to register them. APX goes the other direction. In the auth middleware, unknown extension-less GET routes are treated as protected by default. Only static files and known client-router paths pass before the token is present.
The tests make this concrete.
tests/health-auth.test.js checks that /projects rejects missing or wrong bearer tokens, while /assets/app-abc123.js and /settings can load without getting blocked by the auth wall. tests/security-hardening.test.js adds regression coverage for routes like /plugins and /skills, asserting that they now require a token even though they are GET endpoints.
So the web panel opens in two phases:
- The browser loads the shell and assets.
- The panel fetches
/admin/web-tokenand uses that bearer for actual API calls.
The docs describe the same flow. On the local machine, the panel fetches its token automatically from /admin/web-token. On another device, apx pair web prints a URL with a #token=... fragment so the paired browser can authenticate without exposing the full daemon publicly by default.
That design fits the APC/APX split well.
APC should stay portable, reviewable, and repo-owned. It should not need to encode browser auth rules. APX should own runtime concerns such as daemon auth, paired devices, local sessions, and route protection. If those concerns leak back into APC, the project contract gets polluted with machine-local policy. If APX ignores them, the runtime becomes too trusting.
So the practical rule is simple:
- APC defines project context.
- APX exposes runtime surfaces.
- Every runtime surface still needs an access boundary, even on localhost.
A local admin panel is still an admin panel.
APX gets that right by keeping the portable layer clean and putting authentication exactly where the runtime belongs.
Top comments (0)