Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/sandlock-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,8 @@ async fn run_command(args: RunArgs) -> Result<i32> {
if !pb.extra_deny_syscalls.is_empty() { builder = builder.extra_deny_syscalls(pb.extra_deny_syscalls.clone()); }
for rule in &pb.http_allow { builder = builder.http_allow(rule); }
for rule in &pb.http_deny { builder = builder.http_deny(rule); }
for c in &pb.credentials { builder = builder.credential_spec(c); }
for rule in &pb.http_inject { builder = builder.http_inject(rule); }
for port in &pb.http_ports { builder = builder.http_port(*port); }
if let Some(ref ca) = pb.http_ca { builder = builder.http_ca(ca); }
if let Some(ref key) = pb.http_key { builder = builder.http_key(key); }
Expand Down
12 changes: 12 additions & 0 deletions crates/sandlock-core/src/checkpoint/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,18 @@ fn parse_fdinfo(pid: i32, fd: i32) -> io::Result<(i32, u64)> {
/// Capture a checkpoint from a running, stopped sandbox.
/// The sandbox must already be frozen (SIGSTOP'd and fork-held).
pub(crate) fn capture(pid: i32, policy: &Sandbox) -> Result<Checkpoint, SandlockError> {
// Credential-injection rules hold supervisor-only secrets that are
// deliberately not serialized (`#[serde(skip)]`). A checkpoint image would
// therefore restore with no injection rules and send every request
// uncredentialed — reject rather than silently drop them.
if !policy.inject.is_empty() {
return Err(SandlockError::Runtime(SandboxRuntimeError::Child(
"checkpoint is not supported with credential injection (--http-inject); \
the injected secrets cannot be serialized into the image"
.into(),
)));
}

// Seize via ptrace (PTRACE_SEIZE + PTRACE_INTERRUPT -- doesn't auto-SIGSTOP)
ptrace_seize(pid).map_err(|e| {
SandlockError::Runtime(SandboxRuntimeError::Child(format!("ptrace seize: {}", e)))
Expand Down
9 changes: 9 additions & 0 deletions crates/sandlock-core/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,15 @@ pub(crate) fn confine_child(args: ChildSpawnArgs<'_>) -> ! {
std::env::remove_var(&key);
}
}
// Remove env vars whose value was loaded into the supervisor as a credential
// source, so the agent can't read the real secret straight from its own
// environment (this is the child; the mutation is process-local and post-fork).
// This runs *before* applying `sandbox.env`, so the inherited real secret is
// dropped but a deliberate placeholder the user passes (e.g.
// `--env OPENAI_API_KEY=dummy`, which SDKs need set to start) still survives.
for name in &sandbox.inject_env_strip {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This strip runs after the sandbox.env set loop, so it also deletes an explicitly passed placeholder (--env OPENAI_API_KEY=dummy). SDKs refuse to start without the var set, which is exactly the placeholder flow the Replace default is designed for, so the canonical env: workflow breaks before any request is made. Strip the inherited env first, then apply sandbox.env: the inherited real secret is still removed and deliberate user config is honored.

std::env::remove_var(name);
}
for (key, value) in &sandbox.env {
std::env::set_var(key, value);
}
Expand Down
Loading
Loading