Every arizuko group folder is exposed as WebDAV at https://<host>/dav/<group>/. Any WebDAV client (Finder, Windows Explorer, rclone, davfs2, curl) sees the same files the agent reads and writes.
One-time setup of an rclone remote against your instance, plus a mount:
# 1. point rclone at the WebDAV endpoint (no user/pass yet)
rclone config create arizuko webdav \
url=https://fab.krons.cx/dav/ \
vendor=other
# 2. issue a Bearer token from the dashboard:
# open https://fab.krons.cx/dash/profile in a browser and copy it.
# 3. tell rclone to use the token (it stores it in ~/.config/rclone/rclone.conf)
rclone config update arizuko bearer_token=<paste-token-here>
# 4. mount a group as a local folder
mkdir -p ~/arizuko-atlas
rclone mount arizuko:atlas ~/arizuko-atlas --vfs-cache-mode writes
You should now see the group’s files under ~/arizuko-atlas/ — CLAUDE.md, diary/, facts/, media/, and so on. Edit them with your editor, drop files in, and the agent picks them up on its next turn.
/auth/login, then open https://<host>/dav/<group>/ in your browser. dufs (the underlying server) ships a directory UI, so you get a clickable file tree for free.The URL /dav/<group>/<rest> maps one-to-one to /<group>/<rest> under the data directory on the host. davd serves <DATA_DIR>/groups/ read-write over WebDAV; proxyd strips the leading /dav before forwarding and applies the davAllow write-block guard on top.
| URL | on-disk path (host) |
|---|---|
/dav/ | redirect to /dav/<first-group>/ |
/dav/atlas/ | /srv/data/arizuko_<inst>/groups/atlas/ |
/dav/atlas/strengths/ | /srv/data/arizuko_<inst>/groups/atlas/strengths/ |
/dav/atlas/CLAUDE.md | /srv/data/arizuko_<inst>/groups/atlas/CLAUDE.md |
/dav/atlas/users/alice.md | /srv/data/arizuko_<inst>/groups/atlas/users/alice.md |
<group> is the group’s folder name (the on-disk slug), not the human-friendly group name. If you don’t know it, the operator dashboard at /dash/groups lists them. The default group on a fresh instance is called main.
The same auth layer that gates /dash/ gates /dav/. There is no separate WebDAV password file and no Basic Auth.
| client style | credential | how |
|---|---|---|
| browser | JWT cookie | sign in at /auth/login with GitHub / Google / Discord / Telegram; the refresh_token cookie carries through to /dav/ |
| rclone, davfs2, curl, Finder, Windows Explorer | Bearer token | issue at /dash/profile, send as Authorization: Bearer <token> |
An unauthenticated request to /dav/... returns 303 See Other with Location: /auth/login — that’s by design. The browser follows it; curl does not unless you pass -L. Verify directly:
curl -s -o /dev/null -w '%{http_code}\n' https://fab.krons.cx/dav/
# 303
curl -s -o /dev/null -w '%{http_code}\n' \
-H "Authorization: Bearer $TOKEN" \
https://fab.krons.cx/dav/atlas/
# 200
/dash/profile if you suspect a leak.Authentication tells proxyd who you are; grants tell it which group folders you may touch. Grants are stored as acl rows (with role:operator membership in acl_membership for the operator) and resolved at request time:
acl_membership(sub, role:operator) sees every group on the instance. /dav/ redirects them to the first non-operator group in sorted order (deterministic across requests); they can reach any other group by typing its path directly.acl row authorizes access. Requesting a group you have no grant on returns 403 Forbidden (and proxyd logs dav forbidden with your sub and the path).Grant scope decides what your token can read; auth alone is not enough. A user signed in to /dav/ with no grants gets 403 on every group path.
WebDAV is writable end-to-end — davd mounts <DATA_DIR>/groups/ read-write, and proxyd’s davAllow middleware is the single point of write enforcement. The full ruleset, lifted from proxyd/main.go:
GET, HEAD, OPTIONS, PROPFIND are never blocked by the guard (grants and auth still apply).<group>/logs/... (PUT, POST, MKCOL, DELETE, MOVE, COPY, PROPPATCH) returns 403..env or .git, or ending in .pem, rejects writes. Reads still work so the agent can self-inspect.PUT to /dav/atlas/notes/todo.md lands on disk; the agent reads it on its next turn.The spec at specs/5/M-webdav.md is the source of truth.
rclone mount arizuko:atlas ~/atlas --vfs-cache-mode writes
$EDITOR ~/atlas/CLAUDE.md
# save, unmount. The agent picks up the new persona on its next turn.
# put a CSV in the channel's media folder
curl -u :$TOKEN \
-T ./quarterly.csv \
https://fab.krons.cx/dav/atlas/media/quarterly.csv
# then in chat, ask the agent: "read media/quarterly.csv and summarise"
Or just drag-drop into a Finder window mounted at /dav/atlas/. -u :$TOKEN sends the token as the password with an empty username — curl maps that to a Basic header which proxyd ignores; the real auth still flows through the Bearer header below if you set it explicitly.
curl -H "Authorization: Bearer $TOKEN" \
-T ./quarterly.csv \
https://fab.krons.cx/dav/atlas/media/quarterly.csv
The agent runs inside a container with its group folder mounted read-write; it can drop generated artifacts (PDFs, charts, transcripts) anywhere except logs/. The user then pulls them down:
curl -H "Authorization: Bearer $TOKEN" \
-o report.pdf \
https://fab.krons.cx/dav/atlas/out/report.pdf
Per-user memory lives at <group>/users/<user>.md. With operator grants you can list them all:
curl -X PROPFIND \
-H 'Depth: 1' \
-H "Authorization: Bearer $TOKEN" \
https://fab.krons.cx/dav/atlas/users/
One-liner config (interactive prompts skipped):
rclone config create arizuko webdav \
url=https://fab.krons.cx/dav/ \
vendor=other \
bearer_token=$TOKEN
Mount with the VFS cache so writes survive editor save-rename dances:
rclone mount arizuko:atlas ~/atlas \
--vfs-cache-mode writes \
--dir-cache-time 30s
https://fab.krons.cx/dav/atlas/ (include the trailing slash and the group name)./auth/login in Safari first; the JWT cookie is shared.Install davfs2, then add to /etc/davfs2/secrets (root-owned, mode 0600):
https://fab.krons.cx/dav/atlas/ "" <your-bearer-token>
Mount:
sudo mkdir -p /mnt/atlas
sudo mount -t davfs https://fab.krons.cx/dav/atlas/ /mnt/atlas
davfs2 sends credentials over HTTP Basic; proxyd only accepts Authorization: Bearer <JWT> or the refresh_token cookie. davfs2 will not authenticate. Use rclone on Linux.
https://fab.krons.cx/dav/atlas/Windows’ built-in client is conservative about HTTPS WebDAV — if it refuses to connect, install rclone and use that instead.
# read a file
curl -H "Authorization: Bearer $TOKEN" \
https://fab.krons.cx/dav/atlas/CLAUDE.md
# list a directory (Depth: 1 = immediate children only)
curl -X PROPFIND -H 'Depth: 1' \
-H "Authorization: Bearer $TOKEN" \
https://fab.krons.cx/dav/atlas/
# upload a file
curl -X PUT --data-binary @./notes.md \
-H "Authorization: Bearer $TOKEN" \
https://fab.krons.cx/dav/atlas/notes.md
/auth/logincurl/rclone: pass Authorization: Bearer <token>. Token comes from /dash/profile.PUT under logs/ or against a sensitive segment (.env, *.pem, .git) — or (b) you don’t have a grant on that group. journalctl -u arizuko_<inst> | grep "dav (forbidden|blocked)" tells you which./dav/<group>/davProxy configured (response body: WebDAV not configured). Check that WEBDAV_ENABLED=true in the instance .env and that compose has been regenerated (arizuko run <inst>).dufs implements per-file locks; they expire on their own but can persist for minutes after a hard disconnect.DAV_ADDR is unset on the proxyd container. compose/compose.go sets it to http://davd:8080 when WEBDAV_ENABLED=true; if you toggled it off, davd isn’t in the compose graph. Set WEBDAV_ENABLED=true in .env and re-run arizuko run.403 in proxyd logs) but the agent hasn’t seen it. Either it has nothing pending to react to — new files don’t trigger a turn on their own; ping the agent in chat — or you wrote to a path the agent doesn’t consult (e.g. an ad-hoc folder outside media/, facts/, users/). Reference its CLAUDE.md for what it reads.TLS terminates at the public edge (nginx in front of proxyd). From there inward — proxyd to davd, davd to disk — traffic is plain HTTP on the Docker bridge network; the network namespace is the trust boundary. Identity is signed at proxyd with HMAC over X-User-Sub/X-User-Name/X-User-Groups headers, but davd doesn’t verify those today — it trusts that anything reaching it has already cleared requireAuth + davAllow + auth.Authorize upstream. Full threat model in SECURITY.md.
sigoden/dufs).