Skip to content

refactor(hm-util): Replace bare (file_mode: u32, dir_mode: u32) pair with distinct mode newtypes to prevent swapped-argument bugs#122

Merged
markovejnovic merged 2 commits into
mainfrom
cq/hm-util-replace-bare-file-mode-u32-dir-mode-u32--26
Jun 10, 2026
Merged

refactor(hm-util): Replace bare (file_mode: u32, dir_mode: u32) pair with distinct mode newtypes to prevent swapped-argument bugs#122
markovejnovic merged 2 commits into
mainfrom
cq/hm-util-replace-bare-file-mode-u32-dir-mode-u32--26

Conversation

@markovejnovic

Copy link
Copy Markdown
Contributor

The smell

write_atomic_restricted(path, contents, file_mode: u32, dir_mode: u32) (and its blocking wrapper) took two adjacent, same-typed u32 octal mode parameters. Call sites passed them positionally as bare literals — creds.rs passes 0o600, 0o700, lib.rs passes 0o644, 0o700. Nothing prevented transposing the two arguments. Swapping them would silently set a secrets file to 0o700 (or the parent dir to 0o644) while still compiling and type-checking — a security-relevant invariant (secrets must be 0o600 in a 0o700 dir) caught today only by a unit test.

The change

Introduced two distinct one-field newtypes in crates/hm-util/src/os/fs.rs:

pub struct FileMode(pub u32);
pub struct DirMode(pub u32);

The signature is now write_atomic_restricted(path, contents, file: FileMode, dir: DirMode) for both the async function and its blocking wrapper. Callers write FileMode(0o600), DirMode(0o700), so the file/dir slots can no longer be transposed — an incorrect ordering is now a compile error and the intent is documented at every call site.

Behavior-preserving: the wrappers just unwrap .0 into the existing u32-based internals (create_dir_with_mode, write_file_with_mode). The two real call sites in hm-config (creds.rs, lib.rs) and the in-module tests were updated to match.

Pattern applied

This mirrors the "Distinct id newtypes per domain (no shared usize)" pattern — two incompatible wrappers over the same underlying integer, so the type system rejects mixing them. The reference is the same doctrine used elsewhere for domain id newtypes; here it guards a security-sensitive mode invariant rather than entity ids.

CI note

Only cargo check -p hm-util (and, since the call sites changed, cargo check -p hm-config) was run locally — both pass. Full CI runs on this PR. Opened as a draft for review.

@markovejnovic markovejnovic marked this pull request as ready for review June 10, 2026 20:10
@markovejnovic markovejnovic merged commit 2d7d1a6 into main Jun 10, 2026
17 checks passed
@markovejnovic markovejnovic deleted the cq/hm-util-replace-bare-file-mode-u32-dir-mode-u32--26 branch June 10, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant