diff --git a/CLAUDE.md b/CLAUDE.md index 21520a05..987dbea2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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.0–v2.9) - [docs/roadmap/](docs/roadmap/) — per-version technical specs (v2.0.md–v2.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) diff --git a/V2_ROADMAP.md b/V2_ROADMAP.md index 495aa65c..3fcc8639 100644 --- a/V2_ROADMAP.md +++ b/V2_ROADMAP.md @@ -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 1–2 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 diff --git a/docs/design/agent-authentication.md b/docs/design/agent-authentication.md new file mode 100644 index 00000000..71811302 --- /dev/null +++ b/docs/design/agent-authentication.md @@ -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 + 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 +: +``` + +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 diff --git a/docs/roadmap/v2.9.md b/docs/roadmap/v2.9.md index 2533fde1..2f78518d 100644 --- a/docs/roadmap/v2.9.md +++ b/docs/roadmap/v2.9.md @@ -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 `:` 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