internal/tenant/ provides FS, a sandboxed handle for a single
tenant's notes directory. Implementation strategy:
- Defence in depth: every relative path is validated up front
(rejects "..", absolute paths, NUL bytes, empty), then handed
to os.Root (Go 1.24+) which enforces the boundary at the
syscall layer using openat(2)+RESOLVE_BENEATH on Linux. This
closes TOCTOU races and symlink-target swapping.
- WriteFile is atomic (write to .tmp, rename in-root). Mode 0o600
on files, 0o700 on directories. Tenant root is created with
0o700 by Open().
- Errors are normalised: fs.ErrNotExist -> ErrNotFound, anything
os.Root rejects as "outside" the root -> ErrInvalidPath. The
HTTP layer can map cleanly to 404 / 400.
Tests cover the full traversal attack surface — "../", absolute
paths, mixed separators, NUL bytes, "." and "" — plus symlink
escapes and cross-tenant isolation. All vectors return errors;
none escape the root.
Closes#10.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>