mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-08-27 17:43:11 +00:00
Four rounds of security review kept finding the same shape: a sensitive thing placed under a NAME in the attacker-writable root, defended by a check on that name, and the next round swapped the next name. The scratch name moved inside a private staging directory -- and then the staging directory was itself a name in the root, so it was swappable too. The review demonstrated 274 victim rows readable outside the root after erase_scope reported success. There is no fixed point on that path, because every component under the root is re-resolved on every syscall and this crate holds none of them by descriptor. Two things terminate it: descriptor-anchored I/O, which needs an fd- or directory-relative open in the vector engine and is therefore an engine-level change, or removing the hostile directory from the threat model. This takes the second. It also settles a finding that made the rest academic: under a normal umask shards were published 0644 inside a 0755 root, so a different-uid attacker could read every tenant's vectors with no exploit at all. The staging directory was protecting a shard for the milliseconds it was being built and then publishing it world-readable. The root is now created 0700 by mkdir itself, shards and the lock are created 0600, and open refuses a root that is group- or other-accessible, reporting the offending mode without the path. An existing root is inspected and never modified, so an operator's deliberate permissions are reported rather than silently widened or narrowed. Observed on disk under both umask 022 and umask 000: root 0700, lock 0600, shard 0600, staging 0700. The staging directory, the inode identity check, the lone-regular-file requirement and the reserved-name sweep are all kept, but they are now defence in depth against operator error rather than the boundary itself, and their docs say so. The nlink documentation is corrected in particular: a link count of one means only that no second name existed at the instant of the lstat, not that the file is this index's, and the private root rather than that check is what prevents a second name. An attacker running as the same uid remains conceded, and content forgery remains out of scope for want of a MAC over stored vectors. Both are stated in the crate docs, the tests, and ADR-334. Note for reviewers: tempfile::tempdir() creates 0755, so the new check refused every existing test root and the suite now builds its roots at 0700 explicitly. That default is exactly the class of mistake that produced the world-readable shards. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_016QSCkKnxDjqU49NVVpWMK5 |
||
|---|---|---|
| .. | ||
| src | ||
| tests | ||
| Cargo.toml | ||