docs(v2.9): define agent access architecture

This commit is contained in:
saltbo
2026-07-28 20:08:03 -04:00
parent c66590a51b
commit ddb87f4901
4 changed files with 913 additions and 113 deletions
+1
View File
@@ -19,6 +19,7 @@ Core architecture: clients upload directly to S3-compatible storage via presigne
- [docs/architecture.md](docs/architecture.md) — system architecture, tech decisions, platform abstraction
- [docs/design/admin-form-ui.md](docs/design/admin-form-ui.md) — admin form layout, density, required/help/placeholder rules
- [docs/design/upload-policies.md](docs/design/upload-policies.md) — proposed selector-based upload placement policies for multiple storage backends
- [docs/design/agent-authentication.md](docs/design/agent-authentication.md) — Agent OAuth, CI API keys, protocol-neutral authorization, Restish credential handling, and future Agent Auth compatibility
- [V2_ROADMAP.md](V2_ROADMAP.md) — product positioning, release plan (v2.0v2.9)
- [docs/roadmap/](docs/roadmap/) — per-version technical specs (v2.0.mdv2.9.md)
- [docs/design/spaces-quota-sharing.md](docs/design/spaces-quota-sharing.md) — spaces/quota/sharing design decisions (team billing, allocation, cross-space transfer, no per-item ACL)
+3 -3
View File
@@ -11,7 +11,7 @@ Three scenarios, one platform:
- **Image Hosting** — Upload via PicGo / uPic / ShareX / API, get a permanent URL on your own domain
- **File Sharing** — Upload, generate a link, distribute
- **Automation & Sync** — Agent-friendly CLI for scripted file management, plus a server-side sync protocol for future desktop clients
- **Automation & Sync** — Agent-friendly OpenAPI + Restish + upload plugin + Skill workflows for scripted file management, plus a server-side sync protocol for future desktop clients
## Target Users
@@ -37,7 +37,7 @@ Each version ships 12 major features. Ship small, ship often.
| [v2.6](docs/roadmap/v2.6.md) | **Pro / Business Launch** | Cloud binding, entitlement system, white-label, audit log, quota store machinery, site announcements, retroactive gates |
| [v2.7](docs/roadmap/v2.7.md) | **WebDAV & File Processing** | WebDAV protocol access, small-file zip compression/extraction, and Community remote-download orchestration through Aria2, qBittorrent, and future adapters |
| [v2.8](docs/roadmap/v2.8.md) | **Admin Analytics & Dashboard** | Admin overview, usage/cost/reliability metrics, share analytics, and offline result coverage |
| [v2.9](docs/roadmap/v2.9.md) | **Agent CLI** | Scriptable CLI for agents and CI: upload/manage files, shares, spaces, quota, and tasks |
| [v2.9](docs/roadmap/v2.9.md) | **Agent Access** | Unified OpenAPI + Restish + upload plugin + Skill, protocol-neutral authorization, delegated OAuth, and workspace-scoped CI keys |
| [v2.10](docs/roadmap/v2.10.md) | **Desktop Sync Foundation** | Sync device model, change log, sync API contract, conflict model, and protocol tests for external clients |
| Future | **Native Client Projects** | macOS File Provider, Windows/Linux sync clients, Flutter/mobile clients, and other OS-specific apps in separate repositories |
| Future | **Enterprise Identity & Governance** | SAML, LDAP / SCIM, advanced RBAC/custom roles, retention, and admin support mode if demand proves real |
@@ -76,7 +76,7 @@ Everything an individual, family, or small team needs to self-host:
- One personal workspace plus one extra team workspace, shared folders, and basic roles
- Small-file zip compression and extraction within local runtime limits
- Remote-download orchestration through one user-configured downloader, with Aria2, qBittorrent, and future compatible adapters
- Agent-friendly CLI once v2.9 ships
- Agent-friendly OpenAPI + Restish + upload plugin + Skill workflows once v2.9 ships
- Desktop sync protocol foundation once v2.10 ships; actual native clients live in separate projects
- All 7 deployment targets
+574
View File
@@ -0,0 +1,574 @@
# Agent Authentication and Authorization — Design
> Status: Proposed (2026-07-28)
> Scope: Agent OAuth, API keys, workspace grants, protocol-neutral
> authorization, Restish profiles, future Agent Auth compatibility, revocation,
> and auditing
## 1. Decision
ZPan distinguishes delegated user access from unattended service access:
| Actor | Authorization flow | Runtime credential |
|-------|--------------------|--------------------|
| Interactive Agent, local callback | Authorization code + PKCE | OAuth access/refresh tokens |
| CI or unattended service | Manual issuance | Workspace-scoped Agent API key |
This follows the current FlareAuth Restish v2 design: standard OpenAPI OAuth
metadata and `x-cli-config` let Restish connect, authorize, cache, refresh, and
revoke local tokens without a custom authorization script.
Standard Agent device authorization is deferred to v2.9.x. The existing
`zpan-cli` device flow remains a narrowly scoped compatibility bootstrap for
downloader registration and does not manufacture an Agent API key or a general
OAuth grant.
Anonymous upload and preview-and-claim are explicitly excluded. Every Agent file
operation belongs to an existing user-authorized workspace from the beginning.
OAuth and API keys are v2.9 credential adapters, not the file API's identity
model. Both resolve to a protocol-neutral principal, scope set, fixed workspace,
and audit actor. A future Agent Auth verifier plugs into that same boundary.
## 2. Why OAuth for Interactive Agents
Interactive Agents act on behalf of a signed-in human. OAuth gives that
relationship first-class semantics:
- short-lived access tokens
- refresh-token rotation and revocation
- explicit client identity
- explicit resource scopes
- browser consent
- authorization code + PKCE for public native clients
- no browser-cookie or raw-token copy/paste
Restish v2 natively supports authorization code + PKCE. It caches OAuth tokens
separately from HTTP responses, refreshes them, retries once after a `401`, and
supports explicit logout.
## 3. Why API Keys Still Exist
CI and unattended services are different: no human is present to complete
consent or periodically reauthorize. The existing Better Auth API-key
foundation already supplies:
- hashed credential storage
- named and independently revocable keys
- expiry and enabled state
- rate-limit state
- resource/action permissions
- workspace scope in metadata
- owning user reference
- per-key audit attribution
An Agent API key is therefore the pragmatic v2.9 service credential. It is
created manually and stored in a CI secret. Future workload identity federation
can replace it without changing the canonical scope and policy model.
## 4. Stable Upgrade Boundary
ZPan separates four concepts:
| Concept | Responsibility |
|---------|----------------|
| Credential adapter | Validate OAuth, API key, or future Agent JWT |
| Principal | Identify the authorizing user, credential actor, and fixed workspace |
| Scope and policy authorization | Intersect credential scopes with current workspace authority |
| Use case | Perform the file operation without knowing the credential protocol |
Conceptually, an Agent-facing principal contains:
```ts
type AgentPrincipal = {
kind: 'delegated-user' | 'service' | 'agent'
userId: string
orgId: string
scopes: ReadonlySet<Scope>
actor: {
type: 'agent_oauth' | 'api_key' | 'agent'
id: string
}
}
```
The exact TypeScript representation may remain a discriminated union, but
routes must authorize scopes rather than require a concrete `kind`.
Credential-specific fields remain available for diagnostics and revocation;
they do not select business behavior.
This boundary deliberately avoids two migration traps:
- File routes must not treat an OAuth bearer as an unrestricted browser user.
- Agent API keys must not become ZPan's proprietary Agent identity,
registration, signing, or capability-grant protocol.
With this boundary, adopting Agent Auth later adds a verifier, persistence,
approval UI, and management UI. It does not change operation IDs, the unified
OpenAPI document, Skill/plugin workflows, workspace authorization, or file use
cases.
## 5. System-Managed OAuth Client
Create a built-in public native application such as `zpan-agent`.
Properties:
- system-managed and not editable/deletable
- public client; no client secret
- authorization code grant with PKCE
- loopback redirect URI such as `http://localhost:8484/callback`
- refresh-token support through `offline_access`
- Agent scopes only
Dynamic client registration is not required in v2.9. One first-party client is
enough for the versioned ZPan Skill and Restish integration.
The authorization server publishes discovery metadata. Clients must discover
authorization, token, revocation, and user-info or introspection endpoints
rather than hard-code them.
## 6. Workspace Grant
OAuth scopes describe allowed operation classes, but a ZPan grant also needs a
resource boundary: exactly one workspace.
The consent record binds:
- authorization/grant ID
- user ID
- OAuth client ID
- workspace `orgId`
- approved scopes
- created, expiry, revoked, and last-used state
Access/refresh tokens resolve to that grant. The API does not derive workspace
from the user's mutable active-organization session.
Effective authorization is:
```text
credential is valid
AND grant/key allows the requested action
AND request targets the bound workspace
AND authorizing user still has the required workspace role
```
For a team workspace, relevant requests recheck current membership and role.
Removing the user or reducing their role immediately reduces Agent access.
For a personal workspace, authorization verifies that the organization is the
authorizing user's personal organization. The current API-key branch in
`requirePermission` lacks this personal-ownership fallback and must add it.
Request bodies and query parameters cannot override the credential's workspace.
A mismatch is `403`, never a fallback to another active or personal workspace.
## 7. Scope Model
ZPan defines one canonical authorization vocabulary for scoped credentials.
OAuth grants, Agent API keys, and future Agent credentials resolve to the same
scope set. A browser cookie is a first-party, unbounded credential: it does not
need a role-to-scope mapping, but it still passes the route's declared
workspace, minimum-role, ownership, and resource policies. There is no
separately named permission vocabulary and no `Scope -> Permission` mapping.
Scope names follow:
```text
<resource>:<action>
```
Rules:
- lowercase ASCII only;
- plural domain resource names such as `objects`, `shares`, and `tasks`;
- a small shared action vocabulary such as `read`, `create`, `update`, and
`delete`;
- business operations rather than HTTP methods;
- no wildcard semantics or access implied by string prefixes;
- no `zpan:` prefix, because token issuer and audience already identify the
ZPan API;
- published scope meanings are stable and must never silently broaden.
Initial Agent-grantable scopes are:
| Scope | Intended operations |
|-------|---------------------|
| `objects:read` | List, inspect, and download objects |
| `objects:create` | Create folders, upload drafts, upload-part signatures, and complete uploads |
| `objects:update` | Rename, move, and copy objects within the authorized workspace |
| `objects:delete` | Soft-delete objects |
| `shares:read` | List and inspect shares |
| `shares:create` | Create public shares |
| `shares:delete` | Revoke shares |
| `quota:read` | Inspect workspace quota |
| `tasks:read` | Inspect task state |
Protocol scopes such as `openid` and `offline_access` retain their standard
OAuth/OIDC meaning. They are not ZPan route permissions.
Every protected route declares the minimum scopes required to perform its
operation. It does not enumerate the roles, presets, credential types, broad
scopes, or Agent classes allowed to call it. For example:
```ts
auth: {
allOf: ['objects:delete'],
workspace: 'required',
}
```
Scope authorization is necessary but not sufficient. Workspace membership,
resource ownership, resource state, quota, and other request-specific
constraints remain explicit policy checks.
The consent and API-key UIs can present Reader, File manager, and Publisher
shortcuts. A shortcut expands to an explicit set of scopes; it is not itself a
scope, and routes never reference its name. Destructive and public-sharing
scopes remain separately selectable.
No Agent-grantable scope implies admin, billing, entitlement, membership,
credential management, WebDAV, image-hosting configuration, or downloader
registration. Those protected APIs still use the same route scope mechanism but
are excluded from the Agent credential grant policy.
## 8. Authorization Code + PKCE
This is the default Restish flow:
1. Skill identifies and confirms the ZPan origin and Restish API name.
2. Skill requires Restish v2.
3. `restish api connect` discovers `/api/openapi.json` and applies its
server-published OAuth binding.
4. The first safe Agent operation starts browser authorization.
5. Restish creates a PKCE verifier/challenge and listens on its loopback
callback.
6. User signs in, selects one workspace, reviews scopes, and approves or denies.
7. Restish exchanges the authorization code and caches the tokens.
8. Later commands refresh tokens without exposing them to the Agent response.
The consent page displays the Agent client, instance hostname, workspace,
requested scopes, destructive/public side effects, and grant lifetime.
Restish's `--rsh-no-browser` may be used when a browser cannot be opened but the
authorization-code callback can still be completed manually.
## 9. Deferred Agent Device Authorization
Standard Agent device authorization is a v2.9.x follow-up. It must issue tokens
for the same workspace grant and scope model as authorization code + PKCE, not a
broad Better Auth session token. The existing Better Auth device plugin remains
restricted to the legacy `zpan-cli` downloader bootstrap until that follow-up.
## 10. Agent API-Key Issuance
Manual API-key creation is the initial CI path:
1. User opens Agent Access settings.
2. User selects a workspace.
3. User names the Agent or environment.
4. User selects permissions and expiry.
5. Server verifies current authority and creates an `agent` API key.
6. The plaintext key is shown once.
New Agent keys never use `scope.mode = "user-workspaces"`. One key authorizes one
workspace. Expiry is required, defaults to 90 days, and cannot exceed one year.
Use one key per CI environment.
The UI lists name, workspace, permission summary, creation, expiry, last use,
and status. Revocation is immediate. Rotation creates a new key and never
reveals or mutates the old secret.
## 11. OpenAPI and Restish v2 Binding
ZPan publishes one unified `/api/openapi.json`. It defines:
- relative server URL for Agent API routes
- OAuth authorization-code security scheme with Agent scopes
- Bearer alternative for Agent API keys
- stable operation IDs and structured errors
- document-level Restish v2 `x-cli-config` profiles
Conceptual configuration:
```yaml
components:
securitySchemes:
agentOAuth2:
type: oauth2
flows:
authorizationCode:
authorizationUrl: /api/auth/oauth2/authorize
tokenUrl: /api/auth/oauth2/token
scopes:
objects:read: Read files and folders
objects:create: Upload files and create folders
objects:update: Rename, move, and copy files and folders
objects:delete: Delete files and folders
agentApiKey:
type: http
scheme: bearer
x-cli-config:
profiles:
default:
credentials:
agentOAuth2:
params:
client_id: zpan-agent
scopes: openid offline_access objects:read quota:read
redirect_path: /callback
file-manager:
credentials:
agentOAuth2:
params:
client_id: zpan-agent
scopes: openid offline_access objects:read objects:create objects:update objects:delete quota:read tasks:read
redirect_path: /callback
```
The real document also provides a Publisher shortcut. Reader is the default, so
connecting the API does not silently request write or share permission. These
profile names only expand to explicit scopes; routes never reference them.
A separate environment-backed profile selects `agentApiKey` for CI. No Agent
device-code profile is published in v2.9.
The OpenAPI document never contains credentials or configures an executable
credential helper. Skill instructions select a named Restish profile rather
than assuming OAuth or a particular environment-variable name. This keeps
operation workflows unchanged if a future local profile uses an Agent Auth
signer.
All formal API operations remain visible to Restish CLI generation. Declared
scopes and dynamic policy decide whether a credential may call them. Only
browser callbacks and internal-only endpoints are hidden from CLI generation.
MCP additionally ignores authentication, administration, and credential
management operations and keeps write tools disabled by default. ZPan does not
maintain a second Agent operation allowlist.
## 12. Restish Upload Plugin
`restish-zpan` is a Restish v2 command plugin shipped from this repository. It
contributes `restish zpan-upload` and is installed with:
```sh
restish plugin install saltbo/zpan zpan
```
The plugin uses Restish delegated HTTP for ZPan draft, part re-sign, complete,
and abort operations, preserving the selected profile, OAuth/API-key
authentication, TLS, and normalized output. It streams local file sections
directly to presigned S3 URLs with bounded concurrency, retry, ETag capture,
resume checkpoints, and idempotent completion.
The plugin never asks Restish for authentication secrets. Checkpoints contain
only safe API/profile identity, upload session and file identity, and completed
part/ETag state; they contain no token, cookie, API key, or presigned URL. The
Skill invokes this command and never implements multipart state itself.
## 13. Route Authorization
Both credential types enter a shared Agent authorization boundary.
For OAuth:
1. validate/introspect the access token;
2. require the built-in Agent client ID and the route's required scopes;
3. resolve user and fixed workspace grant;
4. recheck current workspace authority.
For API keys:
1. verify key, expiry, revocation, rate limit, and owner status;
2. require `configId = "agent"` and the route's required scopes;
3. resolve fixed workspace metadata;
4. recheck current workspace authority.
Both then invoke the same use case with the fixed `orgId` and a typed audit
actor. Routes use shared permission middleware instead of session-only or
principal-specific checks. The shared middleware accepts the internal principal
contract, so tests for protected operations do not need to know how the
principal authenticated.
Special considerations:
- A presigned upload URL may remain usable briefly after credential revocation
because S3 validates the signature independently. Keep presigned lifetimes
short.
- Upload completion and new part presigning always reauthorize.
- Issuing a new download URL requires object-read permission.
- Listing and task responses remain workspace-filtered and paginated.
- Share creation requires `shares:create` even when the Agent can read the
object.
## 14. Audit and Management
Audit records distinguish resource ownership from the actor that initiated the
operation. OAuth actions record an `agent_oauth` actor with grant/client
attribution. API-key actions retain `api_key` with the key ID as `actorRef`.
Both record the authorizing user, workspace, action, target, outcome, and safe
metadata. A future Agent Auth adapter records `agent` with its Agent ID while
retaining the delegated user as resource owner.
Agent Access settings show two sections:
- delegated OAuth grants, with client, workspace, scopes, last use, and revoke;
- service API keys, with name, workspace, permissions, expiry, last use, and
revoke/replace.
Revoking a delegated grant invalidates its refresh tokens and prevents new
access tokens. Short access-token lifetime bounds any validation-cache delay.
`restish api auth logout` clears local cached tokens; server-side revoke remains
available when a device is lost.
Credentials are never recorded or redisplayed.
## 15. Current Code Gaps
- ZPan has bearer sessions and device authorization but is not yet an OAuth
authorization server with Agent resource scopes and workspace grants.
- Device authorization validates only `zpan-cli` and currently yields a
user-oriented bearer token.
- `shared/api-key-templates.ts` lacks an Agent template.
- `server/http/objects.ts` rejects ordinary API-key principals.
- authenticated shares, quota, trash, and several task routes require a user
session instead of a permission.
- the current principal model and `requireAuth` helper encourage routes to
branch on identity kind; all protected routes need shared scope declarations
and a protocol-neutral authorization boundary.
- API-key authorization needs the personal-workspace ownership check.
- the unified OpenAPI document lacks operation security and CLI/MCP annotations;
- the current upload contract lacks explicit part descriptors, robust re-sign,
expiry, idempotent completion, and a Restish command plugin.
These authorization-boundary changes require integration tests for OAuth and
API-key success, missing scope/permission, wrong workspace, wrong client, role
reduction, expiry, revocation, and personal/team spaces.
## 16. Agent Auth Protocol Compatibility
The [Agent Auth Protocol](https://agentauthprotocol.com/) is a strong long-term
fit because it gives every Agent a cryptographic identity, scoped capability
grants, an independent lifecycle, and per-Agent audit attribution. The
[Better Auth Agent Auth plugin](https://better-auth.com/docs/plugins/agent-auth)
also provides discovery, device/CIBA approval, short-lived signed JWTs, replay
protection, OpenAPI/MCP adapters, and lifecycle events.
It is not the required v2.9 production path:
- the protocol is currently `v1.0-draft`, and the plugin documentation marks
the implementation as unstable;
- Restish does not natively implement Agent Auth request signing;
- production Cloudflare Workers need distributed JTI replay storage rather than
the plugin's default in-memory cache;
- custom REST `location` handlers must validate grants and constraints in the
shared authorization layer;
- converting the full ZPan OpenAPI document into capabilities would expose too
much surface.
The intended future adapter is:
```text
Agent Auth JWT
-> verify signature, audience, expiry, and JTI
-> resolve delegated user and approved workspace
-> normalize capability grants to the canonical Scope set
-> create protocol-neutral principal and `agent` audit actor
-> run existing scope and policy middleware and use case
```
The effective permission remains:
```text
Agent Auth capability grant
AND authorizing user's current workspace role
AND request targets the approved workspace
AND resource-specific policy allows the operation
```
Expected change surface:
| Remains unchanged | Added for Agent Auth |
|-------------------|----------------------|
| Unified OpenAPI and operation IDs | Agent/host/grant/approval persistence |
| ZPan Skill and upload-plugin workflows | Agent JWT credential adapter |
| File, share, quota, and task use cases | Approval and Agent-management UI |
| Route scope requirements and workspace policies | Distributed JTI replay storage |
| Presigned direct-to-S3 upload sequence | Restish signing profile/helper |
Agent Auth does not replace role, quota, storage, share, or ownership checks.
Autonomous/anonymous Agent registration and later claim are outside the current
product boundary; an initial integration supports delegated Agents only.
Restish remains the operation client. Until it supports Agent Auth natively, a
future profile may use its
[external-tool authentication](https://rest.sh/docs/recipes/use-external-tool-auth/)
to invoke the official Agent Auth client or a minimal reviewed signer. This is
an authentication adapter, not a standalone ZPan CLI. The unified OpenAPI
operations and Skill workflows remain unchanged.
Before promotion from preview to the default interactive flow, require:
- a maintained Restish signing integration or native Agent Auth support;
- distributed JTI replay protection on Workers and an equivalent Node path;
- cross-runtime tests for registration, approval, execution, replay, revoke,
role reduction, and workspace isolation;
- an explicit Agent-grantable scope catalog rather than automatic authorization
for every operation in the unified OpenAPI document;
- acceptable upstream protocol and package stability.
## 17. Rejected Alternatives
### Device Approval Mints an API Key
Rejected because device approval must eventually issue the same delegated OAuth
grant as authorization code + PKCE. Minting an API key would replace that
short-lived and refreshable lifecycle with a proprietary exchange.
### API Key for Every Agent
Rejected because interactive user delegation benefits from consent, short access
tokens, refresh-token revocation, and client identity. API keys remain
appropriate for CI and unattended services.
### OAuth for CI by Pretending a User Is Present
Rejected because unattended automation should not depend on a human refresh
grant. Use a scoped API key until workload identity federation is available.
### Agent Auth as the Only v2.9 Credential
Deferred because the protocol and current plugin remain unstable and Restish
needs an external signer. The compatibility boundary is included now;
production adoption can follow without making v2.9 depend on a draft protocol.
### Browser Cookies
Rejected because they are broad, mutable user-session credentials and unsafe to
copy into Agent environments.
### One Credential Across All User Workspaces
Rejected because it makes compromise impact, audit interpretation, role changes,
and revocation unnecessarily broad.
### Anonymous Upload and Claim
Deferred outside v2.9. File storage normally implies persistence and an
accountable quota owner. Revisit only if ZPan deliberately builds a
try-before-login artifact-delivery product.
## 18. Future Evolution
- Better Auth Agent Auth compatibility adapter, initially behind a feature flag
- Delegated Agent approval and per-Agent revoke/management UI
- Distributed JTI and Agent-key cache storage for Cloudflare Workers
- Workload identity federation for supported CI providers
- Dynamic client registration for trusted third-party Agent platforms
- Standard Agent device authorization using the same workspace grant and scopes
- Rich Authorization Requests if third-party clients need standardized
workspace selection in the authorization request
- HTTP Message Signatures / Web Bot Auth for additional Agent-operator
attribution, never workspace authorization
+335 -110
View File
@@ -1,159 +1,384 @@
# v2.9 — Agent CLI
# v2.9 — Agent Access
Ship ZPan's command-line tool as an automation client for agents, scripts, and
CI/CD. This CLI is not the foundation for desktop sync. It is a separate client
surface optimized for reliable command execution and machine-readable output.
Make ZPan operable by coding agents, scripts, and CI without maintaining a
separate ZPan CLI. v2.9 publishes a stable Agent-facing API contract and teaches
agents to use it through Restish v2 plus a ZPan Skill.
The authentication decision follows the current FlareAuth + Restish v2 pattern:
- **Interactive Agent:** OAuth authorization code + PKCE.
- **Unattended automation:** workspace-scoped Agent API key.
Standard Agent device authorization is deferred to v2.9.x. The existing
`zpan-cli` device flow remains only as a compatibility bootstrap for downloader
registration and does not issue a general Agent credential.
OAuth grants and API keys are separate because they represent different actors:
delegated user access versus a service credential. An Agent never receives a
browser cookie or an unrestricted user session. Anonymous uploads, provisional
workspaces, and claim flows are not part of v2.9.
The file API does not depend on either credential format. Both flows resolve to
a protocol-neutral principal and the same scope and workspace-policy checks. This is
an explicit compatibility boundary for adding Agent Auth Protocol later without
redesigning the OpenAPI operations, Skill, upload plugin, or file use cases.
The detailed model is in
[Agent Authentication and Authorization](../design/agent-authentication.md).
## Product Boundary
- **Community** gets the core CLI for personal and self-hosted automation.
- **Pro / Business** may raise API limits or expose operator-only commands when
the underlying feature is paid-tier gated.
- Desktop sync clients do not call this CLI as their internal engine. They use
sync-specific APIs and OS integrations in their own client projects.
- **Community** gets the unified OpenAPI contract, interactive OAuth, Agent API
keys, Restish setup, the ZPan upload plugin, and the ZPan Skill.
- **Pro / Business** may raise API and automation limits, but paid tiers do not
gate ordinary authenticated file automation.
- Desktop sync clients do not use Restish as their engine. They continue to use
sync-specific APIs and OS integrations in their own projects.
## Why CLI Here
## Why Restish + Skill
Agents and automation need stable verbs:
Restish v2 already generates commands from OpenAPI, supports OAuth and API-key
profiles, caches and refreshes OAuth tokens, emits machine-readable output, and
provides retries and pagination. Its official MCP plugin can expose an API as
tools, with write operations disabled by default.
- upload a file
- list folders
- create a folder
- move / copy / delete objects
- create a share link
- download an object
- inspect quota and task state
ZPan therefore owns:
These map naturally to shell commands with JSON output. They do not require a
filesystem-provider integration, long-running sync daemon, or platform-specific
desktop packaging.
- one unified OpenAPI document with operation-level scope declarations
- server-published Restish v2 authentication bindings
- a `restish-zpan` command plugin for streaming and resumable multipart uploads
- a ZPan Skill that explains safe workflows and selects the right Restish surface
- the server-side authentication and authorization contract
## Major Features
The plugin is a narrow Restish extension, not a standalone ZPan CLI. Restish
continues to own command parsing for generated API operations, profiles,
credential storage, authentication, and output formatting.
### Command Surface
Baseline Restish version: **v2.3 or later**. v2.3 is selected because it includes
the current plugin system, OAuth flows, OpenAPI credential binding, and official
MCP integration. Restish's embedding API can build a branded CLI, but that
option is deliberately not used.
Initial command groups:
References:
```sh
zpan auth login
zpan auth status
zpan auth logout
- [OpenAPI CLI integration](https://rest.sh/docs/reference/openapi-cli-integration/)
- [Authentication](https://rest.sh/docs/guides/authentication/)
- [Automation](https://rest.sh/docs/guides/automation/)
- [MCP plugin](https://rest.sh/docs/plugins/mcp/)
- [Command plugins](https://rest.sh/docs/plugins/command-plugins/)
zpan file ls
zpan file upload
zpan file download
zpan file mkdir
zpan file mv
zpan file cp
zpan file rm
zpan file stat
## Deliverables
zpan share create
zpan share list
zpan share revoke
### Protocol-Neutral Authorization Boundary
zpan space list
zpan space use
zpan quota get
zpan task list
zpan task watch
Authentication adapters resolve credentials into an internal principal before
any file, share, quota, or task authorization:
```text
Browser session ────┐
OAuth access token ─┼─> principal + actor + granted scopes ─> scope + policy check
Agent API key ──────┘
```
Command names should be predictable and script-friendly. Prefer explicit flags
over interactive prompts, with optional prompts only for humans running the tool
manually.
Every protected route declares the minimum scopes required for its operation,
such as `objects:read`, `objects:create`, and `shares:create`. Routes do not
declare which credential types, roles, presets, or Agent classes may call them.
Authentication adapters normalize browser sessions, OAuth tokens, API keys, and
future Agent credentials into the same authorization context. Each adapter
supplies:
### Agent-Friendly Output
- the authorizing `userId`
- the applicable workspace boundary, including one fixed `orgId` for Agent
grants and Agent API keys
- the granted scopes
- a typed actor for audit attribution
- credential expiry and revocation state
The CLI must be easy for coding agents and MCP-style tools to call.
Effective permission is always the intersection of granted scopes, the
authorizing user's current workspace role, and resource-specific rules. Adding
a future `agent-jwt` adapter must therefore require no changes to route scope
requirements, file use cases, or operation IDs.
- `--json` for every command that returns data
- Stable exit codes
- Errors printed as structured JSON when `--json` is active
- No progress bars in non-TTY mode
- `--quiet` and `--no-color`
- Idempotency keys for commands that create or mutate resources where useful
- Clear stderr/stdout separation
### Unified OpenAPI
### Authentication
Publish only `/api/openapi.json`. Every formal operation remains available to
Restish command generation; declared scopes and dynamic policy determine
whether a credential may invoke it. Browser callbacks and internal-only
endpoints are excluded from CLI generation. MCP additionally ignores
authentication, administration, and credential-management operations and keeps
write tools disabled by default.
Supported auth paths:
Every protected operation declares its scope and workspace/role policy at the
route. The same declaration drives runtime enforcement and OpenAPI security, so
there is no second Agent operation allowlist to drift. Every machine-facing
operation needs a stable `operationId`, bounded pagination, documented
idempotency, structured errors, and suitable examples.
- Personal API key for simple automation
- Device authorization flow for human bootstrap
- Environment variable token for CI/agent runtime use
- Config profiles for multiple ZPan instances
The document declares both allowed authentication alternatives:
The CLI should never ask users to paste browser cookies.
- OAuth 2.0 authorization code with PKCE and Agent resource scopes
- HTTP Bearer authentication for a manually issued Agent API key
### Upload And File Management
It also publishes Restish v2 Reader, File manager, and Publisher convenience
profiles for the built-in public native client. These names expand to explicit
scope sets and are never referenced by routes. Reader is the default; broader
profiles request their scopes explicitly. No secret is embedded in the
document.
The CLI should use the same object upload flow as the browser:
The existing document currently exposes roughly 145 operation IDs without
operation security or `x-mcp-ignore` annotations. v2.9 adds those declarations
without creating `/api/openapi.agent.json`.
1. Ask ZPan to create an upload draft and presigned instructions.
2. Upload bytes directly to object storage.
3. Complete the upload through ZPan.
### ZPan Skill
It should support:
Publish a versioned Skill that:
- single-file upload
- recursive directory upload as an explicit command mode
- conflict strategies: fail, keep both, replace
- target space and folder selection
- resumable multipart upload where the server already supports it
1. detects the ZPan origin;
2. verifies Restish v2 and connects `/api/openapi.json` with
`--replace --yes`;
3. selects the least-privilege Reader, File manager, or Publisher profile;
4. syncs a previously connected API before use;
5. triggers browser OAuth on the first safe request when a local callback is
available;
6. verifies and, after explicit trust confirmation, installs the
`restish-zpan` plugin;
7. uses an environment-backed Agent API-key profile for CI;
8. sends every local file upload through `restish zpan-upload`;
9. confirms target workspace, destructive operations, overwrite behavior, and
public sharing;
10. returns object IDs, URLs, quota effects, and task state in a compact
machine-readable result.
This is file management automation, not backup/sync semantics. Local deletions
must not imply remote deletions unless the user runs an explicit delete command.
The Skill never asks the user to paste a bearer token. Restish owns OAuth token
storage, refresh, logout, and redacted authentication diagnostics. Skill
workflows refer to a selected ZPan Restish profile rather than assuming a
specific environment variable or credential type, so a future profile may use
an external Agent Auth signer without changing the file-operation instructions.
### Packaging
### Restish Upload Plugin
The implementation language should be chosen during task planning. The product
contract is more important than the runtime.
Ship `restish-zpan` from this repository and install it with:
Packaging requirements:
```sh
restish plugin install saltbo/zpan zpan
```
- macOS, Linux, Windows
- installable in CI
- easy for agents to locate and execute
- versioned independently enough that agent tooling can pin a known CLI version
It contributes `restish zpan-upload`. The plugin uses Restish delegated HTTP
for ZPan draft, re-sign, complete, and abort operations, preserving the selected
profile, OAuth/API-key authentication, TLS, and output behavior. It streams
local file parts directly to presigned S3 URLs with bounded concurrency, retry,
ETag capture, resume checkpoints, and idempotent completion. File bytes and
presigned URLs never pass through the Agent context or Restish's plugin CBOR
channel.
If implemented in this repository, keep the CLI isolated from the web frontend
and server runtime so it does not complicate normal ZPan deployment.
The first release supports single and multipart files, re-signing expired
parts, interrupted resume, and explicit abort. Checkpoints contain no
credentials or presigned URLs and are removed after success. The Skill invokes
the plugin; it does not implement the upload state machine itself.
### OpenAPI Contract
The official `restish-mcp` plugin is an optional transport for ordinary API
operations, not the upload implementation. Its default read-only mode is useful
for browsing. Enable write tools only with an explicit operation allowlist:
The CLI should use the public REST/OpenAPI surface, not private frontend helpers.
If an existing route is awkward for CLI use, improve the API contract rather than
special-casing the CLI.
```sh
restish plugin install rest-sh/restish mcp
restish mcp serve zpan --allow-write-tools \
--operations listObjects,getObject,createObject,completeObjectUpload
```
Frontend code continues to use Hono RPC wrappers.
Keep MCP results bounded; the plugin's default result limit is 16 KiB. Object
contents continue to move through presigned URLs, never through an MCP result.
### Interactive OAuth
Create a system-managed public native client, for example `zpan-agent`, with:
- authorization code + PKCE
- loopback callback such as `http://localhost:8484/callback`
- refresh-token support through `offline_access`
- only Agent API scopes
The default Restish profile uses authorization code + PKCE. After
`restish api connect`, the first safe Agent API request opens browser consent;
Restish caches and refreshes the resulting tokens. `--rsh-no-browser` may be
used when the callback can still be completed manually. Standard Agent device
authorization remains a v2.9.x follow-up.
Consent binds the grant to:
- the signed-in user
- exactly one workspace
- requested resource scopes
- the user's current workspace role
- the OAuth client and expiry/revocation state
Access tokens are short-lived. Refresh tokens remain bounded by that grant.
Changing team membership or revoking the grant removes access independently of
the user's browser sessions.
### Agent API Keys
Add an `agent` API-key template for CI and other unattended automation.
Defaults:
- exactly one workspace
- least-privilege permissions selected by the user
- explicit name, expiry, last-used time, and revocation
- separate keys for separate Agents and environments
- no admin, billing, membership, entitlement, or credential-management access
- team membership and role rechecked at authorization boundaries
- rate and storage limits enforced server-side
The plaintext key is returned once and stored in a CI secret or another
non-interactive secret store.
### Scopes and Presets
The server has one canonical authorization vocabulary. OAuth grants, API keys,
browser-session roles, and future Agent credentials all produce a set of the
same scopes; there is no separate `Scope -> Permission` mapping.
Scope names use the stable, lowercase `<resource>:<action>` form. Resources are
plural domain nouns and actions come from a small shared vocabulary such as
`read`, `create`, `update`, and `delete`. Scope names describe business
authority, not HTTP methods. They do not include a redundant `zpan:` prefix:
the token issuer and audience already identify the ZPan API.
Initial Agent-grantable scopes are:
| Scope | Intended operations |
|-------|---------------------|
| `objects:read` | List, inspect, and download objects |
| `objects:create` | Upload files and create folders |
| `objects:update` | Rename, move, and copy objects |
| `objects:delete` | Soft-delete objects |
| `shares:read` | List and inspect shares |
| `shares:create` | Create public shares |
| `shares:delete` | Revoke shares |
| `quota:read` | Inspect workspace quota |
| `tasks:read` | Inspect task state |
Protocol scopes such as `openid` and `offline_access` retain their standard
OAuth/OIDC meaning. They are not ZPan route permissions.
Each protected route declares its minimum required scopes. A route declares
what authority the operation needs, not a list of broad scopes or caller types
that are allowed to invoke it. Workspace membership, resource ownership,
resource state, quota, and other dynamic constraints remain policy checks after
the scope check.
Reader, File manager, and Publisher are UI/Restish shortcuts that expand to
explicit scope sets. They are not scopes and routes never reference preset names.
Destructive and public-sharing scopes remain separately selectable.
## Current Gaps to Close
- ZPan is not currently an OAuth authorization server for Agent resource
scopes.
- The existing device authorization plugin returns a user-oriented bearer token
and validates only the legacy `zpan-cli` client ID.
- Object routes currently reject ordinary API-key principals with a blanket
session-only gate.
- Authenticated share and quota routes currently require a user session.
- Protected routes need shared scope middleware that accepts a protocol-neutral
principal instead of branching on `user`, `api-key`, or another credential
kind. Every protected route must declare its minimum scopes.
- API-key templates currently cover image hosting, WebDAV, and remote download,
but not general Agent file management.
- Workspace API-key authorization needs an explicit personal-space ownership
path in addition to team membership checks.
- The unified OpenAPI document lacks operation security and CLI/MCP annotations.
- Upload conflict policy, idempotency, and multipart retry behavior need an
explicit public contract.
## Delivery Order
1. Define the canonical scope vocabulary and scope declaration metadata.
2. Refactor all protected routes to authorize protocol-neutral principals,
declare minimum scopes, and emit typed actors rather than require a
particular principal kind.
3. Add manual Agent API keys and make scoped API access work end to end.
4. Add the system-managed public native OAuth client, Agent grants, and
authorization-code + PKCE flow.
5. Stabilize the server multipart protocol.
6. Publish unified OpenAPI security and Restish v2 credential bindings.
7. Ship and release the `restish-zpan` upload command plugin.
8. Ship the ZPan Skill and validate browser OAuth and CI profiles.
9. Add the optional, allowlisted Restish MCP recipe.
API keys come first as the smallest way to prove the resource authorization
boundary. They do not become the interactive login protocol.
## Non-goals for v2.9
- Desktop sync engine
- Apple File Provider, Windows Cloud Files, Linux filesystem integration
- Flutter, Tauri, Swift, Kotlin, or other graphical clients
- Bidirectional sync or conflict replication
- Enterprise / legacy identity protocols such as LDAP, SCIM, or SAML
- Folder-level ACLs or custom roles
- A standalone `zpan` CLI or branded Restish binary
- Standard Agent device authorization before the v2.9.x follow-up
- Anonymous upload, provisional workspace, preview-and-claim, or anonymous
permanent storage
- Desktop sync or filesystem-provider integration
- Agent access to admin, billing, entitlement, membership, or credential
management
- Giving an Agent a browser cookie or unrestricted user session
- Dynamic third-party OAuth client registration
- Making the draft Agent Auth Protocol or its current Better Auth plugin a
required production dependency
- A ZPan-specific Agent identity, key-signing, capability-grant, or claim
protocol that duplicates a future standards-based integration
- Enterprise identity protocols or custom workspace roles
- Server-proxied file bytes
## Acceptance Criteria
- A Restish v2 client connects to `/api/openapi.json`, triggers browser
OAuth + PKCE, and reuses/refreshes cached tokens without token copy/paste.
- Consent shows and binds one workspace and the requested scopes.
- A CI job can use a manually created workspace-scoped Agent API key.
- Browser-session, OAuth, and API-key principals pass through the same scope and
workspace-policy authorization.
- Every protected route declares its minimum required scopes; no route
authorizes by credential kind, Agent type, or preset name.
- Agent-facing routes and use cases do not branch on OAuth versus API key, and
their authorization tests can supply a protocol-neutral principal directly.
- Attempts to access another workspace or an ungranted operation return `403`
and produce an audit event.
- Removing the grant/key owner's team membership or reducing their role
immediately removes the corresponding Agent access.
- Expired or revoked credentials cannot complete uploads or issue new download
URLs.
- The unified OpenAPI document derives operation security from route declarations.
- `restish zpan-upload` completes and resumes multipart uploads without exposing
file bytes, credentials, or presigned URLs to the Agent.
- Credentials never enter logs, audit metadata, analytics, share URLs, or MCP
results.
## User Scenarios
**Coding agent:**
> The agent uploads a generated artifact, creates a share link, and posts the
> link back to the user without needing browser automation.
**Interactive coding Agent:**
> The Agent connects ZPan through Restish. My browser opens a consent screen
> where I approve one workspace and the requested scopes. Restish stores and
> refreshes the OAuth tokens locally.
**CI release workflow:**
> A GitHub Actions job uploads build artifacts to ZPan and creates a stable share
> link for release notes.
**Power user:**
> I run `zpan file upload ./dist --target /releases/v2.9 --recursive --json` and
> get a machine-readable summary of uploaded files and share URLs.
> I create a File manager API key for the release workspace, store it as a CI
> secret, and rotate it independently of my user sessions.
## Future Work
- MCP server wrapping the CLI or using the same OpenAPI client
- Admin/operator command groups for Business features
- Bulk migration helpers
- Shell completions and package-manager distribution polish
- Evaluate the Better Auth Agent Auth plugin as an optional compatibility layer
once its draft protocol and packages are stable enough for production. Map its
short-lived Agent JWTs and capability grants into the same principal,
workspace, and scope-and-policy boundary defined in v2.9.
- Keep Restish as the operation transport. Until Restish supports Agent Auth
natively, use its reviewed `external-tool` authentication hook with the
official Agent Auth client/signing helper; do not build a standalone ZPan CLI.
- Before enabling Agent Auth on Cloudflare Workers, provide distributed JTI
replay storage and validate revocation across Worker isolates.
- Dynamic client registration for trusted third-party Agent platforms
- Workload identity federation for CI providers to remove stored long-lived
secrets
- HTTP Message Signatures / Web Bot Auth as an additional Agent-operator
authentication signal, never as file authorization by itself
- First-party remote MCP server if Restish's local MCP transport proves
insufficient
- Standard Agent device authorization with the same workspace grant and scopes