use std::path::{Path, PathBuf};
use std::sync::OnceLock;
const PRELOAD: &str = include_str!("../security/opencode-session-header.mjs");
const FILE_NAME: &str = "opencode-session-header.mjs";
const DIR_ATTEMPTS: usize = 32;
fn enabled() -> bool {
std::env::var("RDSH_OPENCODE_SESSION_HEADER").map_or(true, |v| v != "0")
}
pub fn preload_path() -> Option<PathBuf> {
if !enabled() {
return None;
}
static CACHE: OnceLock<Option<PathBuf>> = OnceLock::new();
CACHE.get_or_init(write_preload).clone()
}
pub fn import_specifier() -> Option<String> {
preload_path().map(|path| file_url(&path))
}
fn write_preload() -> Option<PathBuf> {
let directory = private_directory()?;
let path = directory.join(FILE_NAME);
write_new_private_file(&path, PRELOAD).ok()?;
Some(path)
}
fn private_directory() -> Option<PathBuf> {
let base = std::env::temp_dir();
for _ in 0..DIR_ATTEMPTS {
let mut nonce = [0u8; 16];
getrandom::fill(&mut nonce).ok()?;
let directory = base.join(format!(
"rdsh-opencode-preload-{}-{}",
std::process::id(),
hex(&nonce)
));
match create_private_directory(&directory) {
Ok(()) => return Some(directory),
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(_) => return None,
}
}
None
}
fn hex(bytes: &[u8]) -> String {
let mut text = String::with_capacity(bytes.len() * 2);
for byte in bytes {
text.push_str(&format!("{byte:02x}"));
}
text
}
#[cfg(unix)]
fn create_private_directory(directory: &Path) -> std::io::Result<()> {
use std::os::unix::fs::DirBuilderExt;
let mut builder = std::fs::DirBuilder::new();
builder.mode(0o700);
builder.create(directory)
}
#[cfg(not(unix))]
fn create_private_directory(directory: &Path) -> std::io::Result<()> {
std::fs::DirBuilder::new().create(directory)
}
fn write_new_private_file(path: &Path, contents: &str) -> std::io::Result<()> {
use std::io::Write;
let mut options = std::fs::OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600);
}
let mut file = options.open(path)?;
file.write_all(contents.as_bytes())
}
#[cfg(unix)]
fn file_url(path: &Path) -> String {
use std::os::unix::ffi::OsStrExt;
file_url_from_path_bytes_with(path.as_os_str().as_bytes(), false)
}
#[cfg(not(unix))]
fn file_url(path: &Path) -> String {
let text = path.to_string_lossy().replace('\\', "/");
let normalized = if let Some(rest) = text.strip_prefix("//?/UNC/") {
format!("//{rest}")
} else if let Some(rest) = text.strip_prefix("//?/") {
rest.to_string()
} else {
text.clone()
};
file_url_from_path_bytes(normalized.as_bytes())
}
#[cfg(any(not(unix), test))]
fn file_url_from_path_bytes(bytes: &[u8]) -> String {
file_url_from_path_bytes_with(bytes, true)
}
fn file_url_from_path_bytes_with(bytes: &[u8], unc_authority: bool) -> String {
let mut url = String::with_capacity(bytes.len() + 8);
match bytes {
[b'/', b'/', ..] if unc_authority => url.push_str("file:"),
[b'/', ..] => url.push_str("file://"),
_ => url.push_str("file:///"),
}
for &byte in bytes {
match byte {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' | b'/' | b':' => {
url.push(char::from(byte))
}
_ => url.push_str(&format!("%{byte:02X}")),
}
}
url
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn writes_the_embedded_preload() {
let _lock = crate::test_support::env_lock();
std::env::remove_var("RDSH_OPENCODE_SESSION_HEADER");
let path = preload_path().expect("preload path");
assert_eq!(std::fs::read_to_string(&path).unwrap(), PRELOAD);
assert!(PRELOAD.contains("__RDSH_OPENCODE_SESSION_HEADER__"));
assert!(PRELOAD.contains("x-opencode-session"));
}
#[test]
fn the_preload_never_sits_directly_in_the_shared_temp_root() {
let _lock = crate::test_support::env_lock();
std::env::remove_var("RDSH_OPENCODE_SESSION_HEADER");
let path = preload_path().expect("preload path");
let directory = path.parent().expect("preload directory");
assert_eq!(path.file_name().unwrap(), FILE_NAME);
assert_ne!(directory, std::env::temp_dir());
assert!(directory.starts_with(std::env::temp_dir()));
}
#[test]
fn import_specifier_is_a_file_url_for_the_staged_preload() {
let _lock = crate::test_support::env_lock();
std::env::remove_var("RDSH_OPENCODE_SESSION_HEADER");
let specifier = import_specifier().expect("import specifier");
assert!(
specifier.starts_with("file:///"),
"unexpected specifier: {specifier}"
);
assert!(specifier.ends_with(FILE_NAME), "unexpected: {specifier}");
}
#[cfg(unix)]
#[test]
fn preload_directory_and_file_are_private() {
use std::os::unix::fs::PermissionsExt;
let _lock = crate::test_support::env_lock();
std::env::remove_var("RDSH_OPENCODE_SESSION_HEADER");
let path = preload_path().expect("preload path");
let directory = path.parent().unwrap();
assert_eq!(
std::fs::metadata(directory).unwrap().permissions().mode() & 0o777,
0o700
);
assert_eq!(
std::fs::metadata(&path).unwrap().permissions().mode() & 0o777,
0o600
);
}
#[cfg(unix)]
#[test]
fn a_preexisting_symlink_is_not_followed() {
use std::os::unix::fs::symlink;
let mut nonce = [0u8; 8];
getrandom::fill(&mut nonce).unwrap();
let directory =
std::env::temp_dir().join(format!("rdsh-preload-link-test-{}", hex(&nonce)));
create_private_directory(&directory).unwrap();
let attacker = directory.join("attacker.mjs");
std::fs::write(&attacker, "// attacker controlled\n").unwrap();
let planted = directory.join(FILE_NAME);
symlink(&attacker, &planted).unwrap();
assert!(write_new_private_file(&planted, PRELOAD).is_err());
assert_eq!(
std::fs::read_to_string(&attacker).unwrap(),
"// attacker controlled\n"
);
std::fs::remove_dir_all(&directory).unwrap();
}
#[test]
fn node_accepts_the_staged_preload_url() {
let _lock = crate::test_support::env_lock();
let mut nonce = [0u8; 8];
getrandom::fill(&mut nonce).unwrap();
let directory = std::env::temp_dir().join(format!("rdsh preload#{}", hex(&nonce)));
create_private_directory(&directory).unwrap();
let path = directory.join(FILE_NAME);
write_new_private_file(&path, "globalThis.__rdshPreloadRan = 'yes';\n").unwrap();
let run = std::process::Command::new("node")
.arg("--import")
.arg(file_url(&path))
.arg("--input-type=module")
.arg("-e")
.arg("if (globalThis.__rdshPreloadRan !== 'yes') process.exit(3);")
.output();
let Ok(output) = run else {
std::fs::remove_dir_all(&directory).unwrap();
return; };
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"node rejected the staged preload URL: {stderr}"
);
std::fs::remove_dir_all(&directory).unwrap();
}
#[test]
fn file_urls_keep_plain_unix_and_windows_paths_readable() {
assert_eq!(
file_url_from_path_bytes(b"/tmp/rdsh-preload/p.mjs"),
"file:///tmp/rdsh-preload/p.mjs"
);
assert_eq!(
file_url_from_path_bytes(b"C:/Users/alice/AppData/Local/Temp/p.mjs"),
"file:///C:/Users/alice/AppData/Local/Temp/p.mjs"
);
}
#[test]
fn file_urls_encode_reserved_characters() {
assert_eq!(
file_url_from_path_bytes(b"/tmp/a#b?c d/p.mjs"),
"file:///tmp/a%23b%3Fc%20d/p.mjs"
);
assert_eq!(
file_url_from_path_bytes(b"/tmp/100%/p.mjs"),
"file:///tmp/100%25/p.mjs"
);
}
#[test]
fn file_urls_keep_unc_authorities_readable() {
assert_eq!(
file_url_from_path_bytes(b"//server/share/Temp/p.mjs"),
"file://server/share/Temp/p.mjs"
);
}
#[cfg(unix)]
#[test]
fn unix_double_slash_paths_keep_an_empty_authority() {
assert_eq!(
file_url(Path::new("//tmp/rdsh-preload/p.mjs")),
"file:////tmp/rdsh-preload/p.mjs"
);
}
#[cfg(unix)]
#[test]
fn node_accepts_a_preload_url_under_a_double_slash_temp_root() {
let _lock = crate::test_support::env_lock();
let mut nonce = [0u8; 8];
getrandom::fill(&mut nonce).unwrap();
let directory = Path::new("//tmp").join(format!("rdsh-preload-{}", hex(&nonce)));
create_private_directory(&directory).unwrap();
let path = directory.join(FILE_NAME);
write_new_private_file(&path, "globalThis.__rdshPreloadRan = 'yes';\n").unwrap();
let run = std::process::Command::new("node")
.arg("--import")
.arg(file_url(&path))
.arg("--input-type=module")
.arg("-e")
.arg("if (globalThis.__rdshPreloadRan !== 'yes') process.exit(3);")
.output();
let Ok(output) = run else {
std::fs::remove_dir_all(&directory).unwrap();
return; };
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"node rejected the double-slash preload URL: {stderr}"
);
std::fs::remove_dir_all(&directory).unwrap();
}
#[cfg(not(unix))]
#[test]
fn extended_unc_paths_map_to_a_plain_file_url() {
assert_eq!(
file_url(Path::new(r"\\?\UNC\server\share\Temp\p.mjs")),
"file://server/share/Temp/p.mjs"
);
assert_eq!(
file_url(Path::new(r"\\server\share\Temp\p.mjs")),
"file://server/share/Temp/p.mjs"
);
}
}