Docs: update for new safety features and fix SyncMode enum names
- CLAUDE.md: document atomic writes, input size limits, path validation, file download size cap, workspace path validation, taskStack auto-cleanup - API.md: add Input Validation & Safety section with size limits table, atomic writes list, and path safety documentation - API.md: fix SyncMode enum names (PushOnly/PullOnly -> Push/Pull) - API.md: add missing on_progress parameter to sync_workspace examples - Update current state date to 2026-04-06 https://claude.ai/code/session_01F67yfLLmSaBtT7aKKNus1M
This commit is contained in:
parent
a12deb5182
commit
e471534b58
12
CLAUDE.md
12
CLAUDE.md
|
|
@ -35,11 +35,11 @@ Two-crate workspace (`resolver = "2"`, edition 2021) plus a Tauri app:
|
||||||
|
|
||||||
### Key patterns
|
### Key patterns
|
||||||
|
|
||||||
- **Storage trait** (`storage.rs`): Strategy pattern for task persistence. `FileSystemStorage` reads/writes markdown files with YAML frontmatter and JSON metadata files.
|
- **Storage trait** (`storage.rs`): Strategy pattern for task persistence. `FileSystemStorage` reads/writes markdown files with YAML frontmatter and JSON metadata files. Atomic writes (temp file + rename) for all metadata files. Input validation: task titles max 500 chars, descriptions max 1MB, list names max 255 chars.
|
||||||
- **Repository** (`repository.rs`): `TaskRepository` wraps a `Storage` impl and provides the public API for task/list CRUD, ordering, and grouping. Tests live here.
|
- **Repository** (`repository.rs`): `TaskRepository` wraps a `Storage` impl and provides the public API for task/list CRUD, ordering, and grouping. Tests live here.
|
||||||
- **Config** (`config.rs`): `AppConfig` manages workspaces keyed by UUID string. `WorkspaceConfig` stores `name`, `path`, `mode` (Local/Webdav), `webdav_url`, `webdav_path` (user-selected remote folder), `theme`, and `sync_interval_secs`. `add_workspace` returns a generated UUID. Stored in platform-specific config dirs via the `directories` crate.
|
- **Config** (`config.rs`): `AppConfig` manages workspaces keyed by UUID string. `WorkspaceConfig` stores `name`, `path`, `mode` (Local/Webdav), `webdav_url`, `webdav_path` (user-selected remote folder), `theme`, and `sync_interval_secs`. `add_workspace` returns a generated UUID. Stored in platform-specific config dirs via the `directories` crate. Atomic writes (temp file + rename) prevent corruption on crash.
|
||||||
- **Sync** (`sync.rs`): Three-way diff sync with offline queue. Checksum-based conflict resolution: downloads remote, compares SHA-256 — identical content is a false conflict (skipped); when different, remote wins and local is recovered as a duplicate with a new UUID and `[RECOVERED FROM CONFLICT]` prefix. Auto-sync lifecycle: periodic polling (configurable interval, default 60s), debounced file-change (5s), window-focus (30s stale threshold). Wrapped in `tokio::time::timeout` (60s) to handle unreachable servers on Windows.
|
- **Sync** (`sync.rs`): Three-way diff sync with offline queue. Checksum-based conflict resolution: downloads remote, compares SHA-256 — identical content is a false conflict (skipped); when different, remote wins and local is recovered as a duplicate with a new UUID and `[RECOVERED FROM CONFLICT]` prefix. Auto-sync lifecycle: periodic polling (configurable interval, default 60s), debounced file-change (5s), window-focus (30s stale threshold). Wrapped in `tokio::time::timeout` (60s) to handle unreachable servers on Windows. Path traversal validation on all sync paths. Atomic writes for sync state and queue files.
|
||||||
- **WebDAV** (`webdav.rs`): reqwest client with rustls-tls, 30s request timeout, 10s connect timeout. Rejects non-HTTPS URLs. `Zeroizing<String>` for credential fields. `move_resource` method for WebDAV MOVE (workspace rename). 10MB PROPFIND response cap. Desktop credentials via `keyring` crate (feature-gated); Tauri GUI uses `tauri-plugin-credentials` for cross-platform support (Android Keystore + desktop keychain).
|
- **WebDAV** (`webdav.rs`): reqwest client with rustls-tls, 30s request timeout, 10s connect timeout. Rejects non-HTTPS URLs. `Zeroizing<String>` for credential fields. `move_resource` method for WebDAV MOVE (workspace rename). 10MB cap on both PROPFIND responses and file downloads. Desktop credentials via `keyring` crate (feature-gated); Tauri GUI uses `tauri-plugin-credentials` for cross-platform support (Android Keystore + desktop keychain).
|
||||||
|
|
||||||
### On-disk format
|
### On-disk format
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ The GUI uses Svelte 5 runes mode (`$state`, `$derived`, `$effect`, `$props()`).
|
||||||
|
|
||||||
Pre-alpha. No users, no released builds, no data to migrate. Breaking changes to on-disk formats, config structure, or sync conventions are free — do not add migration logic.
|
Pre-alpha. No users, no released builds, no data to migrate. Breaking changes to on-disk formats, config structure, or sync conventions are free — do not add migration logic.
|
||||||
|
|
||||||
### Current state (2026-04-05)
|
### Current state (2026-04-06)
|
||||||
|
|
||||||
- **Phase 1** (Core + CLI): Complete
|
- **Phase 1** (Core + CLI): Complete
|
||||||
- **Phase 2** (WebDAV sync): Complete — remote folder browsing, checksum-based conflict resolution, auto-sync lifecycle, per-workspace sync interval
|
- **Phase 2** (WebDAV sync): Complete — remote folder browsing, checksum-based conflict resolution, auto-sync lifecycle, per-workspace sync interval
|
||||||
|
|
@ -104,6 +104,8 @@ Pre-alpha. No users, no released builds, no data to migrate. Breaking changes to
|
||||||
- Task deduplication on load (handles sync conflict duplicates)
|
- Task deduplication on load (handles sync conflict duplicates)
|
||||||
- Subtask hierarchy: subtask count shown on parent tasks in list, subtask detail via three-panel slide navigation, inline add at top of subtask list (new subtasks prepend), collapsible completed subtasks section, cascade delete (parent deletion removes all subtasks with confirmation warning)
|
- Subtask hierarchy: subtask count shown on parent tasks in list, subtask detail via three-panel slide navigation, inline add at top of subtask list (new subtasks prepend), collapsible completed subtasks section, cascade delete (parent deletion removes all subtasks with confirmation warning)
|
||||||
- Custom confirmation dialogs (ConfirmDialog component replaces native confirm())
|
- Custom confirmation dialogs (ConfirmDialog component replaces native confirm())
|
||||||
|
- Workspace path validation (rejects system directories)
|
||||||
|
- Task detail auto-cleanup (taskStack clears when viewed task is deleted or list switches)
|
||||||
|
|
||||||
### GUI features NOT yet done
|
### GUI features NOT yet done
|
||||||
|
|
||||||
|
|
|
||||||
40
docs/API.md
40
docs/API.md
|
|
@ -305,11 +305,12 @@ let result = sync_workspace(
|
||||||
"username",
|
"username",
|
||||||
"password",
|
"password",
|
||||||
SyncMode::Full,
|
SyncMode::Full,
|
||||||
|
None, // optional progress callback
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
// Push-only or pull-only
|
// Push-only or pull-only
|
||||||
sync_workspace(path, url, user, pass, SyncMode::PushOnly).await?;
|
sync_workspace(path, url, user, pass, SyncMode::Push, None).await?;
|
||||||
sync_workspace(path, url, user, pass, SyncMode::PullOnly).await?;
|
sync_workspace(path, url, user, pass, SyncMode::Pull, None).await?;
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Check Sync Status
|
#### Check Sync Status
|
||||||
|
|
@ -375,7 +376,9 @@ client.delete_file("old-task.md").await?;
|
||||||
- **Offline queue**: Pending operations are queued and replayed when connectivity returns
|
- **Offline queue**: Pending operations are queued and replayed when connectivity returns
|
||||||
- **Sync state**: Stored in `.syncstate.json` within the workspace directory
|
- **Sync state**: Stored in `.syncstate.json` within the workspace directory
|
||||||
- **Auto-sync**: Periodic polling (configurable `sync_interval_secs`), debounced file-change trigger (5s), window-focus trigger (30s stale threshold)
|
- **Auto-sync**: Periodic polling (configurable `sync_interval_secs`), debounced file-change trigger (5s), window-focus trigger (30s stale threshold)
|
||||||
- **Response size cap**: PROPFIND responses are limited to 10 MB (checked via `Content-Length` header and actual body size) to prevent memory exhaustion from malicious servers
|
- **Response size cap**: PROPFIND responses and file downloads are limited to 10 MB (checked via `Content-Length` header and actual body size) to prevent memory exhaustion from malicious servers
|
||||||
|
- **Path traversal protection**: Sync paths are validated to reject `..` and `\` components before any file system operation
|
||||||
|
- **Atomic writes**: Sync state (`.syncstate.json`) and offline queue (`.syncqueue.json`) use atomic write pattern (temp file + rename) to prevent corruption on crash
|
||||||
- **Syncable files**: Only processes files at expected depths — `.onyx-workspace.json` at root (depth 1), `.listdata.json` and `*.md` inside list directories (depth 2)
|
- **Syncable files**: Only processes files at expected depths — `.onyx-workspace.json` at root (depth 1), `.listdata.json` and `*.md` inside list directories (depth 2)
|
||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
@ -397,6 +400,37 @@ pub enum Error {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Input Validation & Safety
|
||||||
|
|
||||||
|
### Size Limits
|
||||||
|
|
||||||
|
The storage layer enforces the following limits:
|
||||||
|
|
||||||
|
| Input | Max Length | Error |
|
||||||
|
|-------|-----------|-------|
|
||||||
|
| Task title | 500 characters | `InvalidData` |
|
||||||
|
| Task description | 1,000,000 bytes (1 MB) | `InvalidData` |
|
||||||
|
| List name | 255 characters | `InvalidData` |
|
||||||
|
| WebDAV file download | 10 MB | `WebDav` |
|
||||||
|
| PROPFIND response | 10 MB | `WebDav` |
|
||||||
|
|
||||||
|
### Atomic Writes
|
||||||
|
|
||||||
|
All metadata and state files use an atomic write pattern (write to `.tmp` then rename) to prevent data corruption if the process crashes mid-write:
|
||||||
|
|
||||||
|
- `.onyx-workspace.json` (root metadata)
|
||||||
|
- `.listdata.json` (list metadata)
|
||||||
|
- `config.json` (app config)
|
||||||
|
- `.syncstate.json` (sync state)
|
||||||
|
- `.syncqueue.json` (offline queue)
|
||||||
|
|
||||||
|
### Path Safety
|
||||||
|
|
||||||
|
- **List names**: Rejected if they contain `/`, `\`, or `..` components. Canonicalized and verified to stay within workspace root.
|
||||||
|
- **Sync paths**: Validated to reject `..` and `\` before any file system operation.
|
||||||
|
- **Workspace paths** (Tauri): Rejected if they point to system directories (`/etc`, `/usr`, `/bin`, etc.).
|
||||||
|
- **Filenames**: Sanitized to replace `/ \ : * ? " < > |` and control characters with `_`.
|
||||||
|
|
||||||
## Example: Complete Workflow
|
## Example: Complete Workflow
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue