Skip to content

Commit 7d7e7e1

Browse files
authored
Merge pull request #27 from A3S-Lab/fix/srt-profile-e2big-20260723
fix(srt): support file-backed macOS profiles
2 parents 2ffb777 + c16f588 commit 7d7e7e1

4 files changed

Lines changed: 120 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Selected sandbox-runtime 0.0.67 for managed hosts.
13+
14+
### Fixed
15+
16+
- Pinned Unix managed-SRT temporary files to the private per-command scratch
17+
directory so hosts can pass large macOS Seatbelt profiles by file without
18+
exceeding the operating system argument-size limit.
19+
1020
## [6.4.1] - 2026-07-23
1121

1222
### Fixed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,9 @@ when its explicitly provisioned runtime is missing or fails. Write-deny paths
326326
already covered by a protected ancestor are collapsed before SRT startup,
327327
while more-specific credential read denies remain intact. Workspace policy
328328
scans treat an entry removed concurrently after enumeration as absent, but
329-
permission and other I/O failures remain fatal. The embedding host remains
329+
permission and other I/O failures remain fatal. Unix wrapper temporary files
330+
are pinned to the same private per-run scratch directory so a lifecycle host
331+
can pass large native sandbox policies by file. The embedding host remains
330332
responsible for choosing whether an unavailable sandbox causes an interactive
331333
escalation or a deterministic denial.
332334

core/src/sandbox/srt.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub const SRT_NPM_PACKAGE_NAME: &str = "@anthropic-ai/sandbox-runtime";
2828
/// Core accepts the tested compatibility range below so a host can roll a
2929
/// compatible patch independently. The CLI deliberately installs one exact
3030
/// version until an A3S-signed component artifact replaces registry bootstrap.
31-
pub const MANAGED_SRT_VERSION: &str = "0.0.66";
31+
pub const MANAGED_SRT_VERSION: &str = "0.0.67";
3232
const MINIMUM_SRT_VERSION: (u64, u64, u64) = (0, 0, 66);
3333
const MAXIMUM_SRT_VERSION_EXCLUSIVE: (u64, u64, u64) = (0, 1, 0);
3434

