M4a task 16: read a secret file from the file that was checked (review finding 2)

The test swaps the file between the check and the open through a `between` hook. A reference fix
passed it and the gate (769 tests) in the working tree, caught two mutations, and was removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 01:27:41 -07:00
co-authored by Claude Opus 5.5
parent 0081f24b70
commit 6feffadd11
4 changed files with 199 additions and 2 deletions
@@ -0,0 +1,80 @@
//! A secret file is read from the file that was checked, never from whatever the path names a
//! moment later (M4a review, finding 2). `read_checked` runs `between` after checking the path and
//! before opening it; each test swaps something there. Do not edit.
#[path = "support/tmp.rs"]
mod tmp;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use gatewayd::secrets::read_checked;
use tmp::TempDir;
const TOKEN: &str = "the-real-token";
const OTHER: &str = "a-file-the-owner-never-chose";
fn owner_file(dir: &TempDir, name: &str, text: &str) -> PathBuf {
let path = dir.write(name, text);
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)).unwrap();
path
}
fn refused(path: &Path, between: &dyn Fn(), word: &str) {
let why = read_checked(path, between).expect_err(word);
assert!(why.contains(word), "{word}: {why}");
assert!(why.contains(&path.display().to_string()), "{why}");
assert!(!why.contains(TOKEN) && !why.contains(OTHER), "never a value: {why}");
}
#[test]
fn an_untouched_file_is_read() {
let dir = TempDir::new("race-ok");
let path = owner_file(&dir, "token", &format!("{TOKEN}\n"));
let bytes = read_checked(&path, &|| {}).unwrap();
assert_eq!(bytes.as_slice(), format!("{TOKEN}\n").as_bytes());
}
#[test]
fn a_file_swapped_for_a_link_is_refused() {
let dir = TempDir::new("race-link");
let path = owner_file(&dir, "token", TOKEN);
let other = owner_file(&dir, "other", OTHER);
let swap = || {
std::fs::remove_file(&path).unwrap();
std::os::unix::fs::symlink(&other, &path).unwrap();
};
refused(&path, &swap, "changed while it was read");
}
#[test]
fn a_file_swapped_for_another_file_is_refused() {
let dir = TempDir::new("race-rename");
let path = owner_file(&dir, "token", TOKEN);
let other = owner_file(&dir, "other", OTHER);
let swap = || std::fs::rename(&other, &path).unwrap();
refused(&path, &swap, "changed while it was read");
}
#[test]
fn the_checks_hold_for_the_file_that_is_read() {
let dir = TempDir::new("race-mode");
let path = owner_file(&dir, "token", TOKEN);
let widen = || {
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
};
refused(&path, &widen, "has mode 644");
}
#[test]
fn the_path_checks_still_come_first() {
let dir = TempDir::new("race-first");
let other = owner_file(&dir, "other", OTHER);
let link = dir.path().join("link");
std::os::unix::fs::symlink(&other, &link).unwrap();
refused(&link, &|| panic!("never reached for a link"), "is a symbolic link");
refused(dir.path(), &|| panic!("never reached for a directory"), "is not a regular file");
let relative = Path::new("relative/token");
let why = read_checked(relative, &|| panic!("never reached")).unwrap_err();
assert!(why.contains("is not an absolute path"), "{why}");
}