YCode sandbox, protocol 1
This directory contains YCode's Windows x64 execution backend. The implementation uses public Windows APIs and the Microsoft SDK. WorkBuddy binaries, services, profiles, hooks and IPC pipes are not runtime or build dependencies.
Build and verify
Prerequisites: Windows x64, Node/npm, Visual Studio C++ Build Tools, and a Windows
10/11 SDK. The build discovers the installed compiler with vswhere; it does not
install or modify the toolchain.
npm run sandbox:build
npm run test:sandbox
npm run sandbox:maintain -- status
npm run sandbox:maintain -- self-test
npm run test:sandbox:app
npm run test:sandbox:policy
npm run test:sandbox:toolchains
npm run test:sandbox:directories
npm run test:sandbox:write
npm run test:sandbox:workspace
npm run test:sandbox:feasibility
npm run test:sandbox:stdio
sandbox:build compiles runner.cpp with MSVC C++17, /W4 /WX /MT /Brepro.
Generated artifacts are resources/sandbox/win32-x64/ycode-sandbox.exe and
manifest.json. The manifest records the source/binary SHA-256, compiler, SDK,
architecture and protocol. Generated binaries are ignored by Git. npm run build
builds the runner before checking TypeScript and building Electron. Packaging
copies the runner, pinned adapters/manifests and bundled Git; test-only probe
executables are excluded.
test:sandbox executes real native processes in test-owned temporary directories.
test:sandbox:app builds and runs the actual production main/preload/renderer,
real OMP RPC, and a deterministic localhost model. It uses separate home/userData,
hides its own windows, and exercises approval, rejection and cancellation.
Ownership and execution
runner.cpp: AppContainer profile, SID/token verification, explicit stdio handle inheritance, restricted child environment, Job, timeout/parent monitor, temporary drive, lease and native result protocol.src/main/sandbox/policy.ts: workspace containment, sensitive names, link/device/alternate stream checks.src/main/sandbox/service.ts: executable identity, bounded workspace snapshot, native process lifecycle, diff, approval and audit.workspace-state.h/workspace.ts/workspace-service.ts: host-only ACL sealing, persistent session drafts, immutable checkpoints, synchronization, approval-based commit and explicit recovery.src/main/sandbox/transactions.ts: independent before/after backups, batch conflict checks, compensation and explicit recovery.src/main/sandbox/maintenance.ts: lease validation, orphan cleanup and record inventory.policy-store.ts/authorizations.ts: versioned global/workspace policy, compare-and-swap updates, single-use approval and policy revocation.toolchains.ts/dependency-cache.ts: explicit content-verified runtime imports and immutable workspace/lock/runtime-bound dependency layers.execution.ts: bounded admission and sequential execution per workspace.src/main/agent/conversation-tools.ts: common OMP and fallback execution path.
Default execute_command creates a fresh snapshot and unique
YCode.Run.<32 hexadecimal characters> AppContainer profile. No network
capabilities are granted. The runner verifies the suspended child's AppContainer
flag and exact SID, assigns the Job, then resumes it. Only stdio handles are
inherited. The Job forbids breakaway, limits active processes to 32 and aggregate
committed memory to 1 GiB, and kills remaining descendants when the root finishes.
The native runner also watches its launch parent and accepts cancel\n on stdin.
A free per-user DOS drive maps to this generated stage. This makes drive-root queries by cmd and PowerShell operate on the scratch root. The allocator has a named mutex and uses exact-target removal. The mapping and profile are removed before a successful native result. The original workspace ACL is not changed.
The child can modify its scratch copy. Only a successful command can propose host
file changes. A real conversation approval precedes writeback. All targets are
checked against the initial hashes and rechecked while committing. Independent
backups are persisted before the first host write. Rejected, failed, cancelled,
conflicting or invalid changes are not committed. A failed compensation is
reported with its transaction ID and recovery-required state. Audit failure
after a successful commit preserves the actual committed outcome.
State and maintenance
Production state is under app.getPath('userData')/sandbox. The actual app API
window.ycode.app.sandboxStatus() reports installed/unavailable backend identity;
installed means the binary manifest is valid. A native self-test or execution
is required to verify that the OS can start the isolation boundary.
Each runs/<uuid>/ contains request metadata, native lease.json/result.json,
audit.json or failure.json, and cleanup.json. Raw stdout/stderr and environment
credentials are excluded from the audit. Scratch stages are removed after runs;
cleanup status records a failure if the OS still holds a file. The transactions/
directory retains the manifest and independent .before/.after contents.
Stateful maintenance requires an explicit absolute state directory. It does not guess or switch to another application's storage.
npm run sandbox:maintain -- audit --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- transactions --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- restore TRANSACTION_UUID --workspace 'C:\absolute\workspace' --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- cleanup --state-root 'C:\absolute\userData\sandbox'
Recovery checks the expected workspace, backup hashes, and current file hashes.
It refuses to overwrite a later external edit. Interrupted prepared/applying
transactions remain inspectable and recoverable. Restoring an already restored
transaction is harmless. cleanup checks a lease's run identity and PID, skips
live runners, and removes an orphaned profile/drive only for the recorded exact
target. It preserves transaction backups and audit history.
Policies, runtimes and dependencies
Policy defaults preserve the original snapshot boundary. Maintenance can tighten entry/file/byte limits, disable commands, deny writeback and add relative denied paths. Workspace overrides cannot relax global restrictions. Updates require the current revision and an exclusive policy lock. Corrupt/unknown policies fail closed. Policy changes cancel active commands and pending conversation approvals; an approved ticket is scoped to its exact change batch and can be consumed once. Bindings include a content fingerprint as well as the revision, so manual edits that keep a revision do not preserve old permissions or self-test observations.
npm run sandbox:maintain -- policy --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- policy-set --expected-revision REVISION --rules 'C:\absolute\rules.json' --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- toolchain-add node --source 'C:\absolute\node.exe' --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- toolchain-add python --source 'C:\absolute\python.exe' --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- toolchain-add git --source 'C:\Program Files\Git\cmd\git.exe' --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- git-state --workspace 'C:\absolute\project' --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- git-reset --workspace 'C:\absolute\project' --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- dependency-add node --workspace 'C:\absolute\project' --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- dependency-add python --workspace 'C:\absolute\project' --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- operation RUN_UUID --state-root 'C:\absolute\userData\sandbox'
Import selects explicit local runtime bytes; it never executes the source interpreter on the host. Node includes the installed sibling npm distribution when present. Python includes its version DLL, standard library, extension DLLs and installed pip when present. Sensitive configuration and test/cache files are filtered; no global PATH or credentials are inherited. Imported runtimes have independent file-count/size limits, manifests and pinned hashes. Registration is metadata availability; every requested file is verified before actual execution.
Model commands select runtimes: ['node'], ['python'] or ['git']. A dependency import
reads only that workspace's node_modules or .venv/Lib/site-packages, rejects
links and hard-linked files, and binds the layer to workspace, project/lockfile
hashes and runtime identity. Commands opt in with dependencies: ['node'] or
['python']. Cached dependencies are copied to the private stage and protected by
native read/execute ACLs. Node keeps the actual node_modules layout for CommonJS
and ESM. Python uses a private PYTHONPATH. Dependency contents are never promoted
as ordinary generated changes; cache updates require explicit import. A changed
lockfile/runtime or a corrupted cache rejects execution. Original dependency
trees may change after import; the pinned layer changes only on explicit reimport.
The following paragraph records the original 0.4.0 Git-only snapshot scope; host-Git import/writeback and transport were added in component 0.7.0 below.
Git is bundled by default; users do not install or register it. Maintainers run
npm run sandbox:vendor-git once when preparing a source checkout. The pinned
official MinGit archive and deterministic runtime manifest are checked before
an offline normal build; licenses and source-provenance notices ship with it.
An optional explicit import overrides the bundle, without silent fallback if
that override is damaged. The importer reads the real MinGW/UCRT x64 image and
its PE DLL dependency closure without executing source programs on the host.
In the original 0.4.0 scope, Git used a persistent private repository
whose initial commit contains the filtered workspace snapshot. Host .git,
history, index, credentials and hooks are never imported or written back. Thus
private status/diff/log do not describe the original host repository. Repository
changes require approval, bind the exact candidate and policy, and reject
approval-time workspace edits. Read-only status/diff/log do not save stat caches.
Git metadata uses its own versioned state outside the worktree; normal file
writeback retains its existing independent transaction and recovery record.
git.persistence and durable operation queries report private publication
separately: the two steps are not one crash-atomic transaction. All protocols are
disabled by default and the native Job retains zero network capabilities.
This historical snapshot path remains available as a separate mode. See
docs/沙盒Git接入-2026-10-18.md for the original subset.
Bundling, copied/packaged-resource and offline reproducibility checks use
npm run test:sandbox:bundled-git; details are recorded in
docs/沙盒内置Git分发-2026-10-18.md.
Git host and remote integration (component 0.7.0)
The complete common Git path is documented in docs/沙盒Git完整接入-2026-10-18.md
and its acceptance matrix. sandbox_git can explicitly import a real host
repository (including an existing linked worktree), run the supported local Git
workflow in the AppContainer draft, and publish project files plus safe Git
metadata through coordinated native transactions. sandbox_workspace routes
commit/discard/recover through the same host-Git binding when one exists.
Import is approval-bound and preserves real objects, HEAD, index, refs, tags,
stash state and allowed configuration without executing host hooks, aliases,
filters, credential helpers or external object paths. New repositories remain
unborn until a real first commit. Clone/fetch/pull/push use the trusted host
transport control plane with fixed Git, explicit HTTPS/SSH credentials, TLS and
known-host verification, per-target approvals and durable unknown outcomes.
The ordinary command Job still has no network capability. git-cleanup removes
only abandoned control roots after checking both the host owner record and native
lease. This is a Windows x64 engineering acceptance, not a claim of complete
Git subcommand compatibility, crash-atomic multi-root publication, or WorkBuddy
1:1 equivalence.
Persistent session drafts
Component 0.6.0 adds workspaceState: 1. Commands explicitly select
workspace: 'persistent' to retain a project/session-bound draft. A successful
command saves private files without writing the host. sandbox_workspace
supports status, changes, commit, discard and recover, with an optional
expectedRevision. Commit uses the existing conversation approval and native v3
host transaction. Explicit snapshot or persistent selection always confines
the command, even in a full-access tool context. Omitting the selector retains
the existing permission behavior.
Normal successful commands retain the same physical NTFS worktree identity by
moving it into and out of the run stage. The host-only --seal-workspace entry
uses relative native directory handles to remove former AppContainer access
before retention. Private hash-addressed blobs and revision manifests remain
host-only, including for later Jobs from the same session. Manifests are capped
at 8 MiB before publication. Root identity and policy changes make old drafts
stale. Three-way synchronization imports unrelated host edits and rejects file
or ancestor conflicts; file/directory type swaps are currently rejected.
Failed, cancelled and timed-out commands restore the saved checkpoint. Explicit
recovery checks the saved run owner, dead host lock and native lease. Commit
intents distinguish an interrupted private update from actual host writes:
metadata failure after host commit still reports committed. The read-only
status path returns busy during execution or approval instead of queueing.
Persistent Git uses its own project/session namespace. Local private Git commits
need no host approval and do not modify host .git. File checkpoint and private
Git publication are separate steps; publication failure is reported separately.
Discard archives both the draft and its active private Git reference. Scoped
stale Git locks are checked during explicit draft recovery. The existing
project-wide snapshot Git behavior is unchanged.
npm run sandbox:maintain -- workspace-changes --workspace 'C:\absolute\project' --session SESSION_ID --state-root 'C:\absolute\userData\sandbox'
npm run sandbox:maintain -- workspace-commit --workspace 'C:\absolute\project' --session SESSION_ID --state-root 'C:\absolute\userData\sandbox' --draft-revision REVISION_UUID --approve
npm run sandbox:maintain -- workspace-recover --workspace 'C:\absolute\project' --session SESSION_ID --state-root 'C:\absolute\userData\sandbox'
The CLI also accepts workspace-status and workspace-discard. Preload exposes
read-only app.sandboxWorkspace(sessionId); main resolves the root from the saved
session and requires the trusted top-level app page. Ordinary file tools and the
read bridge still access the host project; use persistent commands for draft
file access. Processes, home, temp, tools and absolute execution paths are not
persistent. Mutable dependencies and online installs remain pending; imported
read-only dependencies detach before checkpointing. See
docs/沙盒长期工作区接入-2026-10-18.md for verification and recovery limits.
Windows derives an additional private AppContainer TEMP directory from LOCALAPPDATA. The runner provisions that profile-specific path before launch; Node/Python temporary files remain inside the private stage and are cleaned with it. Python 3.13+ gives mkdir(0o700) a user/admin-only DACL, excluding the package SID. The pinned, read-only Python site adapter uses inherited native private ACLs for that mode only within the owned stage. Outside paths retain Python's behavior and OS denial. Isolated/no-site Python flags can bypass this compatibility adapter. For synchronous Node subprocess capture, explicitly preload the owned adapter:
node --require "%YCODE_SANDBOX_PROCESS%" build.cjs
The manifest-pinned node-process.cjs replaces public spawnSync/execFileSync/
execSync (including ESM named exports) with a file-descriptor capture adapter.
Subprocesses still use Node's own spawn and inherit the same token/Job. Inputs
and maxBuffer are capped at 16 MiB; maxBuffer defaults to 1 MiB. Buffer overflow
is detected after completion, not an OS hard disk quota or a claim of early
termination. Scratch handles/files are closed/removed after each call. Explicit
non-pipe stdio configurations pass through. Default Node synchronous pipes still
hang on the tested AppContainer without the adapter; async spawn/fork/IPC and
comprehensive API compatibility remain separate work. Run
npm run test:sandbox:process for the bounded Node/Python regression.
Native operation outcomes are saved independently of verbose audit and chat messages. The operation query returns an unknown/incomplete outcome when no durable host result exists; native process completion does not imply host commit. Self-tests persist their timestamp and exact binary/policy identity. A new binary or policy invalidates old observations. The current self-test verifies command startup/isolation and cleanup, not every network/filesystem/registry capability.
The internal stdioProbe mode remains experimental and accepts bounded echo
requests only. It uses the already bound suspended/verified child and its Job's
inherited pipes, rejects caller-supplied identities, repeated IDs and excessive
frames/concurrency, and forwards replies as bounded base64 control records.
Model-facing tool arguments cannot enable it. It grants no host file/network
operation and is not individual descendant PID authentication. Normal commands
retain NUL stdin. Named-pipe connectivity remains unverified on this host; run
npm run test:sandbox:feasibility -- --probe-pipe to record the experimental
observation, or add --require-pipe to require actual success. An unavailable
transport is reported as unavailable, not as a completed broker capability.
Native read-only workspace bridge
Commands may explicitly select bridge: 'workspace-read' together with
runtimes: ['node']. The manifest-pinned client is available at
process.env.YCODE_SANDBOX_BRIDGE; require(...).connectSync() supplies
readFileSync, statSync, readdirSync, querySync and close. connect()
supplies asynchronous equivalents and an eight-request dispatch queue. Close the
client after use. One Job should have one active stdin consumer. This is an
explicit JSON bridge, not a replacement for every Node fs or child_process API.
filesystem-read.h runs only on the host, requires a fixed local NTFS volume,
pins all ancestors/targets with read sharing only, rejects reparse/multiple-link
files, and compares root file identity and final normalized volume paths. The
host checks policy before and after reads and again for every cached chunk.
Reads are limited to 8 MiB or the tighter file policy; immutable snapshots are
served in 16 KiB chunks, with at most eight snapshots/16 MiB per Job, a 60-second
idle lifetime and a full client SHA-256 check. Directory listing is bounded to
128 raw entries. Native readers monitor their parent and have a four-second
deadline. No writable handle is returned to sandbox code.
The strict protocol caps requests at 8 KiB, replies at 24 KiB, eight concurrent operations and 1024 total requests per Job. Replay, forged ownership, oversized or incomplete frames cancel the channel/Job. Ordinary output is streamed. Final operation metadata is flushed before replying; contents/tokens are not persisted. Completed results remain queryable after a broken reply. Interrupted pending records are unknown, never inferred successful:
npm run test:sandbox:read-bridge
npm run test:sandbox:read-protocol
npm run sandbox:maintain -- broker-operation RUN_UUID REQUEST_UUID --state-root 'C:\absolute\userData\sandbox'
Preload also exposes app.sandboxBrokerOperation(runId, requestId) to the owned
top-level application page. Ownership and record IDs are checked on the host.
The bridge grants a read scope to the whole bound Job, not to individually
authenticated descendant PIDs. Full host write/file-descriptor shims, Python and
Shell adapters remain pending. See the implementation and test record in
docs/沙盒只读文件桥接入-2026-10-18.md.
To maintain a policy, edit the relevant module, add an adversarial case to
scripts/sandbox-regression.ts, run the native suite and application integration,
then rebuild. Keep protocol changes synchronized between runner, manifest,
service and tests. Do not enable ordinary host spawn as a default-mode fallback.
Current scope and limits
- Windows x64 is implemented. Other platforms reject default-mode commands.
- The model command path is isolated. The OMP sidecar, manually operated terminal, local MCP server processes and remote connectors retain their existing trust and approval models; their process confinement is separate work.
- Input excludes credentials/protected names,
.git,.ycode,.tmp,node_modules,.venv,venv,__pycache__and symbolic links. Multi-link files are rejected. Limits are 10,000 entries, 128 MiB of copied/generated file data, 8 MiB per file, 120 seconds by default, and 2 MiB retained per stdout/stderr stream. - File-count/size limits are snapshot and writeback checks, not an OS disk quota. Hard disk quotas and automatic audit/backup retention are pending work.
- Cmd and Windows PowerShell work in the mapped stage. Explicitly imported Node/npm and Python/pip run offline with controlled optional dependency layers; Git supports the private filtered repository described above. Runtimes and package managers are not automatically copied or granted access. Commands must use relative scratch paths; actual original workspace absolute paths are not redirected. Persistent filtered drafts are opt-in as described above; persistent processes/home/temp, mutable dependency layers, host Git history/index integration and metadata writeback, remote Git, network installs, pnpm link layouts and comprehensive native-addon compatibility remain pending.
- File creation/update/deletion, empty directories and directory tree deletion or rename are supported through versioned transactions. Deletion preflight refuses unknown/excluded host contents; recovery refuses later external additions. Directory metadata, ACLs, timestamps, arbitrary reparse behavior and binary compatibility with every program remain outside this first acceptance set.
- AppContainer can read shared Windows resources and other objects permitted by Windows ACLs. It does not implement a universal read-path allowlist or DLP. A workspace credential filter is an additional application policy.
- Registry/domain policies, configurable network egress, connector isolation, security-center UI and WorkBuddy equivalence remain separate acceptance tasks.
- Read-bridge operations pin native handles. Host commit/compensation/recovery
now use the host-only NTFS transaction channel in
filesystem-write.h, with root identity, relative directory-handle operations and a v3 intent journal. Replacement uses a no-overwrite two-rename handoff, with a brief absent-name window rather than one atomic swap. Changed/unknown scratch content refuses automatic cleanup. Full shims/metadata and power-loss acceptance remain pending; seedocs/沙盒原生写回事务-2026-10-18.md. - The recovery journal is intended for process interruption. Power-loss durability and independent security review require additional validation.