@@ -611,8 +611,7 @@ fn compose_srt_process_env(
611611
#[cfg(not(windows))]
612612
{
613613
let _ = explicit;
614-
let _ = scratch;
615-
Ok(compose_wrapper_env(workspace))
614+
Ok(compose_wrapper_env(workspace, scratch))
616615
}
617616
#[cfg(windows)]
618617
{
@@ -628,7 +627,7 @@ fn compose_srt_process_env(
628627
}
629628

630629
#[cfg(not(windows))]
631-
fn compose_wrapper_env(workspace: &Path) -> HashMap<OsString, OsString> {
630+
fn compose_wrapper_env(workspace: &Path, scratch: &Path) -> HashMap<OsString, OsString> {
632631
const SAFE_KEYS: &[&str] = &[
633632
"HOME",
634633
"USER",
@@ -656,6 +655,10 @@ fn compose_wrapper_env(workspace: &Path) -> HashMap<OsString, OsString> {
656655
if let Some(path) = trusted_wrapper_path(workspace) {
657656
environment.insert(OsString::from("PATH"), path);
658657
}
658+
let scratch = scratch.as_os_str().to_os_string();
659+
environment.insert(OsString::from("TMPDIR"), scratch.clone());
660+
environment.insert(OsString::from("TMP"), scratch.clone());
661+
environment.insert(OsString::from("TEMP"), scratch);
659662
remove_bootstrap_injection_variables(&mut environment);
660663
environment
661664
}

core/src/sandbox/srt/tests.rs

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,22 @@ fn child_environment_drops_ambient_secrets_and_pins_scratch_paths() {
270270
);
271271
}
272272

273+
#[cfg(unix)]
274+
#[test]
275+
fn wrapper_environment_pins_profile_files_to_the_private_scratch_directory() {
276+
let workspace = tempfile::tempdir().unwrap();
277+
let scratch = tempfile::tempdir().unwrap();
278+
let environment = compose_srt_process_env(None, scratch.path(), workspace.path()).unwrap();
279+
280+
for key in ["TMPDIR", "TMP", "TEMP"] {
281+
assert_eq!(
282+
environment.get(OsStr::new(key)),
283+
Some(&scratch.path().as_os_str().to_os_string()),
284+
"{key} must keep managed SRT profile files inside the per-run scratch directory"
285+
);
286+
}
287+
}
288+
273289
#[test]
274290
fn child_environment_rejects_explicit_bootstrap_injection_variables() {
275291
let scratch = tempfile::tempdir().unwrap();
@@ -314,7 +330,7 @@ fn child_environment_rejects_explicit_bootstrap_injection_variables() {
314330

315331
#[test]
316332
fn supported_srt_version_range_is_explicit() {
317-
for version in ["0.0.66", "v0.0.66", "0.0.99-beta.1"] {
333+
for version in ["0.0.66", "0.0.67", "v0.0.67", "0.0.99-beta.1"] {
318334
ensure_supported_srt_version(version).unwrap();
319335
}
320336
for version in ["0.0.65", "0.1.0", "1.0.0", "unknown"] {
@@ -517,6 +533,89 @@ async fn real_srt_probe_survives_concurrent_workspace_churn() {
517533
result.unwrap();
518534
}
519535

536+
/// Run explicitly with `A3S_TEST_SRT_BIN=/absolute/path/to/cli.js` and
537+
/// `A3S_TEST_SRT_NODE=/absolute/path/to/node`.
538+
#[tokio::test]
539+
#[ignore = "requires an installed A3S-patched srt runtime and Node.js"]
540+
async fn real_srt_probe_handles_many_nested_sensitive_paths_without_e2big() {
541+
let binary = std::env::var_os("A3S_TEST_SRT_BIN")
542+
.map(PathBuf::from)
543+
.expect("set A3S_TEST_SRT_BIN");
544+
let node = std::env::var_os("A3S_TEST_SRT_NODE")
545+
.map(PathBuf::from)
546+
.expect("set A3S_TEST_SRT_NODE");
547+
let workspace = tempfile::tempdir().unwrap();
548+
549+
for directory in 0..128 {
550+
let nested = workspace
551+
.path()
552+
.join(format!("service-{directory:03}/config"));
553+
std::fs::create_dir_all(&nested).unwrap();
554+
for variant in 0..4 {
555+
std::fs::write(
556+
nested.join(format!(".env.variant-{variant}")),
557+
b"SECRET=hidden",
558+
)
559+
.unwrap();
560+
}
561+
}
562+
563+
let sandbox =
564+
SrtBashSandbox::from_verified_npm_with_node(&binary, &node, workspace.path()).unwrap();
565+
let output = sandbox
566+
.exec_command("printf a3s-managed-srt-ready", "/workspace")
567+
.await
568+
.unwrap();
569+
570+
assert_eq!(
571+
output.exit_code, 0,
572+
"large managed SRT profile failed: {}{}",
573+
output.stdout, output.stderr
574+
);
575+
assert_eq!(output.stdout, "a3s-managed-srt-ready");
576+
}
577+
578+
/// Run explicitly with `A3S_TEST_SRT_BIN=/absolute/path/to/cli.js` and
579+
/// `A3S_TEST_SRT_NODE=/absolute/path/to/node`.
580+
#[cfg(unix)]
581+
#[tokio::test]
582+
#[ignore = "requires an installed A3S-patched srt runtime and Node.js"]
583+
async fn real_srt_probe_handles_a_large_hardlink_profile_without_e2big() {
584+
let binary = std::env::var_os("A3S_TEST_SRT_BIN")
585+
.map(PathBuf::from)
586+
.expect("set A3S_TEST_SRT_BIN");
587+
let node = std::env::var_os("A3S_TEST_SRT_NODE")
588+
.map(PathBuf::from)
589+
.expect("set A3S_TEST_SRT_NODE");
590+
let root = tempfile::tempdir().unwrap();
591+
let workspace = root.path().join("workspace");
592+
std::fs::create_dir_all(&workspace).unwrap();
593+
let outside = root.path().join("outside-secret");
594+
std::fs::write(&outside, "outside-secret").unwrap();
595+
for index in 0..1_024 {
596+
std::fs::hard_link(
597+
&outside,
598+
workspace.join(format!(
599+
"source-tree-hardlink-alias-with-a-deliberately-long-name-{index:04}.txt"
600+
)),
601+
)
602+
.unwrap();
603+
}
604+
605+
let sandbox = SrtBashSandbox::from_verified_npm_with_node(&binary, &node, &workspace).unwrap();
606+
let output = sandbox
607+
.exec_command("printf a3s-managed-srt-ready", "/workspace")
608+
.await
609+
.unwrap();
610+
611+
assert_eq!(
612+
output.exit_code, 0,
613+
"large managed SRT profile failed: {}{}",
614+
output.stdout, output.stderr
615+
);
616+
assert_eq!(output.stdout, "a3s-managed-srt-ready");
617+
}
618+
520619
/// Run explicitly with `A3S_TEST_SRT_BIN=/absolute/path/to/srt`.
521620
#[tokio::test]
522621
#[ignore = "requires an installed srt runtime"]

0 commit comments

Comments
 (0)