Security
A3S Code exposes security controls at session creation time and through
session lifecycle hooks. Treat direct host calls such as session.tool() and
session.bash() as privileged host operations: the application that calls them
is responsible for deciding whether to expose that power to a user.
Delegated child runs intersect their local permissions with the parent
permission checker and inherit the parent process sandbox. A child can narrow
its capabilities but cannot replace the host boundary. Child-local approval
behavior applies only to an Ask introduced by the child policy; a parent
Ask, or a tool-owned escalation after both policies allow, remains under the
parent confirmation provider. Missing providers fail closed before a HITL
request is emitted. Keep high-risk release or publish commands behind explicit
policy.
A permission prompt should explain the operation, reason, scope, and risk—not just present two buttons.
Enforced Execution Boundary
Permission policy decides whether an operation is allowed, denied, or needs
approval. It does not, by itself, confine a process. Every local A3S Code
session binds a concrete native BashSandbox before capability construction.
The same handle reaches direct tools, model-governed tools, workflows, Skills,
and delegated runs. Hosts use SessionOptions::with_sandbox_handle only to
replace this default with an equivalent boundary; non-local workspace backends
retain their explicit command-runner contract.
Core and the Code TUI use the A3S-owned a3s-sandbox Rust library (pinned to
0.2.1) through its NativeBashSandbox adapter. The adapter starts from the
library's a3s_bash_baseline policy, whose network profile denies all egress.
It selects the native boundary for the host
platform—Seatbelt on macOS, Bubblewrap namespaces plus seccomp on Linux, and
AppContainer plus a kill-on-close Job Object on Windows—and probes that
boundary before enabling Bash. No Node.js, npm package, or global runtime
installation is required or selected.
Capability probing is necessary before readiness. macOS requires the system
/usr/bin/sandbox-exec; Linux requires /usr/bin/bwrap and permitted
unprivileged user namespaces; Windows uses PowerShell 7 from the system
Program Files directory and the native AppContainer APIs. The TUI runs a
bounded command through the actual OS boundary before enabling its deferred
handle. Failure marks that handle unavailable, so Default can request one exact
escalated host invocation and Auto denies Bash. Embedded Core sessions also
fail closed: initialization failure installs an error-only handle instead of
falling back to the local workspace runner, and default bash on a governed
local run without a configured sandbox is refused.
The adapter fails closed and never silently retries on the host. It denies
network egress, local binding, and Unix sockets; limits writes to the workspace
and a private scratch directory; protects the top-level .git, .a3s,
.agents, .codex, .claude, .vscode, and .idea directories and the
.gitmodules, .mcp.json, .ripgreprc, .bashrc, .bash_profile, .zshrc,
.zprofile, and .profile control files; masks common credential stores;
and scrubs ambient secrets. Delegated tasks, Skills, and workflow steps retain
the same handle.
Process-host opt-in
Some deployments already run A3S Code inside an outer container or VM (for
example Harbor or Terminal-Bench) where the native sandbox cannot initialize.
Only there, a host may set allow_process_host_sandbox (Rust
SessionOptions::with_allow_process_host_sandbox(true), Node.js
allowProcessHostSandbox, Python allow_process_host_sandbox, Go
AllowProcessHostSandbox) or the environment variable
A3S_CODE_ALLOW_PROCESS_HOST_SANDBOX (1, true, yes, or on). When native
initialization fails, the session then runs bash -c directly on the host with
process-group cleanup and bounded output instead of installing the error-only
handle. This removes the local boundary; the outer isolation becomes the only
one. A host-supplied sandbox_handle always takes precedence, and non-local
workspace backends keep their own command runner.
Network grants
The baseline has no network. Code does not expose a broader network profile or
a general mediated-HTTP option. The only way to reach the network from sandboxed
bash is a per-call grant: the tool call sets
sandbox_permissions: "request_network_grant" with
network_grant: { host, port }. The host must be an exact name (no wildcards;
localhost and 127.0.0.1 are different hosts) and the port is optional. The
call requires confirmation. When approved, the sandbox enables mediated access
for exactly that origin, pinned to the current policy digest; a stale digest is
refused and the grant is recorded in the result metadata as network_grant
with host, port, and policy_digest. A call with
sandbox_permissions: "require_escalated" must include a justification and
also requires confirmation before it runs outside the sandbox.
In-process file tools and credentials
Because built-in file tools execute in-process, the TUI separately enables Core's local workspace credential policy. It covers direct and range reads, writes, edits, patches, and both manifest-backed and fallback grep. Explicit sensitive paths fail closed, broad grep omits protected candidates, and source-tree hardlink aliases are denied before mutation. Ordinary package-store hardlinks remain usable unless they alias a discovered credential inode. Read-only Git diff regenerates output only for allowed changed paths, option-like revisions cannot become Git flags, and displayed remotes omit embedded HTTP credentials and query tokens.
The a3s-sandbox library runs its enforcement tests on macOS, Linux, and
Windows; the Code release workflow runs Core's library tests, including the
native Bash sandbox tests, on Linux and Windows before publishing. The tests
check that normal workspace writes and offline toolchain commands remain
usable, while outside and symlink writes, protected metadata mutations,
credential reads, network egress, local listeners, and Unix sockets remain
blocked.
The terminal execution modes apply this boundary as follows:
Threat Model
The protected assets are host files outside the active workspace, repository and agent-control metadata, credential stores, host network and listener capabilities, process lifetime, and the user's authority to approve a specific operation. Model output, repository content, command output, fetched content, Skill instructions, and MCP results are untrusted inputs. A malicious dependency or child process is assumed able to close output pipes early, fork descendants, create symlinks, inspect its environment, and attempt filesystem, socket, or network escapes.
The trusted computing base is the running CLI/Core binary, the verified release support tree, the selected sandbox provider and operating-system enforcement, and host code that invokes direct SDK helpers. A configured MCP server or native integration is a separately trusted extension; missing or unsafe behavior annotations cause confirmation rather than granting read-only status.
Trust does not waive process ownership. Each local stdio MCP server leads a dedicated Unix process group. Closing or dropping its transport stops pipe tasks, clears pending requests, reaps the leader, and terminates descendants. The stderr pipe is drained independently so diagnostics cannot stall protocol traffic.
This local boundary does not claim kernel-level protection against a compromised sandbox provider, CLI binary, or operating system. It also does not turn the privileged host-direct SDK into end-user authorization. Use a stronger workload provider for hostile multi-tenant code, kernel attack resistance, or OCI-level isolation.
Invocation Ingress
Every session-owned path enters one scoped invocation kernel:
Low-level ToolRegistry and standalone ProgramExecutor APIs are deliberately
ungoverned building blocks for hosts that own the registry. AgentSession and
the TUI do not use those APIs as a fallback.
Complete TUI Decision Matrix
The cells below are the terminal outcomes for model and ordinary governed nested invocations. “Deny” means no confirmation event is created. “Confirm once” means one exact invocation ID is pending; cancelling or expiring it cannot settle another prompt.
Origin modifies that matrix only as follows:
Decision precedence is fail-closed: active-Skill restrictions and hard
guardrails first, then hook blocks, origin authority, mode/permission policy,
tool-owned escalation, confirmation availability, and finally execution.
Terminal policies always use TimeoutAction::Reject; Core's generic
AutoApprove option is not accepted by the TUI.
Treat the native sandbox as a local enforcement provider, not as the stack-wide
execution contract. A3S Code owns agent policy and its BashSandbox interface.
A3S Runtime owns durable, provider-neutral Task and Service lifecycle and
placement. A3S Box owns OCI and stronger isolation. A3S Observer and A3S Sentry
can add execution evidence and adaptive runtime enforcement as defense in
depth; they do not replace the deterministic per-process sandbox boundary.
Secure Downloads
download is a bounded workspace mutation, not an unrestricted network or
filesystem primitive. It is registered only when the session has a writable
local workspace. Model-selected calls pass through the same permission policy,
HITL confirmation, hooks, timeout, cancellation, and workspace checks as other
mutations. Direct session.tool('download', ...) calls remain privileged host
operations and require authorization in the embedding application.
Its network boundary accepts only HTTP(S), rejects user information and non-public targets, and revalidates every bounded redirect hop. Direct connections reject DNS answers containing any private or otherwise reserved address and pin the validated public addresses for that hop. Cross-origin redirects drop credentials and resource validators. Explicit proxy mode leaves hostname resolution to the configured proxy but retains literal-host and redirect checks.
Signed query parameters are preserved because object stores and release systems
need them to authorize the request. They are removed from diagnostics and
source_anchors, so successful and failed tool results do not expose those
secrets in metadata.
The destination must remain below the local workspace and may not cross
symlinks. Content is streamed to an adjacent temporary file under byte and time
limits. Strict Range validation, optional expected_sha256, sync-before-promote,
and atomic replacement keep incomplete or unverified data away from the final
path. Cancellation and failure remove the temporary file; overwrite defaults
to false.
See Tools for the complete parameter contract.
Permission Policy
Rules use Tool(pattern) or a bare Tool name and match tool names
case-insensitively; tool globs such as mcp__github__* are supported. The
argument pattern is matched against one string per tool: the command for
bash, file_path for read/write/edit/download, <mode> <query> <path>
for search, and serialized JSON arguments otherwise. A pattern must match the
whole string; * stops at /, ** crosses it, a trailing :* is a prefix
match, and a bare * matches any arguments. Use bash(rm -rf**), not
bash(rm -rf*), so the rule also matches rm -rf /tmp/x. search(grep **)
covers only grep-mode searches. Rules naming grep, glob, or bm25 are
rewritten to that mode of search (grep(*) becomes search(grep **)); they
never authorize the other modes.
Evaluation order is deny, then allow, then ask, then defaultDecision
(core/src/permissions/policy.rs). An allow match therefore wins over an
ask match for the same call: keep an ask pattern out of any broader allow
pattern you want it to guard. defaultDecision defaults to ask, and
enabled: false allows everything.
Avoid permissive defaults for release or production sessions. Make dangerous commands explicit and auditable.
Confirmation
An ask decision, a tool that declares it needs confirmation (for example
bash with require_escalated or request_network_grant), and annotated
external side effects all become one pending confirmation for one exact
invocation. If no confirmation provider is available, the call fails closed
before a HITL request is emitted. Delegated children keep the parent's
confirmation provider for a parent Ask and for tool-owned escalation.
Hooks
Hooks are registered on a session with an event type, matcher, optional config, and handler:
Verification
A turn that changed the workspace cannot complete on assistant text. The completion gate requires a Passed verification report bound to the turn's mutation effect digest, or a host waiver for that digest; see Verification. Verification reports and summaries are available from the session and selected result fields. Release workflows should require tests, package checks, CI checks, and provider evidence.