Skip to main content
The Box VFS mounts a Box account at some prefix such as /box/. All operations involve network I/O to the Box v2 HTTP API: /2.0/folders/{id}/items for directories, /2.0/files/{id}/content for content, and multipart upload / /2.0/folders / delete / PUT (rename or move) / copy for writes. Files are served as raw bytes. Both READ and WRITE modes are supported. Box addresses everything by numeric item id, not by path (the account root is folder id 0). Mirage resolves each path to its id by listing folders level by level and caches the path → id mapping, so nested directories cost one API call per level on first access. Every Box item is served as its raw bytes, keeping its real name. Box’s native .boxnote / .boxcanvas come back as their stored ProseMirror-style JSON (text, so cat foo.boxnote | jq . works), and Box’s Google-Workspace files (.gdoc / .gsheet / .gslides) come back as the Office Open XML zips (docx / xlsx / pptx) Box stores them as, opaque binary like any other Office document.
Box exposes no API to edit these formats from a structured payload, so Mirage does not decode them, it hands back exactly what Box stores. For a rich, editable view of Google-format documents, mount them through Google Drive with Google credentials instead; the gws commands operate on Google file ids and do not apply to Box.
For credential setup, see the Box Setup guide. The TypeScript packages ship the same backend for Node and the browser, see Box (TypeScript).

Config

Box supports three authentication modes:
  • Developer token (access_token): quickest to try; expires after ~60 minutes and cannot be refreshed programmatically.
  • OAuth2 refresh (client_id + refresh_token, optional client_secret): Box rotates the refresh token on each refresh; supply on_refresh_token_rotated to persist the new one across restarts.
  • Client credentials (client_id + client_secret + enterprise_id): the app authenticates as its own service account; expired tokens are simply re-fetched.
The access token is cached in memory and refreshed ~5 minutes before expiry (refresh and client-credentials modes).

Config Reference

Fields marked secret are redacted in snapshots. * Provide one of: access_token; client_id + refresh_token; or client_id + client_secret + enterprise_id.

VFS.md

The VFS.md an agent reads with Box mounted at /<path>.

Virtual Filesystem

/<path> layout

Supported Unix Commands for VFS

Unix commands work on each path as on a local filesystem, as far as the path supports them:

Limitations of Unix Commands for VFS

Where a path differs from a local filesystem:Run man <cmd> or <cmd> --help for one command’s usage.

Mount a subfolder

Pass root_folder_id to expose a single Box folder as the mount root instead of the whole account. Every command and FUSE/VFS op is scoped to that folder; paths outside it are unreachable. Folder ids are stable across renames and moves, and are visible in the Box web URL (app.box.com/folder/<id>), so an id-based mount survives reorganization that a path prefix would not.

Cache

The Box VFS caches directory listings via IndexCacheStore to hold the path → id mapping and reduce repeated API calls during traversal; with no index: configured, listings live for the mount’s ttl (600 s by default), at most 24 hours; a configured index: uses its own ttl, still capped by the mount’s ttl:. File reads go through the standard read cache (caches_reads = True). Writes invalidate the affected listings so subsequent reads see the new state.

Writes

A write records the sha1 its upload reply names, so a fresh read right after a tee or sed -i downloads nothing.

Conditional writes

A rewrite that observes a missing file requires atomic create-only support, which this backend does not currently enable. Appending to a missing file therefore returns an unsupported-operation error under either write policy. An intentional write with > can create the file, and subsequent appends to that existing file retain the selected policy. A Box mount accepts write: conditional. A write or a file delete compares the sha1 mirage holds with the one the folder listing it walks anyway shows, and refuses a file changed since it was read before sending anything; the request then carries the file’s etag as If-Match, so Box refuses (412) a change that lands in between. A conditional write costs no request more than a plain one. rm -r walks the folder file by file, and a mv within the mount is Box’s own move, which is not refused when its source changed (a mv to another mount is a copy and a conditioned delete, so there a changed source stays and mv reports the failed removal).

Reads under fresh

A Box mount accepts read: fresh. The token is the file’s sha1, which a folder listing and GET /files/{id} both carry; a download names no version, so a whole read stamps the listed sha1 only when the downloaded bytes hash to it, and a ranged read stamps nothing. On a write: conditional mount a whole read stamps the sha1 of the bytes it downloaded, so a listing another client has since outdated cannot leave the next write without the version the agent read. modified_at is never the token: two same-size edits in one second share it. Checking a cached file costs one request, GET /files/{id} by the id the mount last listed, while the mount still holds that file’s listing row. The answer is trusted only when Box places the same active file at the same path from the mount root; a file that was moved, renamed, trashed or recreated falls back to listing each folder from the mount root, one listing per level (up to two per level when the file is gone). When that fallback confirms byte-identical content, the built-in RAM and Redis indexes retain the newly resolved file row if the original hint is still current. A later check then uses the recovered id without refreshing the parent listing. Custom indexes must implement replace_if_unchanged to retain this row; otherwise later checks repeat the fallback walk. A persistent 403 still requires the failed point request and a listing walk on each check. Any stale, unknown or gone verdict on the mount drops its cached listings, so the checks after one walk until a cold read or an ls lists the folders again. A file Box reports without a sha1 cannot be verified: each fresh read of it downloads it again and drops the mount’s cached listings. A folder’s etag does not move when its children change, so under fresh listings are re-fetched once per command. Repeated uses within that command share the result; outside commands, the existing one-second listing trust window applies. Before fetching a nested listing, mirage resolves its folder through current parent listings up to the configured mount root. A folder moved away and replaced under the same name is therefore resolved to the replacement on the next fresh command. This costs one listing per ancestor level; a warm file check still uses one metadata request.

Integration

What Box answers through its own API, so fewer files are read. Integration explains each command. grep -rw and rg -w read only the files Box search names. Every file is read when the search fails, reaches its 10,000-match limit or finds nothing, or when the searched path is not a folder. Search lists how each option uses the search. walk_limit: -1 in the mount’s "@all" command limit reads every file instead, since Box searches the text it extracted, not the bytes grep reads, and indexes a write some time after it lands. A file that holds the word can be left out: an office file, PDF or archive, the end of a large file, a word Box splits differently (foo.bar), or a recent write.

Example