use std::sync::Arc;
use forge_sandbox::{SandboxConfig, SandboxExecutor, ToolDispatcher};
struct StubDispatcher;
#[async_trait::async_trait]
impl ToolDispatcher for StubDispatcher {
async fn call_tool(
&self,
_server: &str,
_tool: &str,
_args: serde_json::Value,
) -> Result<serde_json::Value, forge_error::DispatchError> {
Ok(serde_json::json!({"status": "ok"}))
}
}
fn test_executor() -> SandboxExecutor {
SandboxExecutor::new(SandboxConfig::default())
}
#[tokio::test]
async fn sec_e2e_01_url_encoded_traversal_blocked() {
use forge_sandbox::validator::validate_code;
let code = r#"async () => { return String.raw`../../../etc/passwd`; }"#;
let result = validate_code(code, None);
assert!(result.is_err(), "String.raw should be blocked by validator");
let executor = test_executor();
let dispatcher: Arc<dyn ToolDispatcher> = Arc::new(StubDispatcher);
let result = executor.execute_code(code, dispatcher, None, None).await;
assert!(result.is_err(), "String.raw should be blocked in pipeline");
let err = result.unwrap_err().to_string();
assert!(
err.contains("banned") || err.contains("String.raw"),
"should mention banned pattern: {err}"
);
}
#[tokio::test]
async fn sec_e2e_02_invalid_stash_keys_blocked() {
let executor = test_executor();
let dispatcher: Arc<dyn ToolDispatcher> = Arc::new(StubDispatcher);
let code = r#"async () => { return "valid-stash-key"; }"#;
let result = executor.execute_code(code, dispatcher, None, None).await;
assert!(
result.is_ok(),
"simple valid code should pass: {:?}",
result
);
}
#[tokio::test]
async fn sec_e2e_03_redacted_errors_dont_leak_credentials() {
use forge_sandbox::redact::redact_error_for_llm;
let msg = "auth error: key AKIAIOSFODNN7EXAMPLE rejected";
let result = redact_error_for_llm("server", "tool", msg);
assert!(
!result.contains("AKIAIOSFODNN7"),
"AWS key leaked: {result}"
);
assert!(
result.contains("[REDACTED]"),
"should be redacted: {result}"
);
let msg = "token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U expired";
let result = redact_error_for_llm("server", "tool", msg);
assert!(!result.contains("eyJhbGci"), "JWT leaked: {result}");
let msg = "invalid token ghp_ABCDEFGHIJKLMNOPQRSTuvwxyz1234";
let result = redact_error_for_llm("server", "tool", msg);
assert!(
!result.contains("ghp_ABCDE"),
"GitHub token leaked: {result}"
);
}
#[tokio::test]
async fn sec_e2e_04_validator_blocks_string_raw() {
let executor = test_executor();
let dispatcher: Arc<dyn ToolDispatcher> = Arc::new(StubDispatcher);
let code = r#"async () => { return String.raw`template`; }"#;
let result = executor
.execute_code(code, dispatcher.clone(), None, None)
.await;
assert!(result.is_err(), "String.raw should be blocked");
let code2 = r#"async () => { return typeof WebAssembly; }"#;
let result2 = executor
.execute_code(code2, dispatcher.clone(), None, None)
.await;
assert!(result2.is_err(), "WebAssembly should be blocked");
let code3 = r#"async () => { return Symbol.toPrimitive; }"#;
let result3 = executor.execute_code(code3, dispatcher, None, None).await;
assert!(result3.is_err(), "Symbol.toPrimitive should be blocked");
}
#[tokio::test]
async fn sec_e2e_05_ipc_message_size_limit() {
use forge_sandbox::ipc::{read_message_with_limit, write_message, ChildMessage};
let msg = ChildMessage::Log {
message: "x".repeat(2048),
};
let mut buf = Vec::new();
write_message(&mut buf, &msg).await.unwrap();
let mut cursor = std::io::Cursor::new(buf);
let result: Result<Option<ChildMessage>, _> = read_message_with_limit(&mut cursor, 64).await;
assert!(result.is_err(), "oversized message should be rejected");
let err = result.unwrap_err().to_string();
assert!(
err.contains("too large"),
"error should mention size: {err}"
);
assert_eq!(
forge_sandbox::ipc::DEFAULT_MAX_IPC_MESSAGE_SIZE,
65 * 1024 * 1024,
"default IPC limit should be 65 MB"
);
}