feat(web): authorize agent-binary download endpoint (VS-57) #80

Closed
David wants to merge 1 commit from feat/VS-57-authz-agent-binary-download into main
Owner

What

Authorizes the public agent-binary download endpoint GET /api/v1/agent/binary/:id (VS-56). It shipped with no client-facing authz, leaving the streamed binary an open distribution point; the parent epic VS-54 requires authorization before it is exposed publicly.

How

  • New install_token module: InstallTokenStore, an in-memory store of opaque, URL-safe, TTL'd tokens bound to the minting operator's userid, modeled on control_token (VS-32). Multi-use within a 10-minute window (not single-use like the control token) so the operator's browser download button and the unattended device-side curl and its retries all present the same token from one enrollment.
  • agent_binary_download_handler authorizes before any upstream read: accept the operator vervain_session cookie (browser "Download agent" button) OR a valid ?token= install token (device curl, which carries no session); otherwise 401 with no body and no Forgejo fetch. No per-id permission beyond authentication, so 401 is the only failure mode.
  • Updated the route and handler doc comments to document the authorized-caller model.

Scope split

This is the enforcement half plus the token primitive. The token-minting HTTP endpoint the enrollment flow calls (GET /agentbinaries/install-token) is the separate, linked VS-60. VS-57 is safe to land first: the only consumer of the public route is the enrollment dialog (which cannot render until VS-60), and the legacy /vervainagents/:id agent self-update path is a different, intentionally unauthenticated route this change does not touch.

Tests

  • Store: mint/verify, multi-use within TTL, unknown-token rejection, expiry-and-reap, per-mint distinctness.
  • Handler: 401 with no credentials, 401 with an invalid token, and an authorized token passing the gate through to the no-fetcher 404.

Full just pre-commit gate green (fmt, clippy -D warnings, workspace check, 445 lib tests).

Acceptance criteria (VS-57)

  • Unauthorized requests receive 401 with no binary bytes and no upstream error detail.
  • Authorized-caller model documented (operator session for the browser button; scoped expiring install token for the unattended fetch).
  • Server-side Forgejo PAT never derivable from any client-visible response (unchanged from VS-56; authz runs before the stream).
  • Authorization enforced before the Forgejo stream begins.
## What Authorizes the public agent-binary download endpoint `GET /api/v1/agent/binary/:id` (VS-56). It shipped with no client-facing authz, leaving the streamed binary an open distribution point; the parent epic VS-54 requires authorization before it is exposed publicly. ## How - New `install_token` module: `InstallTokenStore`, an in-memory store of opaque, URL-safe, TTL'd tokens bound to the minting operator's userid, modeled on `control_token` (VS-32). Multi-use within a 10-minute window (not single-use like the control token) so the operator's browser download button and the unattended device-side `curl` and its retries all present the same token from one enrollment. - `agent_binary_download_handler` authorizes before any upstream read: accept the operator `vervain_session` cookie (browser "Download agent" button) OR a valid `?token=` install token (device `curl`, which carries no session); otherwise 401 with no body and no Forgejo fetch. No per-id permission beyond authentication, so 401 is the only failure mode. - Updated the route and handler doc comments to document the authorized-caller model. ## Scope split This is the enforcement half plus the token primitive. The token-minting HTTP endpoint the enrollment flow calls (`GET /agentbinaries/install-token`) is the separate, linked VS-60. VS-57 is safe to land first: the only consumer of the public route is the enrollment dialog (which cannot render until VS-60), and the legacy `/vervainagents/:id` agent self-update path is a different, intentionally unauthenticated route this change does not touch. ## Tests - Store: mint/verify, multi-use within TTL, unknown-token rejection, expiry-and-reap, per-mint distinctness. - Handler: 401 with no credentials, 401 with an invalid token, and an authorized token passing the gate through to the no-fetcher 404. Full `just pre-commit` gate green (fmt, clippy `-D warnings`, workspace check, 445 lib tests). ## Acceptance criteria (VS-57) - [x] Unauthorized requests receive 401 with no binary bytes and no upstream error detail. - [x] Authorized-caller model documented (operator session for the browser button; scoped expiring install token for the unattended fetch). - [x] Server-side Forgejo PAT never derivable from any client-visible response (unchanged from VS-56; authz runs before the stream). - [x] Authorization enforced before the Forgejo stream begins.
feat(web): authorize agent-binary download with session or install token
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 1m36s
Create release / Create release from merged PR (pull_request) Has been skipped
fc69604d7c
The public agent-binary download endpoint `GET /api/v1/agent/binary/:id` (VS-56) shipped without client-facing authorization, so the streamed binary was an open distribution point. Add the authorization the parent epic (VS-54) requires before public exposure.

