feat: complete WorkBuddy parity and Windows release workflow

This commit is contained in:
xiaohan.yuan committed 2026-10-18 12:45:21 +08:00
1 parent 143516b3a8
commit d5512df549
434 files changed
+54977 -1854

No files matched your search

+387
View File
@@ -0,0 +1,387 @@
# 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.
```powershell
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.
```powershell
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.
```powershell
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.
```powershell
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:
```text
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:
```powershell
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;
see `docs/沙盒原生写回事务-2026-10-18.md`.
- The recovery journal is intended for process interruption. Power-loss durability
and independent security review require additional validation.
## Primary API references
- [AppContainer isolation](https://learn.microsoft.com/en-us/windows/win32/secauthz/appcontainer-isolation)
- [CreateAppContainerProfile](https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-createappcontainerprofile)
- [Process attributes, including security capabilities and handle lists](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute)
- [Job objects and process trees](https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects)