Harden auth before exposing Nexus to the internet #110
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lz/agent-nexus#110
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Tracking issue. Nexus's auth is built for the deployment it has today: one operator, one passphrase, a LAN. Every item below is a reasonable choice at that scale and a real exposure the moment the instance is reachable from the internet. None of them are addressed by #108 (session expiry) or #109 (scoped API tokens).
Filed as a checklist, not a plan. Each item is independently shippable.
1. No brute-force protection on
/api/auth/unlock— highest priorityroutes/api/auth/unlock/+server.tsaccepts unlimited unauthenticated attempts. It is inPUBLIC_API(hooks.server.ts:5) by necessity. Argon2id raises the cost per guess but does not bound the number of guesses, and it is the server's CPU paying that cost — so the same property that slows an attacker is also a DoS vector.Wants: attempt counter with backoff, keyed on source IP and globally; a lockout window; logging of failures. The global key matters — per-IP alone is trivially defeated.
2. Sessions are in-memory and hold the vault key
SessionStore(auth/session.ts) is aMapin the process. Two consequences:event.locals.masterKeyis the unlocked master key. The session is not a pointer to an identity, it is a live key. This is the single deepest assumption in the codebase and the one that multi-tenancy breaks (see the multi-tenancy issue).Note: persisting sessions is not a fix on its own. A restored session without its key is authenticated but locked — same failure, more code. Any solution has to answer where key material lives across a restart, and "on disk" defeats the vault.
3.
/api/auth/lockis globalsessions.destroyAll()(auth/session.ts:38) zeroizes and drops every session. With one operator that is exactly right and is the point of the button. With more than one, any user locking signs out the whole instance. Needs to become per-session, with the global form kept as an explicit admin action.4. Secure cookies are opt-in
force_secure_cookiedefaults off andNODE_ENV=productionis what otherwise setsSecure. An internet-exposed instance must not be reachable over plain HTTP at all — the flag should be a hard requirement in that posture rather than a knob, and there is currently nothing that warns an operator who exposes an instance without it.5. No CSRF layer beyond
SameSite=LaxThe API is JSON routes, not SvelteKit form actions, so SvelteKit's built-in origin check does not cover them.
Laxblocks cross-site POST cookies and carries most of the weight today, but it is the only thing doing so. Worth an explicit origin check on state-changing routes.6. No session inventory or revocation
There is no way to see active sessions or revoke one. Single-operator,
/api/auth/lockis a sufficient blunt instrument. Beyond that, "sign out other devices" is table stakes — and it becomes a hard requirement once #109 lands, since a leaked API token needs revocation independent of sessions.Out of scope here
Rate limiting the rest of the API, audit logging, and 2FA. Real, but each wants its own issue.