A new `install_token` module provides `InstallTokenStore`: an in-memory store of opaque, URL-safe, TTL'd tokens bound to the minting operator's userid, modeled on `control_token` (VS-32). Unlike the single-use control-upgrade token, an install token is multi-use within its 10-minute window so the operator's browser download button and the unattended device-side `curl` (plus retries) can all present the same token from one enrollment.

`agent_binary_download_handler` now authorizes before any upstream read: a request is accepted when it carries either the operator's `vervain_session` cookie (browser "Download agent" button) or a valid `?token=` install token (device `curl`, which has no session). Otherwise it returns 401 with no body and no upstream fetch, so an unauthorized caller never triggers a Forgejo read or sees a binary byte. There is no per-id permission beyond authentication, so 401 is the only failure mode.

The token-minting HTTP endpoint the enrollment flow calls (`GET /agentbinaries/install-token`) is the separate, linked VS-60; this change is the enforcement half and the token primitive it validates against.

Tests cover the store (mint/verify/multi-use/expiry/distinctness) and the handler (401 with no credentials, 401 with an invalid token, and an authorized token passing the gate through to the no-fetcher 404).

#VS-57

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Closing as superseded.

While this PR was open, VS-60's PR #79 (feat(web): mint scoped agent-binary install token, gate download endpoint, commit c51fbd0) merged into main and implemented the same authorization this PR adds, plus the mint endpoint:

  • install_token::InstallTokenStore (opaque, in-memory, TTL'd, multi-use) - equivalent to this PR's module, with validate() in place of verify() and a 5-minute TTL.
  • agent_binary_download_handler gated before any upstream read: operator vervain_session OR a valid ?token= install token, else 401 with no bytes - the same model this PR documents.
  • GET /agentbinaries/install-token mint route (the VS-60 half this PR deliberately left out).

So main is a superset of this branch; the merge conflict is two near-identical install_token.rs files and duplicate handler gating. There is nothing here to land that main does not already have. All four VS-57 acceptance criteria are satisfied on main by PR #79.

Verified against origin/main HEAD c4f09a4 this turn (git show origin/main:crates/vervain-web/src/lib.rs and install_token.rs).

Closing as superseded. While this PR was open, VS-60's PR #79 (`feat(web): mint scoped agent-binary install token, gate download endpoint`, commit c51fbd0) merged into `main` and implemented the same authorization this PR adds, plus the mint endpoint: - `install_token::InstallTokenStore` (opaque, in-memory, TTL'd, multi-use) - equivalent to this PR's module, with `validate()` in place of `verify()` and a 5-minute TTL. - `agent_binary_download_handler` gated before any upstream read: operator `vervain_session` OR a valid `?token=` install token, else 401 with no bytes - the same model this PR documents. - `GET /agentbinaries/install-token` mint route (the VS-60 half this PR deliberately left out). So `main` is a superset of this branch; the merge conflict is two near-identical `install_token.rs` files and duplicate handler gating. There is nothing here to land that `main` does not already have. All four VS-57 acceptance criteria are satisfied on `main` by PR #79. Verified against `origin/main` HEAD c4f09a4 this turn (`git show origin/main:crates/vervain-web/src/lib.rs` and `install_token.rs`).
David closed this pull request 2026-06-26 20:45:18 +02:00
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 1m36s
Required
Details
Create release / Create release from merged PR (pull_request) Has been skipped

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-server!80
No description provided.