Skip to content

Commit e6402cf

Browse files
committed
test(agents,workspace): add integration + unit tests for security hardening
Docker Hub pull-then-build fallback: - ensure_image_for_runtime now tries gotempsh/temps-sandbox-{runtime} from Docker Hub first, falls back to local build if pull fails. Speeds up CI/fresh installs from minutes to seconds when images are published. - Tests: hub_image_for_runtime naming, pull_fallback_on_missing_hub_image Preview gateway shared secret (Phase 1): - 5 unit tests: creation, idempotency, empty-file rejection, 0600 perms on Unix, nested parent dir creation KillSignal integration test: - Docker-gated test spawning real sleep processes, verifying SIGTERM and SIGKILL. Skips when Docker unavailable or temps serve running. Terminal WebSocket rate limiter: - Token-bucket math unit test verifying drain, rejection, refill Sessions handler: - is_safe_tab_id and tmux_cli_for_provider unit tests
1 parent 203fbc8 commit e6402cf

File tree

5 files changed

+502
-2
lines changed

5 files changed

+502
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/temps-agents/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,6 @@ pulldown-cmark = "0.12"
4141
libc = { workspace = true }
4242
rand = { workspace = true }
4343
hex = "0.4"
44+
45+
[dev-dependencies]
46+
tempfile = { workspace = true }

crates/temps-agents/src/preview_gateway.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,3 +675,58 @@ pub async fn tail_logs(docker: &Docker, tail: usize) -> Result<Vec<String>> {
675675
let joined = chunks.join("");
676676
Ok(joined.lines().map(|l| l.to_string()).collect())
677677
}
678+
679+
#[cfg(test)]
680+
mod tests {
681+
use super::*;
682+
use tempfile::TempDir;
683+
684+
#[test]
685+
fn ensure_shared_secret_creates_file_on_first_call() {
686+
let dir = TempDir::new().unwrap();
687+
let secret = ensure_shared_secret(dir.path()).unwrap();
688+
// 32 random bytes → 64 hex chars
689+
assert_eq!(secret.len(), 64, "secret should be 64 hex chars");
690+
// File should exist with the same content
691+
let on_disk =
692+
std::fs::read_to_string(dir.path().join(PREVIEW_GATEWAY_SECRET_FILE)).unwrap();
693+
assert_eq!(on_disk, secret);
694+
}
695+
696+
#[test]
697+
fn ensure_shared_secret_is_idempotent() {
698+
let dir = TempDir::new().unwrap();
699+
let first = ensure_shared_secret(dir.path()).unwrap();
700+
let second = ensure_shared_secret(dir.path()).unwrap();
701+
assert_eq!(first, second, "second call should return the same secret");
702+
}
703+
704+
#[test]
705+
fn ensure_shared_secret_rejects_empty_file() {
706+
let dir = TempDir::new().unwrap();
707+
let path = dir.path().join(PREVIEW_GATEWAY_SECRET_FILE);
708+
std::fs::write(&path, "").unwrap();
709+
let result = ensure_shared_secret(dir.path());
710+
assert!(result.is_err(), "empty secret file should be an error");
711+
}
712+
713+
#[cfg(unix)]
714+
#[test]
715+
fn ensure_shared_secret_sets_restrictive_perms() {
716+
use std::os::unix::fs::PermissionsExt;
717+
let dir = TempDir::new().unwrap();
718+
let _ = ensure_shared_secret(dir.path()).unwrap();
719+
let meta = std::fs::metadata(dir.path().join(PREVIEW_GATEWAY_SECRET_FILE)).unwrap();
720+
let mode = meta.permissions().mode() & 0o777;
721+
assert_eq!(mode, 0o600, "secret file should be 0600, got {:o}", mode);
722+
}
723+
724+
#[test]
725+
fn ensure_shared_secret_creates_parent_dirs() {
726+
let dir = TempDir::new().unwrap();
727+
let nested = dir.path().join("a").join("b").join("c");
728+
let secret = ensure_shared_secret(&nested).unwrap();
729+
assert_eq!(secret.len(), 64);
730+
assert!(nested.join(PREVIEW_GATEWAY_SECRET_FILE).exists());
731+
}
732+
}

0 commit comments

Comments
 (0)