Implement safe password-store repository core (#3)

This commit is contained in:
Hermes Agent
2026-08-09 21:41:31 +00:00
parent 2dff2d5254
commit 8e4c9390a0
9 changed files with 1732 additions and 21 deletions

35
docs/repository-core.md Normal file
View File

@@ -0,0 +1,35 @@
# Password-store repository core
`crates/storage::repository` maps logical entry names directly to the upstream
password-store layout: `team/service` is stored as `team/service.gpg` and no
IronStorage-specific metadata is added. Discovery inventories ordinary
directories, encrypted entries, `.gpg-id`, `.gpg-id.sig`, auxiliary regular
files, and `.git` directory boundaries. Git contents are not mistaken for
password-store objects, and the innermost boundary applies to a nested entry.
Logical paths are relative component sequences. Absolute paths, `.` and `..`,
empty entry paths, and entry paths with a trailing slash are rejected before
filesystem access. A trailing slash in display-path resolution explicitly
selects a directory. If both `name.gpg` and `name/` exist, the unadorned name is
reported as ambiguous and mutations through the collision are rejected.
The repository is opened as a capability-scoped directory. All subsequent
discovery, reads, temporary files, renames, and cleanup remain relative to that
handle. Symlinks are rejected, and the capability layer prevents a raced link
from escaping the selected vault. FIFOs, sockets, devices, and other special
file types are also rejected; unrelated regular files remain visible as
auxiliary repository objects.
Encrypted replacements are written to a temporary file in the destination
directory, assigned private permissions on Unix, flushed to stable storage,
atomically renamed over the destination, and followed by a directory sync.
Before the rename, any error leaves the old entry intact and removes newly
created empty directories. After the rename, the only possible observable
state is the complete new entry; an interrupted directory sync is reported as
durability-uncertain rather than pretending that rollback occurred. Empty
ancestor cleanup uses directory-only removal and therefore stops at the store
root, content, or a recipient-policy boundary.
Decrypted bytes use `SecretBytes`, which takes ownership without an additional
copy, redacts `Debug`, exposes data only through explicit methods, and zeroes
its allocation on drop.