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>
81 lines
2.9 KiB
Rust
81 lines
2.9 KiB
Rust
//! 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}");
|
|
}
|