BoxBox is built for private homelab use. It can modify real host files, so treat deployment configuration as part of the security boundary.
Before exposing BoxBox beyond your own machine:
- Set
BOXBOX_JWT_SECRETto a long random value. - Set
BOXBOX_USERS_adminto a bcrypt hash or configure a bcrypt hash inconfig.yaml. - Use a reverse proxy with HTTPS.
- Mount only the directories BoxBox needs.
- Use
read_only: truefor backups and sensitive locations. - Do not expose
/host_root; whole-host browsing is intentionally absent from the default deployment. - Keep the default same-origin WebSocket policy or use a narrow
allowed_originslist. - Put the service behind your normal VPN, Tailscale, WireGuard, or trusted reverse proxy access controls when possible.
BoxBox uses JWT access and refresh tokens:
- Access tokens expire after 15 minutes.
- Refresh tokens expire after 7 days and are rotated in an HttpOnly, SameSite=Strict cookie.
- Access tokens stay in browser memory and logout revokes both current tokens.
- Auth endpoints are rate-limited per client IP.
- Repeated failures trigger exponential per-account lockouts.
Generate bcrypt hashes with htpasswd (from apache2-utils on Debian/Ubuntu):
htpasswd -bnBC 12 admin 'your-password' | cut -d: -f2Store only the resulting hash:
users:
admin: "$2b$12$..."BOXBOX_USERS_admin='$2b$12$...'Plaintext configured passwords and JWT secrets shorter than 32 bytes are rejected at startup. If no users are configured, the server also refuses to start.
Mount points define what the authenticated UI and API can access.
Prefer narrow paths:
mount_points:
- name: "media"
path: "/srv/media"
read_only: false
- name: "backups"
path: "/srv/backups"
read_only: trueThe default compose file does not mount /. A mount that resolves to / is rejected unless allow_root_mount: true is explicitly configured after reviewing the risk.
The backend validates requested paths against configured mount points and blocks traversal outside those roots. Examples of blocked paths include:
../secret
..%2Fsecret
media/../../etc/passwd
Read-only mount points also block write operations after path resolution.
With no allowed_origins, BoxBox accepts browser WebSocket upgrades only when Origin matches the request host. A literal * is supported as an explicit, risky opt-out.
Restrict origins for browser-exposed deployments:
allowed_origins:
- "https://boxbox.example.com"
- "*.internal.example.com"Or through env:
BOXBOX_ALLOWED_ORIGINS="https://boxbox.example.com,*.internal.example.com"Use your proxy to provide:
- HTTPS certificates.
- HTTP to HTTPS redirect.
- WebSocket forwarding for
/api/v1/ws. - Request size limits compatible with your upload size.
- Optional extra auth, IP allow-listing, or VPN-only access.
If you use Traefik or another reverse proxy, add routing and TLS configuration according to that proxy's setup. The default compose file uses normal host port binding.
Forwarded client-IP headers are ignored unless the direct proxy IP/CIDR is configured in trusted_proxies or BOXBOX_TRUSTED_PROXIES.
Chunked uploads are assembled in /tmp/boxbox by default and moved into the final destination when complete. Configure enough disk space for temporary chunks:
volumes:
- boxbox-temp:/tmp/boxboxUse max_upload_mb to cap accepted upload sizes.
Share links expose one file or a folder tree to anyone holding the token URL:
- Tokens are 256-bit random values (43 URL-safe characters); possession of the token is the only credential for recipient endpoints.
- Recipient endpoints are public and rate-limited per client IP, separately from the stricter auth budget.
- Unknown, expired, and revoked tokens all return the same
404, so recipients cannot probe why a link stopped working. - Share management is scoped to the creating account: users can list, update, and revoke only their own links.
- Owners can revoke a link at any time; expiry is optional (
expiresInSecondsat creation). A revocation that completes during an upload prevents that upload from being published. - Shared targets are re-resolved against the current mount configuration on every recipient access. Removed or renamed mounts invalidate the link, and a mount that became read-only still serves reads while denying uploads or deletes.
- Recipient downloads and previews carry the same sandboxed
Content-Security-Policyas the app's streaming endpoints, and active document formats (HTML, SVG, XML) are forced to download instead of rendering inline. - Folder uploads are capped by both the per-link
maxUploadBytesand servermax_upload_mblimits. Upload-only links cannot replace existing items; delete permission also permits replacement and deletion below (never at) the shared root. - Folder ZIP archives validate traversal and resolved paths against the share root before streaming.
- Recipient metadata responses never include mount names or internal paths.
- The share store directory is restricted to owner access (
0700) and its token-bearingshares.jsonfile to0600, including existing stores when the service initializes. - Before downgrading to a version predating split upload/delete permissions, back up
shares.json; older versions cannot preserve the new per-link permissions and upload caps if they rewrite the store.
- Rotate
BOXBOX_JWT_SECRETif it was ever committed or shared. - Rotate admin passwords after test deployments.
- Review mounted paths after adding new host disks.
- Check logs for repeated failed logins or path validation errors.
- Keep the image rebuilt from current dependencies.
- Back up
/dataif custom drive names matter to you.