Skip to content

fix(auth): remove node role from anonymous self-registration - #82

Merged
adrian-burlacu-software merged 1 commit into
mainfrom
copilot/fix-auth-register-role-vulnerability
Aug 2, 2026
Merged

fix(auth): remove node role from anonymous self-registration#82
adrian-burlacu-software merged 1 commit into
mainfrom
copilot/fix-auth-register-role-vulnerability

Conversation

Copilot AI commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

The /auth/register endpoint allowed unauthenticated callers to self-assign the node role. Unlike viewer, node is a control-plane role — migrations 014/017 gate writes (node/pod state, metrics) on has_any_role(['node','admin']). Anyone could POST /auth/register with {"roles":["node"]} and get write access with zero authentication.

Changes

  • Removed 'node' from nonAdminRoles so anonymous registration is restricted to viewer only
  • Admin-path registration (isAdmin && roles) is unchanged — admins can still assign any role including node
  • Node enrollment continues to work via the dedicated node-auth flow (pod-auth-service.ts / api/nodes.ts)
- const nonAdminRoles = ['node', 'viewer'] as const;
+ const nonAdminRoles = ['viewer'] as const;

Remove 'node' from nonAdminRoles to prevent unauthenticated users from
self-registering with the 'node' role, which grants control-plane write
access (node/pod state, metrics). Node registration should go through
the dedicated node-auth flow (pod-auth-service / api/nodes).
Copilot AI changed the title fix(auth): restrict anonymous registration to viewer role only fix(auth): remove node role from anonymous self-registration Aug 2, 2026
@adrian-burlacu-software

Copy link
Copy Markdown
Member

From the bot:

Why the change won't break automation
The concern is that nodes need the node role to operate, so removing node from nonAdminRoles in /auth/register might block legitimate node registration. It won't, because automated nodes never use /auth/register to get their identity. There are two completely separate registration paths, and only the irrelevant one was changed:

  1. How nodes actually register (unchanged)
    Node registration is a two-step authenticated flow:

WebSocket authentication (auth:authenticate message in connection-manager.ts:468): The node connects over WebSocket and presents a Supabase JWT. The connection manager validates the JWT via supabase.auth.getUser() (line 155), then sets conn.userId, conn.isAuthenticated, and conn.userRoles on the WebSocket connection (lines 720-724). This JWT comes from a pre-existing user account — typically an admin or operator who set up the node.

Node registration (node:register message → handleNodeRegister in node-handler.ts:106): After the WebSocket is authenticated, the node sends a node:register message. The handler checks ws.userId is set (line 113 — rejects if not), then creates the node record in the database with registeredBy: ws.userId (line 185).

There is also a REST equivalent: POST /api/nodes (in api/nodes.ts:664) which goes through authMiddleware → abilityMiddleware → canCreateNode — all require an existing authenticated user with the right RBAC permissions.

Neither of these paths touches /auth/register or its nonAdminRoles list. They both require a pre-existing authenticated user with a valid JWT.

  1. What /auth/register actually does (what was changed)
    POST /auth/register (api/auth.ts:332) is the user self-registration endpoint — it creates a new human user account in Supabase Auth with app_metadata.roles. It's a public endpoint (no authMiddleware on line 1081) intended for onboarding human users.

The nonAdminRoles list controls which roles an anonymous (unauthenticated) caller can request when creating their own account. Before the fix, someone could call:

Code
POST /auth/register
{"username": "attacker", "email": "a@b.com", "password": "...", "roles": ["node"]}
...and get a user account with the node role, which grants write access to node state, pod state, and metrics through the RLS policies in migrations 014/017 (has_any_role(['node','admin'])).

  1. Why there's no conflict
    Nodes don't self-register users. A node process authenticates with credentials (JWT) from an already-existing user account. It doesn't call /auth/register to create a new user for itself.
    Node creation is a separate concept from user creation. "Registering a node" (POST /api/nodes or node:register WS message) creates a row in the nodes table. "Registering a user" (POST /auth/register) creates a Supabase Auth user. These are independent operations.
    The pod-auth-service (pod-auth-service.ts) is yet a third auth mechanism — HMAC-signed tokens for pod-to-server communication — also completely unrelated to /auth/register.
    Summary
    Path What it does Uses /auth/register? Affected by change?
    WS auth:authenticate + node:register Authenticates node agent, creates node record No — uses existing JWT No
    POST /api/nodes REST node registration No — requires authMiddleware No
    Pod token auth Pod-to-server HMAC tokens No — uses pod-auth-service No
    POST /auth/register Creates human user accounts Yes — this is what was fixed Yes — only affects what roles anonymous callers can self-assign
    The only thing that changed is: an unauthenticated caller hitting /auth/register can no longer request the node role. An admin can still assign any role (including node) through the same endpoint when authenticated, and all actual node/pod automation continues to work through its own dedicated auth flows.

@adrian-burlacu-software
adrian-burlacu-software marked this pull request as ready for review August 2, 2026 17:58
@adrian-burlacu-software
adrian-burlacu-software merged commit e8c519f into main Aug 2, 2026
3 of 4 checks passed
Copilot stopped work on behalf of adrian-burlacu-software due to an error August 2, 2026 17:58
@adrian-burlacu-software
adrian-burlacu-software deleted the copilot/fix-auth-register-role-vulnerability branch August 2, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants