Jasper VanandClaude Fable 5 7bad8d2aea fix: audit must-fixes + product gaps (SSRF, move-quota, multipart, password-reset, trash retention, notifications) (#428)
* fix(downloads): block SSRF targets in remote-download source URL

The remote-download source URI was only length-validated, so an
authenticated editor could point a task at the cloud metadata endpoint,
loopback, or RFC 1918 hosts and have the response exfiltrated to their
own drive. Add a shared isSafeHttpUrl/isBlockedUrlHost guard (scheme
allowlist + private/loopback/link-local/metadata/IPv6 blocking) and
cross-check source type vs uri in createDownloadTaskSchema.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(api): cover 9 untested src/lib/api.ts wrappers

Adds api.test.ts coverage (RPC path, method, payload, success + ApiError
paths) for listObjectsByPath, isNameConflictError, listAdminAuthProviders,
upsertAuthProvider, deleteAuthProvider, listInviteCodes, generateInviteCodes,
deleteInviteCode, and listTeamActivities — satisfying the CLAUDE.md coverage
gate that otherwise blocks PRs touching api.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(spaces): release source quota on cross-space move

A cross-space "move" copied bytes into the target (reserving quota there)
but only trashed the source. Trashed files still count toward usage, so the
moved bytes were billed in both spaces and the source never freed — contrary
to the design doc ("copy + delete source, quota effectively transfers").

Purge the source subtree (independent S3 copy already exists in the target)
instead of trashing it, which deletes the objects, cascades share cleanup,
and reconciles usage. Rename the response field sourceTrashed -> sourceDeleted
and update the move hint copy accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(upload): wire S3 multipart for large files

The upload UI only ever did a single presigned PUT, which caps at S3's
5 GiB limit and fails the whole transfer on any network blip — despite a
complete multipart backend (object-upload-sessions) sitting unused.

Add uploadPartToS3 (PUTs a part, returns its ETag) and a multipart-upload
orchestrator: open session -> presign parts in batches of 100 -> PUT parts
with bounded concurrency and per-part retry -> complete. Files over 100 MiB
take this path; smaller files keep the single-PUT flow. Cancellation aborts
the multipart and the draft. Also fixes the presignObjectUploadParts wrapper
type to match the server's actual `url` field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(auth): add password-reset flow

There was no self-service password recovery — a forgotten password needed
admin intervention. SMTP/email sending was already built; this wires the
last mile: better-auth sendResetPassword (reset email), a "Forgot password?"
link on sign-in, and /forgot-password + /reset-password pages. The
forgot-password page never reveals whether an account exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(trash): auto-purge trashed items past a retention window

Trashed files counted toward quota forever — trash never auto-emptied, so
storage was never reclaimed without a manual "empty trash". Add a daily cron
(CF Workers 0 4 * * * + Node setInterval) that purges trashed items older than
ZPAN_TRASH_RETENTION_DAYS (default 30, 0 disables) across all orgs, reusing the
existing purge path so S3 objects, share references, and quota are all cleaned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(notifications): typed NotificationType, i18n rendering, team-join

Notifications were a bare-string type with only 3 producers, and server copy
was stored as hardcoded English (zh users saw English).

- Add a NotificationType union in shared/ and type the notification service.
- Render notification title/body client-side from type + metadata via i18n,
  falling back to stored strings for older rows (fixes the hardcoded-English gap).
- Notify users when they join a team (team_join).

(Login auditing was intentionally dropped: reusing the activity-events feed for
sign_in events would spam every user's per-org activity timeline. Proper auth
auditing belongs in a dedicated log and can be added separately.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: cover SSRF guard and multipart upload branches

Raise patch coverage on the new code: uploadPartToS3 pre-aborted-signal and
network-error paths, the url-safety octet-overflow and public-IPv6 branches,
and the invalid-magnet rejection in the download-task schema.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:06:25 -04:00
2026-06-01 11:05:56 -04:00
2026-04-14 23:44:12 -04:00
2026-05-06 12:41:02 -04:00

ZPan logo

ZPan

Open-source file hosting for your S3-compatible storage.

Deploy on Cloudflare Workers or Docker. Upload directly to object storage.

CI codecov Release GitHub Release Docker Image License

English · 简体中文 · 日本語 · 한국어 · Русский · Español · Português (BR)

🎉 ZPan v2 is here. To celebrate, we're giving away ZPan Pro three ways — for early stargazers, contributors, and new stars. See v2 Launch Offers.

What is ZPan?

ZPan is a lightweight file hosting platform built on top of S3-compatible storage. Files upload directly from the client to S3 through presigned URLs, bypassing server bandwidth entirely. The server is the control plane: auth, metadata, shares, quotas, teams, WebDAV, tool integrations, and admin operations.

The product boundary is intentional: ZPan is a purpose-built S3-backed web drive, not a wrapper around every consumer cloud drive and not a full groupware suite. You bring an S3-compatible bucket; ZPan gives it a clean web UI, public sharing, image-hosting APIs, and deployment options that do not require a VPS or NAS.

Core scenarios:

  • S3 web drive — Manage files, folders, previews, trash, quotas, and team workspaces on top of your own object storage
  • Image hosting — Upload via PicGo, PicList, uPic, ShareX, Flameshot, or API and get a stable URL instantly
  • File sharing — Publish share links with password, expiration, download limits, direct links, and save-to-drive flows
  • Personal homepage — Give each user a public /u/username page for curated shared files and folder-style browsing
  • External access — Mount files through WebDAV and run downloader workers for remote-download workflows

Why ZPan?

S3 only, by design. ZPan does not chase every net-disk provider or build a cloud-drive nesting layer. The storage contract stays simple and durable: S3-compatible buckets such as Cloudflare R2, AWS S3, Backblaze B2, MinIO, RustFS, Tigris, and other S3-compatible services.

Cloudflare Workers first. ZPan is built around Cloudflare Workers, D1, Hono, and web-standard APIs, with Docker and other runtimes as additional deployment targets. You can run a real file-hosting control plane without owning a VPS, keeping a NAS online, or proxying uploads through a long-running server.

Direct transfer path. Uploads and downloads use presigned object-storage URLs whenever possible. That keeps server bandwidth low, avoids a central file-transfer bottleneck, and lets object storage do the heavy lifting.

Practical file workflows. ZPan includes a web file manager, public sharing, image-hosting configuration, API keys, WebDAV access, teams, quotas, remote-download tasks, file previews, and admin controls without turning into a provider-aggregation platform.

Deployable downloader workers. Remote download does not have to run inside the main ZPan instance. You can deploy the downloader together with ZPan for a simple setup, or run it separately in an environment with better network access and fewer source-site restrictions, then let ZPan import the completed files into object storage.

Product Boundaries

ZPan is a good fit when you want:

  • A focused S3-backed web drive instead of a storage-provider zoo
  • A self-hosted image bed and file-sharing app backed by your own bucket
  • Cloudflare-native deployment without maintaining a VPS or NAS
  • Browser-to-S3 transfers instead of app-server file proxying
  • Tool integrations for screenshot, publishing, WebDAV, remote download, and API-driven workflows

ZPan is not trying to be:

  • A real-time document co-editing suite like Nextcloud Office
  • A general-purpose cloud-drive aggregator like AList
  • A local server directory browser like File Browser

How ZPan Compares

Most self-hosted file projects start from either server files, desktop sync, collaboration, or many-provider aggregation. ZPan starts from S3-compatible object storage and a Cloudflare Workers-friendly control plane.

Capability ZPan Cloudreve AList Nextcloud Seafile File Browser
S3-backed product focus ✅ ❌ ❌ ❌ ❌ ❌
S3-compatible storage backend ✅ ✅ ✅ ✅ ⚠️ ❌
Direct browser-to-object-storage path ✅ ⚠️ ⚠️ ❌ ❌ ❌
Cloudflare Workers deployment ✅ ❌ ❌ ❌ ❌ ❌
No VPS/NAS required ✅ ❌ ❌ ❌ ❌ ❌
PicGo/ShareX image-hosting workflow ✅ ❌ ❌ ❌ ❌ ❌
Per-user public file homepage ✅ ⚠️ ⚠️ ⚠️ ⚠️ ❌
Remote download workflow ✅ ✅ ✅ ❌ ❌ ❌
Separately deployable downloader/node ✅ ✅ ⚠️ ❌ ❌ ❌
Multi net-disk aggregation ❌ ❌ ✅ ⚠️ ❌ ❌
Server local directory as primary file root ❌ ⚠️ ⚠️ ⚠️ ❌ ✅
Real-time document co-editing ❌ ❌ ❌ ✅ ⚠️ ❌
Dedicated sync clients Planned ❌ ❌ ✅ ✅ ❌
Team/workspace model ✅ ⚠️ ❌ ✅ ✅ ❌
WebDAV access ✅ ✅ ✅ ✅ ✅ ❌
Share links ✅ ✅ ✅ ✅ ✅ ✅
Docker deployment ✅ ✅ ✅ ✅ ✅ ✅

Legend: ✅ first-class or core capability; ⚠️ partial, edition-dependent, or not the product's main focus; ❌ not a core capability.

Deploy

Deploy via GitHub Actions with zero server management. Free tier covers personal use.

  1. Fork this repository
  2. In your fork, go to Settings → Secrets and variables → Actions and add:
    • CLOUDFLARE_ACCOUNT_ID — found on the Cloudflare dashboard sidebar
    • CLOUDFLARE_API_TOKEN — create one here with Workers Scripts:Edit, D1:Edit, and R2 Storage:Edit permissions (R2 scope is needed to auto-provision the avatar/logo bucket)
  3. Go to the Actions tab, select Deploy to Cloudflare Workers, and click Run workflow

After initial setup, the workflow runs automatically every time you sync your fork with the latest release.

AWS Lambda

Deploy via GitHub Actions using SAM. Lambda Function URL provides HTTPS with no API Gateway needed.

  1. Fork this repository
  2. In your fork, go to Settings → Secrets and variables → Actions and add:
    • TURSO_DATABASE_URL and TURSO_AUTH_TOKEN — from Turso (free, no credit card)
    • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
  3. Go to the Actions tab, select Deploy to AWS Lambda, and click Run workflow

See docs/deploy/aws-lambda.md for full setup instructions and IAM permissions.

Docker

Quick start — pull the pre-built image and bring your own S3 storage:

curl -O https://raw.githubusercontent.com/saltbo/zpan/main/deploy/docker-compose.yml
docker compose up -d

With RustFS (self-hosted S3-compatible storage, no external dependencies):

curl -O https://raw.githubusercontent.com/saltbo/zpan/main/deploy/docker-compose.rustfs.yml
docker compose -f docker-compose.rustfs.yml up -d

After startup:

  1. Open the RustFS console at http://localhost:9001 (admin / admin123) and create a bucket (e.g. zpan-bucket)
  2. Open ZPan at http://localhost:8222, register a user (first user gets admin role)
  3. Go to Admin → Storage and add the RustFS storage:
    • Endpoint: http://localhost:9000 (must be reachable from your browser, not the Docker internal hostname)
    • Bucket: the bucket name you created in step 1
    • Region: us-east-1
    • Access Key / Secret Key: admin / admin123

Important: The storage endpoint must be accessible from the client browser, since files upload directly to S3 via presigned URLs. Use http://localhost:9000 for local development, or your server's public URL for production.

Documentation

v1

Looking for ZPan v1 (Go version)? See the v1 branch.

Contributing

See CONTRIBUTING.md for details.

Thank you to all the people who contributed to ZPan!

License

ZPan is under the GNU Affero General Public License v3.0. See the LICENSE file for details.

S
Description
A self-hosted cloud disk base on the cloud storage./ 一个基于云存储的网盘系统,用于自建私人网盘或企业网盘。
Readme AGPL-3.0
70 MiB
Languages
TypeScript 92.7%
Go 6.5%
JavaScript 0.5%
CSS 0.1%