daemon: fs/safepath — the saveDir/filename path-traversal boundary (security)

veloxd is the one process that turns an untrusted string into a
filesystem destination, and via capture.offer that string can come
from a web page. CLAUDE.md §4 and the M1 DoD both name this.

daemon/docs/safepath-adversarial.md is the spec, written before the
code the way EXT did for shouldCapture: 21 rows — .. traversal
(A1/A2), absolute-outside-roots (A3), prefix-match confusion (A4),
symlink-out (A7), TOCTOU on a created tail (A8), NUL/control bytes in
the leaf that CORE's fuzzer hit through Content-Disposition (A9/A10),
degenerate and overlong leaves (A11/A13), overlong dir component
(A14), symlinked root (A16), destination-is-a-file (A17), and the
legitimate cases that must still pass — non-ASCII (A18), redundant "."
(A19), trailing space/dot trimming (A20).

fs/safepath.cpp:
- sanitize_leaf: strip <0x20 and 0x7F, trim ws, strip trailing dots,
  reject ""/"."/".."/contains-'/', cap 255 UTF-8 bytes on a codepoint
  boundary. Mirrors core/src/net/content_disposition.cpp.
- canonicalize_root: expand ~ and realpath each allowedRoots entry
  once, so a symlinked root resolves to its target.
- resolve_target: reject relative saveDir and any ".." component
  lexically; if the dir exists, realpath + component-wise containment
  (a symlink that escapes is caught, one that stays inside passes); if
  a tail is missing, realpath+check the deepest existing ancestor then
  create the tail via an openat/mkdirat O_NOFOLLOW walk and re-derive
  the final path from the fd. Every failure is -32011 with data.path =
  the *original* saveDir (never the resolved path). Residual TOCTOU on
  a pre-existing intermediate dir is documented and closed by CORE's
  O_NOFOLLOW open of the file.

veloxd_fs static lib; veloxd_rpc links it for the download.add wiring
next. Test veloxd.safepath is the adversarial table, on a real temp
tree. ASan+UBSan and TSan clean; 33 daemon/cli tests green.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Upd9WhG9oppieig5nRDLig
This commit is contained in:
2026-09-10 19:37:01 +04:00
co-authored by Claude Sonnet 5
parent 1914eed7db
commit ab479e7885
8 changed files with 528 additions and 2 deletions
+9 -1
View File
@@ -44,6 +44,14 @@ target_compile_features(veloxd_store PUBLIC cxx_std_23)
target_compile_options(veloxd_store PRIVATE -Wall -Wextra -Wpedantic -Werror)
target_link_libraries(veloxd_store PUBLIC SQLite::SQLite3 PRIVATE OpenSSL::Crypto)
# --- veloxd_fs — the saveDir/filename path-traversal boundary (security) -----------
# daemon/docs/safepath-adversarial.md is the spec; safepath_test.cpp is that table.
add_library(veloxd_fs STATIC src/fs/safepath.cpp)
add_library(velox::daemon_fs ALIAS veloxd_fs)
target_include_directories(veloxd_fs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_compile_features(veloxd_fs PUBLIC cxx_std_23)
target_compile_options(veloxd_fs PRIVATE -Wall -Wextra -Wpedantic -Werror)
# --- veloxd_sched — the concurrency governor (pure; no engine link yet, see
# daemon/docs/deferrals.md D4) ---------------------------------------------------
add_library(veloxd_sched STATIC
@@ -73,7 +81,7 @@ target_include_directories(veloxd_rpc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_compile_features(veloxd_rpc PUBLIC cxx_std_23)
target_compile_options(veloxd_rpc PRIVATE -Wall -Wextra -Wpedantic -Werror)
target_link_libraries(veloxd_rpc
PUBLIC velox::proto veloxd_store nlohmann_json::nlohmann_json Threads::Threads
PUBLIC velox::proto veloxd_store veloxd_fs nlohmann_json::nlohmann_json Threads::Threads
)
# --- veloxd — the daemon binary -------------------------------------------------------