Skip to content

feat(license): open the license FCs to any authenticated role, not just admin - #176

Merged
marconetsf merged 1 commit into
developmentfrom
feat/license-any-authenticated-role
Aug 25, 2026
Merged

marconetsf merged 1 commit into
developmentfrom
feat/license-any-authenticated-role

Conversation

@marconetsf

Copy link
Copy Markdown
Contributor

Why

Hit in a live bench test (2026-08-25): activating a purchased licence on a runtime-v4 device required an admin role. The license FCs (0x48 anchor read, 0x49 blob write, 0x4A read back) were admin-gated in #169 on the theory that they were a trust boundary.

That gate protected the wrong thing. The purchase is authorized by the Edge account on the /buy page, never by the runtime role. So admin-gating the device-side FCs did not gate the money or the entitlement — it only stopped an operator from writing a licence they had already paid for. Runtime role and purchase authority are orthogonal.

What

  • debug_websocket.py: drop the _current_user_is_admin gate on the license FCs (plus the now-dead helper and its current_user import). JWT re-verification still runs on every command, so "any role" means any logged-in user, never anonymous.
  • test_debug_websocket_auth.py: the parametrized test flips from "a non-admin cannot use the license FCs" to "a user role can run 0x48/0x49/0x4A, and Admin privileges required never returns". 0x48 is additionally asserted to hand the user the real anchor.

What opens, and why it is low-risk

Concern Why it is acceptable
Non-admin reads the anchor (0x48) The anchor is the board's serial. Baremetal exposes it with no auth at all (physical access = full access); opening v4 only aligns with that.
Non-admin writes the blob (0x49) The blob is node-locked (device_id + product_id), useless on another device. A bad write is recoverable: the entitlement lives in the backend, and a refresh rewrites the correct blob.
Anonymous access Not possible — _reverify_session_token() still runs on every command.

Proof

  • py_compile clean.
  • pytest: 45 passed (tests/pytest/restapi/test_debug_websocket_auth.py + tests/pytest/plugins/test_vpp_license_debug.py). The 0x49 case shows the write is now reachable by a user and refuses an invalid blob on validation (status 0x83), not on role.

🤖 Generated with Claude Code

https://claude.ai/code/session_015uUH3ZL5ehreMUf2dtanWD

…st admin

The license function codes (0x48 anchor read, 0x49 blob write, 0x4A read
back) were admin-gated in #169 on the theory that they were a trust boundary.
That gate protected the wrong thing: the purchase is authorized by the Edge
account on the /buy page, never by the runtime role, so requiring admin here
only stopped an operator from activating a licence they had already paid for
(hit in a live bench test 2026-08-25).

What opens is low-risk: the anchor is the board's serial (baremetal exposes it
with no auth at all), the blob is node-locked and useless on another device,
and a bad write is recoverable (the entitlement lives in the backend; a
refresh rewrites the correct blob). JWT re-verification still runs on every
command, so "any role" means any logged-in user, never anonymous.

- debug_websocket.py: drop the _current_user_is_admin gate on the license FCs,
  plus the now-dead helper and its current_user import.
- test_debug_websocket_auth.py: the parametrized test now proves a user role
  runs 0x48/0x49/0x4A and that "Admin privileges required" never returns.

Suite: 45 passed (restapi + plugins license tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uUH3ZL5ehreMUf2dtanWD
@marconetsf
marconetsf merged commit 1d47f4b into development Aug 25, 2026
@thiagoralves
thiagoralves deleted the feat/license-any-authenticated-role branch August 27, 2026 13:14
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