Per-user sandbox and file links
HomeClaw gives each user (and the Companion app when not tied to a user) a private workspace: a per-user sandbox for file access and a dedicated output folder for generated files. When the assistant saves a report or export, it can return a shareable link so you can open or download the file in a browser. No separate file server: Core serves files from the same URL you use for chat.
Design reference: docs_design/FileSandboxDesign.md.
What is the per-user sandbox?
When tools.file_read_base is set in config/core.yml (e.g. D:/homeclaw or /home/user/homeclaw), all file and folder tools are restricted to paths under that base:
| Path form | Resolves to | Who can access |
|---|---|---|
| share/ or share/... | base/share/ |
All users + Companion |
| output/... | base/{user_id}/output/ or base/companion/output/ |
That user or Companion only |
| Any other path | base/{user_id}/... or base/companion/... |
That user or Companion only |
- User folder: Derived from system_user_id in config/user.yml (safe segment: alphanumeric, underscore, hyphen; max 64 chars).
- Companion folder: When the request is from the Companion app and not combined with a user, paths resolve under base/companion/.
- Shared folder: Paths starting with share/ (or configured
file_read_shared_dir) are visible and writable by everyone.
So: each user has a private area under base/{user_id}/; the Companion app has base/companion/ when not combined. Skills, plugins, and tools that write files must use output/
Output folder and generated files
- Reserved path: output/ (e.g.
output/report_123.html,output/summary.md). - Rule: Any feature that writes a file as a result of user or Companion interaction (reports, images, exports, summaries) must write under the current context’s output folder:
- User: path output/
→ stored under base/{user_id}/output/. - Companion (no user): path output/
→ stored under base/companion/output/.
The assistant uses tools like file_write or save_result_page with path output/
Generate a link for the response
When the assistant saves a report or file, it can return a link you can open in a browser or share. By default links are signed and time-limited (token style). You can instead use static links so the URL points directly under your web server’s doc root — see FileLinkStatic.md (www_root = homeclaw_root).
Config (minimal for links)
In config/core.yml (top level):
auth_api_key— Set to any non-empty string. Required to sign file links so they can be opened without sending the API key. If you use auth_enabled for /inbound, use the same key here.core_public_url— Optional. When set (e.g.https://homeclaw.example.com), report/file links use this URL. When empty, Core uses http://127.0.0.1:so links work on the same machine. With Pinggy tunnel, leave empty and Core uses the tunnel URL when it’s up. tools.file_read_base— Must be set so the sandbox and output folders exist (e.g.D:/homeclaw). Files are stored under file_read_base//output/ or file_read_base/companion/output/.
How it works
- Assistant writes to output: e.g. save_result_page (HTML/Markdown report) or file_write with path output/
. The file is saved in your (or companion’s) output folder. - Core returns a link: e.g.
https://your-core/files/out?path=output/report_xxx.html&token=.... The token is signed with auth_api_key and is time-limited (default 24 hours; max 7 days). - You open the link: In a browser, or “Open in app” / “Download” in the Companion app. GET /files/out verifies the token and serves the file (or an HTML listing if the path is a directory).
Markdown vs HTML
- Markdown — Best for short or medium text (summaries, lists). The tool can return the content in the chat so you see it there, plus the link to open the full file.
- HTML — Best for long or complex reports (tables, multi-section). The assistant typically returns “Report is ready. Open: ”; you open the link in a browser.
Safety
- No path escape: Token path and scope are validated (no
.., no/in scope). Resolved path must stay under file_read_base. - Token: Signed with auth_api_key; expired or invalid token returns 403. Link validity is set in config/core.yml via file_view_link_expiry_sec (seconds or e.g.
"7d"; default 7 days, max 365 days). See FileLinkStatic.md. - Core never crashes: File tools and GET /files/out return HTTP and messages on error; no uncaught exceptions.
Summary
| Topic | Summary |
|---|---|
| Per-user sandbox | When file_read_base is set, each user has base/{user_id}/; Companion has base/companion/ when not combined. share/ is shared. |
| Output folder | Use path output/ |
| Generate link | Set auth_api_key and optionally core_public_url. Tools like save_result_page write to output and return a signed link; open it in a browser or app. |
| Full design | docs_design/FileSandboxDesign.md |