docs: finalize v1 design and Windows service model

This commit is contained in:
2026-08-28 11:00:58 +00:00
parent a89253be96
commit 85f4d5d2a0
11 changed files with 833 additions and 241 deletions
+44 -13
View File
@@ -8,9 +8,19 @@ state and exposes a local control plane to `rvc` and, optionally, JSON-RPC
callers. This design is the v1 contract for Go implementations and a later Rust callers. This design is the v1 contract for Go implementations and a later Rust
client implementation. client implementation.
RVBox deliberately executes arbitrary commands with the identity, permissions, The first supported Go client is the Windows service client. The Linux client
and base environment of the client daemon. It is therefore an administrative is implemented afterward against the already proven shared runtime/protocol
tool, not a multi-tenant remote-execution service. and remains a primary v1 target. The server remains Linux-first.
RVBox deliberately executes arbitrary commands under locally selected
execution identities. It is therefore an administrative tool, not a multi-
tenant remote-execution service. On Windows the main service runs as
`LocalSystem` and maps the request's elevation intent plus current login state
to an effective user/session context. A peer that can successfully impersonate
the client's routing identity can request elevation, whose fallback may reach
SYSTEM. This makes the accepted self-reported-identity/network trust boundary
especially consequential; the explicit elevation bit and tray visibility are
intent/audit signals, not authorization controls.
Both daemons use strict TOML 1.0 configuration. The normative schema and fully Both daemons use strict TOML 1.0 configuration. The normative schema and fully
annotated examples are in the [configuration contract](configuration.md). annotated examples are in the [configuration contract](configuration.md).
@@ -47,11 +57,11 @@ external access-control layer.
## Components ## Components
```text ```text
rvc -- gRPC/Unix socket -- rvbox-server -- WSS/nginx -- rvbox client -- shell rvc -- gRPC/Unix socket -- rvbox-server -- WSS/nginx -- rvbox service -- shell
| | | | |
SQLite/WAL +-- optional HTTP JSON-RPC (debug/batch) SQLite/WAL | local named pipe
| | | |
compressed output segment files + audit log compressed data +-- optional JSON-RPC +-- per-session tray
``` ```
`rvc` is a thin control-plane client. The server owns durable command history, `rvc` is a thin control-plane client. The server owns durable command history,
@@ -60,10 +70,18 @@ owns active process supervision, unacknowledged output spooling, and safe
reconnection. Neither daemon lets a slow peer, command, or output stream block reconnection. Neither daemon lets a slow peer, command, or output stream block
its dispatch loops. its dispatch loops.
On Windows, SCM starts one machine-wide `LocalSystem` service before login. It
alone owns networking, durable state, logs, command admission, process handles,
and Job Objects. A separate unelevated tray may run in each logged-in session
and communicates only through an ACL-protected local named pipe; it is an
optional frontend and its exit or absence does not stop the service. Task
Scheduler is not used.
## Identity, sessions, and lifecycle ## Identity, sessions, and lifecycle
1. The client connects over WSS and sends `ClientHello` with its client ID, 1. The client connects over WSS and sends `ClientHello` with its client ID,
protocol capability, OS/architecture, daemon version, current daemon CWD, protocol capability, OS/architecture, daemon version, configured daemon CWD/
Windows work root,
supported shells, and a durable random client-instance UUID. supported shells, and a durable random client-instance UUID.
2. The server accepts the current compatible protocol version, fences the 2. The server accepts the current compatible protocol version, fences the
previous connection for that ID and same client instance, and returns a previous connection for that ID and same client instance, and returns a
@@ -83,7 +101,8 @@ its dispatch loops.
Each user request has a UUID (`issue_uuid`) and a durable request record: target Each user request has a UUID (`issue_uuid`) and a durable request record: target
client, request/issue timestamps, shell type, command text or script descriptor, client, request/issue timestamps, shell type, command text or script descriptor,
CWD, environment overrides, resource-profile flags, and lifecycle state. `rvc` CWD, environment overrides, Windows elevation intent and effective execution
identity, resource-profile flags, and lifecycle state. `rvc`
normally supplies this UUID as its optional `request_id`; the server generates normally supplies this UUID as its optional `request_id`; the server generates
one when it is omitted. Command and mutation identifiers are UUIDv7 values. one when it is omitted. Command and mutation identifiers are UUIDv7 values.
Transport is at-least-once, but the client durably Transport is at-least-once, but the client durably
@@ -160,9 +179,21 @@ client and 10,000 queued commands globally, subject to the stricter byte quotas.
PowerShell. This avoids transport quoting and Windows command-line limits; PowerShell. This avoids transport quoting and Windows command-line limits;
it never performs shell detection or fallback. Wrapper cleanup follows the it never performs shell detection or fallback. Wrapper cleanup follows the
same terminal rule as uploaded scripts. same terminal rule as uploaded scripts.
- A command receives the daemon account's permissions and startup environment, - Unix commands receive the daemon account's permissions and startup
overlaid with the persisted `env_overrides` map. The effective CWD is the environment. Windows interprets the request's `elevated` boolean through a
requested existing directory or the registered daemon CWD when omitted. login-aware hierarchy. With a usable active session, normal work uses a
deliberately non-elevated `active_user` token; elevated work tries
`active_user_elevated`, then `active_system`, then `local_system`. With no
usable active session, normal work uses `local_service` and elevated work uses
`local_system`. Fallback is allowed only during token selection before
`launch_prepared`, never after a process may have started. Requested elevation,
attempted contexts, effective token SID, session ID/session-owner SID, and
selection detail are persisted in command history.
- A command receives the base environment for its effective identity, overlaid
with the persisted `env_overrides` map. The effective CWD is the requested
existing accessible directory when supplied. When omitted it is the registered
daemon CWD on Unix or an ACL-isolated identity child beneath that registered
root on Windows.
- Each command is isolated into a process tree: a Unix session/process group or - Each command is isolated into a process tree: a Unix session/process group or
a Windows Job Object. A daemon that cannot supervise its children terminates a Windows Job Object. A daemon that cannot supervise its children terminates
them and reports interruption rather than claiming recovery it cannot make. them and reports interruption rather than claiming recovery it cannot make.
+23 -5
View File
@@ -4,11 +4,19 @@ RVBox v1 uses TOML 1.0 for daemon configuration. The normative annotated
examples are [`examples/server.toml`](examples/server.toml) and examples are [`examples/server.toml`](examples/server.toml) and
[`examples/client.toml`](examples/client.toml). They list every supported v1 [`examples/client.toml`](examples/client.toml). They list every supported v1
knob, with the routing, persistence, and safety limits first in each section. knob, with the routing, persistence, and safety limits first in each section.
The runnable Windows-first deployment example is
[`examples/client.windows.toml`](examples/client.windows.toml); omitted entries
use the defaults documented by the all-knob client reference.
## Loading and precedence ## Loading and precedence
- `rvbox-server --config PATH` and `rvbox --config PATH` load one UTF-8 TOML - `rvbox-server --config PATH` and `rvbox --config PATH` load one UTF-8 TOML
file. There is no implicit merge of multiple files and no hot reload in v1. file. There is no implicit merge, runtime rewrite, or hot reload in v1.
On Windows, the SCM service command line holds the canonical explicit config
path; omitting it during installation selects the resolved
`%ProgramData%\RVBox\client.toml`; first run creates its annotated template
and the service reports not-ready until routing is valid rather than
connecting to a placeholder server.
- Precedence is compiled default, then TOML, then an explicitly supplied CLI - Precedence is compiled default, then TOML, then an explicitly supplied CLI
flag. Flags exist for operationally important scalar keys; they use the same flag. Flags exist for operationally important scalar keys; they use the same
validation as TOML. RVBox does not implicitly import configuration from validation as TOML. RVBox does not implicitly import configuration from
@@ -21,10 +29,13 @@ knob, with the routing, persistence, and safety limits first in each section.
bytes; comments show the equivalent binary unit. URLs and paths are strings. bytes; comments show the equivalent binary unit. URLs and paths are strings.
- Relative paths are rejected for state, socket, CA, shell-executable, and - Relative paths are rejected for state, socket, CA, shell-executable, and
allowed-CWD-root fields. `client.daemon_cwd` is resolved once at startup and allowed-CWD-root fields. `client.daemon_cwd` is resolved once at startup and
then stored and advertised as an absolute path. The annotated client example then stored and advertised as an absolute path. On Unix it is the omitted-CWD
uses Unix paths; a Windows deployment replaces `state_dir`, `daemon_cwd`, and default; on Windows it is the protected parent under which the service creates
relevant shell paths with absolute Windows paths. Shell fields for the other an ACL-isolated default directory for the selected execution identity. The
platform are syntax-checked but not resolved or advertised. annotated client example uses Unix paths; a Windows deployment replaces
`state_dir`, `daemon_cwd`, and relevant shell paths with absolute Windows
paths. Shell fields for the other platform are syntax-checked but not resolved
or advertised.
- The daemon prints its effective configuration after validation, with no - The daemon prints its effective configuration after validation, with no
command data or TLS material. Since v1 stores command/environment payloads in command data or TLS material. Since v1 stores command/environment payloads in
plaintext, configuration output is hygiene rather than a secrecy guarantee. plaintext, configuration output is hygiene rather than a secrecy guarantee.
@@ -44,6 +55,13 @@ Any enabled non-loopback bind produces a conspicuous warning but is permitted by
the accepted v1 debugging contract. The control Unix socket always uses mode the accepted v1 debugging contract. The control Unix socket always uses mode
`0600`; it is not a configurable relaxation. `0600`; it is not a configurable relaxation.
Windows automatic start is SCM state, not TOML state. Installation registers
the machine-wide service as Automatic; an administrator may change it to Manual
through the tray or normal service-management tools. RVBox has no
`windows.start_on_boot` key and never attempts to reconcile two sources of
truth. The optional per-user tray uses the installer-created logon registration
and is never required for service readiness or command execution.
## Shell executable resolution ## Shell executable resolution
Each `ShellType` maps to one startup-validated absolute executable path from Each `ShellType` maps to one startup-validated absolute executable path from
+15
View File
@@ -90,6 +90,21 @@ actual lifecycle with a late-after-expiry warning. It renders terminal
`Rejected` with the client's structured validation/platform reason; `Failed` `Rejected` with the client's structured validation/platform reason; `Failed`
means the requested code actually launched. means the requested code actually launched.
`rvc run --elevated` maps directly to `ExecutionSpec.elevated`; omission is
false. Non-Windows clients reject true as unsupported in v1. Windows chooses
the effective context from that bit and launch-time login state: normal commands
use `active-user` when possible and otherwise `local-service`; elevated commands
with an active user try `active-user-elevated`, `active-system`, then
`local-system`, while logged-out machines use `local-system` directly. These
fallbacks finish before `launch_prepared` and never retry a process.
Detailed `rvc stat CLIENT ISSUE_UUID` output shows requested elevation, every
attempted Windows context, selection/fallback detail, effective context and
process-token SID, and target session ID/owner SID when applicable.
`active-system` is rendered conspicuously as SYSTEM in another user's session,
never as that user. A pre-launch rejection shows the structured final context-
selection error rather than implying requested code ran.
`rvc append` turns a string into `StdinWrite` with `append_newline=true` unless `rvc append` turns a string into `StdinWrite` with `append_newline=true` unless
the caller selects raw mode; `--file` supplies raw bytes; `--attach` streams the caller selects raw mode; `--file` supplies raw bytes; `--attach` streams
local standard input. `CloseStdin` is available separately. All stdin actions local standard input. `CloseStdin` is available separately. All stdin actions
+6
View File
@@ -119,6 +119,12 @@ metrics_path = "/metrics"
log_level = "info" log_level = "info"
# Structured log encoding: json or text. # Structured log encoding: json or text.
log_format = "json" log_format = "json"
# Optional log path; empty uses stderr on Unix and the conventional file on Windows.
log_file = ""
# Rotate a nonempty log_file after this many bytes (10 MiB).
log_max_bytes = 10485760
# Number of sealed rotated log files to retain.
log_max_files = 5
# Resource profiles are administrator policy. These illustrative values are not # Resource profiles are administrator policy. These illustrative values are not
# protocol guarantees. LIGHT is exclusive; otherwise combine at most one tier # protocol guarantees. LIGHT is exclusive; otherwise combine at most one tier
+53
View File
@@ -0,0 +1,53 @@
# RVBox v1 Windows-first client example. Omitted keys use client.toml defaults.
[client]
# Reverse WebSocket endpoint exposed by nginx; replace before first connection.
server_url = "wss://rvbox.example.test/v1/agent"
# Private durable state; first-run code resolves ProgramData rather than expanding env text.
state_dir = "C:\\ProgramData\\RVBox\\state"
# Empty selects the local hostname; otherwise use an opaque 1-128 ASCII ID.
client_id = ""
# Protected root for per-identity default CWDs when a request omits cwd.
daemon_cwd = "C:\\ProgramData\\RVBox\\work"
# Maximum simultaneously running supervised Job Objects.
max_running_commands = 16
# Maximum durably accepted commands waiting to start.
max_queued_commands = 100
# Grace for reserved terminal cleanup during orderly exit.
shutdown_grace = "30s"
[tls]
# Optional PEM CA bundle; empty uses the Windows trust store.
ca_file = ""
# Optional certificate-name override; empty derives it from server_url.
server_name = ""
[shells]
# Required Windows default shell enum.
default_windows = "powershell"
# Unix shell is inactive on Windows but remains syntax-checked.
default_unix = "sh"
# Inactive Unix path.
sh = ""
# Inactive Unix path.
bash = ""
# Exact absolute executable used for SHELL_CMD.
cmd = "C:\\Windows\\System32\\cmd.exe"
# Exact absolute executable used for SHELL_POWERSHELL.
powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
# Absolute CWD roots permitted by local policy; empty allows any accessible path.
allowed_cwd_roots = []
[observability]
# Loopback HTTP listener for local liveness, storage, and supervisor health.
listen = "127.0.0.1:6902"
# Structured logging threshold: debug, info, warn, or error.
log_level = "info"
# Structured log encoding used in the rotating file.
log_format = "json"
# Current service log opened read-only by the tray menu.
log_file = "C:\\ProgramData\\RVBox\\logs\\rvbox.log"
# Rotate the current log after this many bytes (10 MiB).
log_max_bytes = 10485760
# Number of sealed rotated log files to retain.
log_max_files = 5
+437 -194
View File
@@ -11,12 +11,39 @@ binaries:
spool, and reconnect/reconciliation owner. spool, and reconnect/reconciliation owner.
- `rvc`: local CLI over the server's Unix-domain gRPC socket. - `rvc`: local CLI over the server's Unix-domain gRPC socket.
The primary release target is a Linux server and Linux client. The Go client is The first supported client target is Windows connecting to a Linux server;
structured behind OS interfaces from the outset; Windows implementations of Linux client support follows and remains a primary v1 goal. Build shared client
process management, diagnostics, and resource controls are completed before a runtime code behind OS interfaces, but complete and exercise Windows process
Windows client is declared supported. Windows v1 requires Windows 10 or Windows management, diagnostics, resource controls, desktop hosting, and native CI
Server 2016 or newer. Do not claim Windows feature parity while those before beginning the Linux supervisor. Windows v1 requires Windows 10 or newer,
implementations are absent. or Windows Server 2016 or newer. Desktop Experience is required only for the
tray and active-session command contexts; the service and Session 0 execution
contexts support headless Server Core.
The released `rvbox.exe` is one signed binary with explicit SCM-service, tray,
installer/configurator, per-command launcher, and signal-helper modes.
Installation is UAC-elevated and registers one machine-wide Automatic service
running as LocalSystem. That single main service owns WSS, all accepted
requests, durable state/history, logs, token policy, Job Objects, process
handles, and recovery before any user logs in. There is no separate persistent
worker or elevation broker.
The installer also registers an unelevated per-user tray through the machine-
wide `Run` key. A tray is only a named-pipe frontend to the service: it may show
health, open config/log, and request locally authorized service actions, but it
never opens the store or owns remote work. Closing it affects neither service
nor commands. Task Scheduler is not used anywhere in v1.
Use conventional machine-wide paths: `%ProgramData%\RVBox\client.toml`,
`%ProgramData%\RVBox\state`, and `%ProgramData%\RVBox\logs\rvbox.log`, with
state ACLs limited to Administrators and SYSTEM and separate read-only tray
access for config/log. The menu opens the exact config or current log via a safe
explicit file-opening path. SCM Automatic/Manual state is
the only service-startup source of truth and is not mirrored into TOML. Create
`%ProgramData%\RVBox\work` as a SYSTEM-owned root. For omitted CWDs, create an
ACL-isolated child for the selected user SID, LocalService, or SYSTEM context;
do not make one shared work directory writable by every identity. These
requirements are part of Phase 4, not post-v1 polish.
This plan implements the already agreed v1 contract. In particular, it does This plan implements the already agreed v1 contract. In particular, it does
not add authentication, client enrollment, mutual TLS, or a client allowlist. not add authentication, client enrollment, mutual TLS, or a client allowlist.
@@ -80,8 +107,10 @@ internal/
runtime/ # reconnect loop, transport, dispatcher runtime/ # reconnect loop, transport, dispatcher
spool/ # active command/event/output durable spool spool/ # active command/event/output durable spool
supervisor/ # OS-neutral interface supervisor/ # OS-neutral interface
supervisor/unix/ # process groups, /proc, cgroup v2 supervisor/windows/# first target: tokens/sessions, launcher, Jobs, diagnostics
supervisor/windows/# Job Objects and Windows diagnostics supervisor/unix/ # later target: process groups, /proc, cgroup v2
windowsservice/ # SCM install/control and service host
windowstray/ # per-session named-pipe frontend and notification icon
observability/ # logging, metrics, health/readiness observability/ # logging, metrics, health/readiness
testkit/ # clocks, fake transport, fault helpers testkit/ # clocks, fake transport, fault helpers
gen/go/rvbox/v1/ # generated protobuf/grpc code gen/go/rvbox/v1/ # generated protobuf/grpc code
@@ -101,6 +130,11 @@ versions in `go.mod`; record reasons for non-standard dependencies in
toolchain for normal Linux/Windows client builds. Use a maintained, toolchain for normal Linux/Windows client builds. Use a maintained,
context-aware WebSocket implementation and a Zstandard implementation that context-aware WebSocket implementation and a Zstandard implementation that
supports bounded decompression. Do not rely on an archived WebSocket package. supports bounded decompression. Do not rely on an archived WebSocket package.
For Windows, prefer `golang.org/x/sys/windows` plus narrow reviewed wrappers for
SCM, tokens/WTS, Job, console, named-pipe, shell, ACL, and notification-area APIs. Do not
adopt an unmaintained tray abstraction merely to reduce Win32 code. Pin the
resource compiler used for icon/version manifests and keep signing credentials
out of build images and repository state.
### 3.2 Containerized developer tooling ### 3.2 Containerized developer tooling
@@ -134,9 +168,23 @@ Add CI (or a repository script ready for CI) that runs, in order:
2. Generation freshness check. 2. Generation freshness check.
3. `go fmt`, `go vet`, static analysis, and unit tests. 3. `go fmt`, `go vet`, static analysis, and unit tests.
4. Race tests for server/client concurrency packages. 4. Race tests for server/client concurrency packages.
5. Linux integration tests in Compose. 5. Linux server/storage/session integration tests in Compose; no Linux client
6. Cross-compilation/build verification for Windows client packages; Windows supervisor is required at this stage.
runtime tests run on a Windows runner once available. 6. Cross-compilation checks for Windows packages in the container plus native
Windows build/runtime smoke tests on a Windows runner. A Windows runner is a
Phase 0 prerequisite, not a later optional enhancement.
Provide two native Windows lanes before Phase 4: a clean noninteractive runner
for unit/Job/process/storage tests, and a resettable interactive VM with Explorer,
a split-token administrator, UAC enabled, and Desktop Experience for tray/WTS/
token-session tests. Add repeatable test users/policy fixtures for a standard
user, no logged-in user, multiple active sessions where supported, UAC disabled,
and Administrator Protection enabled when the current Windows image exposes it.
Tests that require the interactive desktop may be a
release-candidate gate rather than per-commit, but cannot be replaced by mocks
or a service-account runner. Add a Server Core service/Session-0 smoke lane.
Exercise at least the oldest supported Windows baseline and one current desktop
release before publishing.
**Exit criteria:** all three empty `main` packages build in the toolchain **Exit criteria:** all three empty `main` packages build in the toolchain
container; generated code is checked in; `make verify` works from a fresh clone. container; generated code is checked in; `make verify` works from a fresh clone.
@@ -163,6 +211,11 @@ At the application boundary, validate what protobuf cannot express:
<= 16 MiB, JSON-RPC HTTP bodies are <= 24 MiB, and decoded field limits remain <= 16 MiB, JSON-RPC HTTP bodies are <= 24 MiB, and decoded field limits remain
identical across the two control transports. identical across the two control transports.
- shell type is explicit or assigned only to the documented platform default. - shell type is explicit or assigned only to the documented platform default.
- `elevated=false` is normal privilege; non-Windows clients reject true in v1.
Windows applies the documented login-aware context hierarchy and records
every attempted/effective context. Fallback ends before `launch_prepared`.
- active Windows contexts bind one revalidated session ID, logon SID, and user
SID; process creation is never retried under a different context.
- CWD exists, is a directory, and is allowed by local daemon policy. - CWD exists, is a directory, and is allowed by local daemon policy.
- an execution request has exactly one source: command text or script descriptor. - an execution request has exactly one source: command text or script descriptor.
- script descriptors and payloads agree on SHA-256 and <= 10 MiB size. - script descriptors and payloads agree on SHA-256 and <= 10 MiB size.
@@ -182,6 +235,9 @@ It contains:
races; races;
- typed domain errors mapped to gRPC status/structured details, JSON-RPC error - typed domain errors mapped to gRPC status/structured details, JSON-RPC error
objects, or agent-protocol `ControlError` as appropriate; objects, or agent-protocol `ControlError` as appropriate;
- typed Windows pre-launch errors for a failed normal execution context or an
exhausted elevated-context chain; ambiguity and per-attempt token/policy
failures remain bounded selection detail rather than separate terminal codes;
- event-sequence validation, duplicate equivalence checks, and declared gap - event-sequence validation, duplicate equivalence checks, and declared gap
validation through `OutputTruncation` metadata; validation through `OutputTruncation` metadata;
- default values and hard-limit validation; - default values and hard-limit validation;
@@ -214,7 +270,9 @@ Use strict TOML 1.0 and implement the normative contract in
[`configuration.md`](configuration.md). Keep the annotated [`configuration.md`](configuration.md). Keep the annotated
[`examples/server.toml`](examples/server.toml) and [`examples/server.toml`](examples/server.toml) and
[`examples/client.toml`](examples/client.toml) synchronized with the Go config [`examples/client.toml`](examples/client.toml) synchronized with the Go config
structs and compiled defaults. structs and compiled defaults. Keep
[`examples/client.windows.toml`](examples/client.windows.toml) as the runnable
Windows-first subset using those same defaults.
Implement configuration in this order: Implement configuration in this order:
@@ -265,6 +323,9 @@ takeover TTL, and observability. Client TOML covers WSS/TLS routing, durable
identity/state, exact shell paths, allowed CWD roots, queue/concurrency, identity/state, exact shell paths, allowed CWD roots, queue/concurrency,
reconnect/liveness, spool/flow limits, execution diagnostics/grace periods, reconnect/liveness, spool/flow limits, execution diagnostics/grace periods,
resource profiles, and observability. resource profiles, and observability.
Windows client configuration additionally covers rotating service-log output.
SCM startup type, service/launcher/tray identities, execution-context selection,
conventional paths, and secure ACLs are mandatory policy rather than TOML knobs.
Do not add or claim application-level at-rest encryption in v1. Command-owned Do not add or claim application-level at-rest encryption in v1. Command-owned
payloads are stored in plaintext under the private state directories; document payloads are stored in plaintext under the private state directories; document
@@ -274,8 +335,8 @@ Hard defaults are: 10-second heartbeat-idle interval, 30-second liveness
timeout, 1–60-second full-jitter reconnect, reset after 60 stable seconds, 16 timeout, 1–60-second full-jitter reconnect, reset after 60 stable seconds, 16
running/100 queued client commands, 1,000 server-queued commands per client and running/100 queued client commands, 1,000 server-queued commands per client and
10,000 server-wide, 5-minute one-shot live-conflict takeover grant, 15-minute 10,000 server-wide, 5-minute one-shot live-conflict takeover grant, 15-minute
queue TTL, 64 KiB raw stream queue TTL, 64 KiB raw stream chunk, 1 MiB decoded agent envelope, 768 KiB
chunk, 1 MiB decoded agent envelope, 768 KiB serialized execution spec, 16 MiB serialized execution spec, 16 MiB
decoded control request, 24 MiB JSON-RPC HTTP body, 10 MiB output window/raw decoded control request, 24 MiB JSON-RPC HTTP body, 10 MiB output window/raw
script cap, 32 MiB total per command, 256 MiB per client/client daemon, 4 GiB script cap, 32 MiB total per command, 256 MiB per client/client daemon, 4 GiB
server-wide command storage, 30-day terminal retention, 100 MiB audit storage server-wide command storage, 30-day terminal retention, 100 MiB audit storage
@@ -286,18 +347,19 @@ plus unacknowledged send windows of 1 MiB per command and 8 MiB per session.
Reserve 64 KiB within every accepted command's quota for terminal/loss closeout Reserve 64 KiB within every accepted command's quota for terminal/loss closeout
metadata and bound protocol detail/reason plus incident-note text to 4 KiB. metadata and bound protocol detail/reason plus incident-note text to 4 KiB.
Use emergency filesystem free-space floors of 256 MiB server-side and 64 MiB Use emergency filesystem free-space floors of 256 MiB server-side and 64 MiB
client-side by default. client-side by default. The Windows service installs as Automatic; service logs
rotate at 10 MiB and retain five sealed files.
Test both annotated example files through the production decoder. Add table tests Test all annotated example files through the production decoder on their target
for every unknown key, invalid duration/size, high/low inversion, tier inversion, platforms. Add table tests for every unknown key, invalid duration/size,
zero semantic, noncanonical path/device, unsupported default shell, conflicting high/low inversion, tier inversion, zero semantic, noncanonical path/device,
profile combination, and flag-precedence case. Add a test proving a request-level unsupported default shell, conflicting profile combination, and flag-precedence
`PATH` override cannot change the chosen shell executable. Golden-test the case. Add a test proving a request-level `PATH` override cannot change the
redacted effective configuration and keep its key order stable enough for chosen shell executable. Golden-test the redacted effective configuration and
operators to compare deployments. keep its key order stable enough for operators to compare deployments.
**Exit criteria:** state-machine and configuration tests cover all legal/illegal **Exit criteria:** state-machine and configuration tests cover all legal/illegal
transitions and boundary values; both examples parse to the documented defaults; transitions and boundary values; all examples parse to the documented defaults;
generated protos compile and are the only DTOs crossing process boundaries. generated protos compile and are the only DTOs crossing process boundaries.
## 5. Server persistence and retention (Phase 2) ## 5. Server persistence and retention (Phase 2)
@@ -349,7 +411,7 @@ invariants must not.
| --- | --- | | --- | --- |
| `clients` | client ID, most-recent platform/capabilities/CWD/version, durable instance ID, current generation, connection/last-seen timestamps, latest rejected live-conflict instance/time, unified charged command-storage total | | `clients` | client ID, most-recent platform/capabilities/CWD/version, durable instance ID, current generation, connection/last-seen timestamps, latest rejected live-conflict instance/time, unified charged command-storage total |
| `sessions` | opaque session ID, client ID, durable client-instance UUID, generation, opened/fenced/closed times, close reason | | `sessions` | opaque session ID, client ID, durable client-instance UUID, generation, opened/fenced/closed times, close reason |
| `commands` | UUID, client ID, indexed issue/queue-expiry/terminal times, lifecycle, revision, exit result, last event sequence, retention status, and a Zstandard-compressed immutable execution-spec payload including plaintext environment values | | `commands` | UUID, client ID, indexed issue/queue-expiry/terminal times, lifecycle, revision, exit result, last event sequence, retention status, a Zstandard-compressed immutable execution-spec payload including plaintext environment values, and optional Windows selection attempts/effective identity |
| `command_payloads` | command UUID, payload kind (script or other command-owned blob), Zstandard compression, raw/stored sizes, digest, inline bytes or validated segment reference | | `command_payloads` | command UUID, payload kind (script or other command-owned blob), Zstandard compression, raw/stored sizes, digest, inline bytes or validated segment reference |
| `command_events` | command UUID + event sequence unique key, observed and server receipt times, event type, payload metadata, immutable duplicate checksum | | `command_events` | command UUID + event sequence unique key, observed and server receipt times, event type, payload metadata, immutable duplicate checksum |
| `output_segments` | command UUID, segment ordinal/path, `committed_end_offset`, min/max event sequence, stream mix, compressed/raw byte totals, checksum, created time | | `output_segments` | command UUID, segment ordinal/path, `committed_end_offset`, min/max event sequence, stream mix, compressed/raw byte totals, checksum, created time |
@@ -740,13 +802,15 @@ slow client that cannot block another client or local control RPC.
receive bounded dispatches, and recover connection faults without duplicate receive bounded dispatches, and recover connection faults without duplicate
execution or server deadlock. execution or server deadlock.
## 7. Client runtime, spool, and Unix supervisor (Phase 4) ## 7. Windows-first client runtime, spool, and supervisor (Phase 4)
### 7.1 Client state and reconnect runtime ### 7.1 Client state and reconnect runtime
Keep client state private (mode `0700`): durable accepted-command records, Keep client state private: mode `0700` on Unix; on Windows disable inherited
active process metadata, command event journal, stdout/stderr spool segments, ACLs and grant only SYSTEM and Administrators the required access. Store durable
stdin write acknowledgements, and outstanding script upload state. It contains accepted-command records, active process metadata, command event journal,
stdout/stderr spool segments, stdin write acknowledgements, and outstanding
script upload state. It contains
no terminal command history after server acknowledgement and local cleanup. no terminal command history after server acknowledgement and local cleanup.
It retains a separate compact FIFO of the most recent 1,000,000 command It retains a separate compact FIFO of the most recent 1,000,000 command
tombstones (binary UUIDv7, immutable request hash, and acknowledgement time) so tombstones (binary UUIDv7, immutable request hash, and acknowledgement time) so
@@ -775,6 +839,15 @@ first registration. Never regenerate it merely because the network/server
rejects a session. If the identity file is corrupt, enter dirty local health and rejects a session. If the identity file is corrupt, enter dirty local health and
require the repair path rather than silently presenting a new installation. require the repair path rather than silently presenting a new installation.
Define durable-file primitives per platform and use them everywhere. Unix uses
file `fsync`, atomic same-filesystem rename, then parent-directory `fsync`.
Windows writes a same-directory temporary file with restrictive ACLs, calls
`FlushFileBuffers`, atomically installs it with `ReplaceFileW` or
`MoveFileExW(MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)` as applicable,
and flushes a directory handle where supported. Treat an unsupported/failed
required flush as a storage error; do not acknowledge durable state based only
on buffered writes. Test power-loss boundaries on NTFS, not only process exit.
Give the client store the same committed-offset segment discipline as the Give the client store the same committed-offset segment discipline as the
server. Persist, at minimum, command phase/revision/hash, platform launch server. Persist, at minimum, command phase/revision/hash, platform launch
identity, local event ordinal, optional assigned wire sequence, payload digest, identity, local event ordinal, optional assigned wire sequence, payload digest,
@@ -899,6 +972,10 @@ directory, and only then make the command launch-eligible. A permanent upload
failure durably ends the command as `REJECTED` before launch authorization and failure durably ends the command as `REJECTED` before launch authorization and
releases every associated reservation. releases every associated reservation.
On Windows create wrappers with an explicit non-inherited ACL, exclusive random
basename, and delete-sharing suitable for terminal cleanup; use the common
Windows durable replacement primitive rather than POSIX rename assumptions.
Materialize ordinary `command_text` through the same private generated-file Materialize ordinary `command_text` through the same private generated-file
machinery, while retaining its separate command-text metadata. Execute the file machinery, while retaining its separate command-text metadata. Execute the file
with exactly `sh`/`bash`, `cmd.exe /D /S /C`, or `powershell.exe` with with exactly `sh`/`bash`, `cmd.exe /D /S /C`, or `powershell.exe` with
@@ -916,7 +993,7 @@ reject if it is no longer the validated regular executable. Add tests with an
attacker-controlled `PATH` entry and same-named fake shell to prove it cannot be attacker-controlled `PATH` entry and same-named fake shell to prove it cannot be
selected. selected.
### 7.4 Unix process supervisor ### 7.4 Supervisor contract and Windows process implementation
Define a narrow interface used by the runtime: Define a narrow interface used by the runtime:
@@ -929,107 +1006,229 @@ type Supervisor interface {
} }
``` ```
The Unix implementation starts an RVBox launcher as leader of a new `StartSpec` includes the validated `elevated` intent; a successful `Process`
session/process group; after authorization that launcher creates the selected exposes an immutable effective `WindowsExecutionIdentity` after preparation,
`sh`/`bash` child inside the same group and remains as its watchdog. It rejects and a context-selection error carries the same record without an effective
unsupported shell values. It applies daemon environment plus persisted context. The runtime persists the intent, attempted contexts, selection detail,
overrides, validates the CWD, connects stdin/stdout/stderr pipes, and records and optional effective identity. It emits the record with a pre-launch
launcher/root/group identities. Signal the full process group. rejection or with `running` before reporting requested code as started.
On orderly shutdown and unclean-start recovery, terminate surviving managed
groups and emit `interrupted` rather than pretending pipe monitoring survived.
Treat root exit as the start of a configurable 5-second tree/output drain grace Implement Windows code in platform-specific files so non-Windows builds never
period. Wait for the cgroup/process group and capture readers; terminate residual import Windows APIs. Keep launch phases identical across platforms:
descendants after the grace period, drain to EOF, and emit incomplete-output `accepted -> launch_prepared -> launch_authorized -> running`, with no shortcut.
metadata as a sequenced `OutputIncomplete` event if handles still cannot be
drained. The terminal lifecycle event must be sequenced and spooled only after
all retained output/truncation events and is
always the final client event.
Implement launch through an internal blocked-launcher mode with a private Implement one exhaustive token selector; do not scatter token fallback across
release/watchdog channel. The launcher remains alive as a non-user-code launch code:
supervisor for the process-tree lifetime. After the launcher reports ready,
persist and flush
its PID/process-group/platform birth identity as `launch_prepared`; persist and
flush `launch_authorized` before releasing requested command code. EOF before
release aborts without executing it. On Linux create a per-command cgroup v2
for supervision regardless of resource-profile selection, place the blocked
launcher into it before release (`clone3` with `CLONE_INTO_CGROUP` and
`CLONE_PIDFD` where available), and persist the cgroup path plus
`/proc/<pid>/stat` start time. Recovery prefers `cgroup.kill`; a process-group
fallback is allowed only after positive birth-identity verification. Other Unix
platforms use the watchdog/process-group fallback and document that descendants
which deliberately create a new session may escape it.
Encode launch as `accepted -> launch_prepared -> launch_authorized -> running` 1. Reject `elevated=true` on non-Windows in v1. On Windows, enumerate WTS
and permit no shortcut: sessions immediately before preparation. Prefer the physical console only
when it is `WTSActive` with a valid user token; otherwise accept exactly one
`WTSActive` interactive session. Zero candidates means logged out. Multiple
candidates without a preferred console are recorded as ambiguous and treated
as no *usable* active user, never selected nondeterministically.
2. With a usable active user and `elevated=false`, attempt only `ACTIVE_USER`.
Use an existing filtered/standard token. If Windows supplies only a full
administrator token, call `CreateRestrictedToken` with LUA/max-privilege
restriction, make administrator/privilege-bearing groups deny-only, set and
verify medium integrity, and retain the user's SID/session. If a verified
non-elevated token cannot be built, reject; do not use LocalService or SYSTEM
while that selected active session remains valid.
3. With a usable active user and `elevated=true`, attempt contexts in this exact
order: `ACTIVE_USER_ELEVATED`, `ACTIVE_SYSTEM`, `LOCAL_SYSTEM`.
`ACTIVE_USER_ELEVATED` queries `TokenElevationType`, uses a linked full token
for a traditional limited administrator, and accepts an already-full admin
token. A standard user/missing linked token records
`CODE_ELEVATION_UNAVAILABLE`;
Administrator Protection or another just-in-time approval policy records a
bounded policy-specific attempt reason. V1 shows no UAC/Hello prompt and
continues to `ACTIVE_SYSTEM`.
4. `ACTIVE_SYSTEM` duplicates the service SYSTEM token, sets `TokenSessionId` to
the selected session while `SeTcbPrivilege` is enabled only around that call,
verifies SYSTEM SID/session, and hides all windows. A pre-preparation failure
records its error and continues to Session 0 `LOCAL_SYSTEM`. This fallback
intentionally bypasses user-scoped approval because the installed service
already holds SYSTEM and must be conspicuous in status/audit history. The
final `LOCAL_SYSTEM` fallback supplies elevation but no interactive-desktop
access; session-scoped commands may launch successfully and then fail in the
ordinary way in Session 0.
5. With no usable active user, `elevated=false` attempts only `LOCAL_SERVICE`:
passwordless `LogonUserW` for `NT AUTHORITY\LocalService` with
`LOGON32_LOGON_SERVICE`, required SID `S-1-5-19`, and session 0. Do not use a
restricted SYSTEM token as a substitute. `elevated=true` attempts only
`LOCAL_SYSTEM` by duplicating the service token and requiring SID `S-1-5-18`
plus session 0.
6. Fallback decisions cover token/session capability only and all complete
before `launch_prepared`. Never fall back after launcher creation, shell
creation, CWD/executable/profile validation failure, authorization, or an
uncertain outcome. If the selected user logs out during selection, close all
provisional handles, enumerate once more, and apply the resulting row; never
switch silently to another user.
7. Verify the final user SID, session ID, token type, elevation state, and
integrity level against the selected context. Close every token/profile
handle on all paths. Persist the request's elevation bit, ordered attempted
contexts with a total bounded selection detail, optional effective context/
user SID, and target session/user/logon SIDs; never persist token handles or
credentials. If every context fails, emit that decision record with no
effective context on the terminal pre-launch rejection.
1. Create the private cgroup/process group, pipes, wrapper, and close-on-exec Build the base Unicode environment with `CreateEnvironmentBlock` for the
release/watchdog channel without executing user code. effective token, then apply validated request overrides deterministically.
2. Start the blocked launcher and obtain a positive ready message containing Load/unload an active user's profile only when required and keep it loaded until
the platform process identity; place and verify it in its command cgroup. the complete Job exits. Session 0 contexts use their service-account profile and
3. Commit and fsync `launch_prepared` with that identity. Recheck the latest cannot see interactive mapped drives. Validate an explicit CWD and wrapper ACL
revision and pending cancellation while the launcher remains blocked. access while impersonating the effective token. For an omitted CWD, treat
4. If still executable, commit and fsync `launch_authorized`, then send the `%ProgramData%\RVBox\work` (or configured `daemon_cwd`) as a SYSTEM-owned root
one-byte release and keep the watchdog channel open until tree cleanup. and create/open an ACL-isolated child keyed by the selected user SID,
Record `running` only after the launcher reports successful requested-shell LocalService, or SYSTEM context. Reject insecure owners, inherited write grants,
creation/`exec`. and reparse points on reuse. Generated wrappers grant only SYSTEM and the
5. A crash before durable preparation cleans an untrusted orphan and may retry. effective token SID the required access.
A prepared-but-unauthorized launcher must exit on channel EOF and may retry
only after positive death verification. After release, channel EOF makes the
launcher terminate its process group; an authorized but uncertain launch is
killed as a tree and ends `interrupted`, never launched again.
Make launch-journal and launcher-control records checksum-framed and bounded. 1. Create a non-inheritable per-command Job Object, enable
Fault-inject process death before and after every fsync, ready/release, and exec `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, and do not enable breakaway.
acknowledgement; assert that user-visible side effects occur at most once and 2. Resolve the hierarchy's effective token/session as specified above. Create
that recovery cannot confuse a reused PID. unpredictable local named pipes for control/stdin/stdout/stderr with
`PIPE_REJECT_REMOTE_CLIENTS` and an ACL restricted to SYSTEM and the effective
token SID. Do not rely on inherited service handles: Windows prohibits normal
handle inheritance across Terminal Services sessions.
3. Create a stateless per-command launcher mode of the canonical `rvbox.exe`
suspended through `CreateProcessAsUser`, with no inherited handles and an
opaque pipe-channel identifier. Assign this still-suspended launcher to the
empty Job, then resume it. Verify the connecting pipe client's PID, creation
`FILETIME`, token SID, session ID, and generation before sending any launch
material.
4. The launcher opens only the verified pipes and creates the requested shell
suspended with `CREATE_NEW_CONSOLE`, `CREATE_SUSPENDED`,
`CREATE_UNICODE_ENVIRONMENT`, and `EXTENDED_STARTUPINFO_PRESENT`. Use
`STARTF_USESHOWWINDOW`/`SW_HIDE`; pass only standard-I/O handles with
`PROC_THREAD_ATTRIBUTE_HANDLE_LIST`. Job membership propagates because
breakaway is disabled. Report shell PID/creation time and wait for release.
5. Persist and flush `launch_prepared` with requested elevation, attempted/
effective contexts and reasons, token SID, session/user/logon SIDs when
active, launcher/shell birth identities,
Job generation, and console identity. Recheck revision/cancellation, WTS
session identity, executable identity, CWD access, and requested Job limits
while requested code remains suspended.
6. Persist and flush `launch_authorized`, then send the authenticated release
message. The launcher resumes the shell and acknowledges the attempt; record
`running` with effective identity only after that acknowledgement. Failure or
crash after authorization terminates the Job and becomes interrupted, never
redispatched.
7. The service retains the sole Job handle. Control EOF before authorization
makes the launcher exit without executing; EOF afterward terminates the Job.
Service exit therefore kills launcher, shell, and descendants in every crash
window. Recovery never signals or kills by PID alone.
Implement the portable Unix set `HUP`, `INT`, `TERM`, `KILL`, `USR1`, and Do not combine `CREATE_NEW_PROCESS_GROUP` with `CREATE_NEW_CONSOLE`: Windows
`USR2`, accepting optional `SIG` prefixes in the CLI and mapping only through ignores the former, and it is unnecessary because every command owns a distinct
the `SignalKind` enum. Reject arbitrary native numbers and unsupported names. hidden console. For TERM, start a short-lived private signal-helper mode of the
Never allow signal zero or arbitrary PID targeting. The process's group ID same canonical `rvbox.exe` using the command's effective token/session. Pass the
comes only from durable supervisor metadata, never a request field. target identity through a separate unpredictable, PID/token/session-verified
local named pipe rather than inherited cross-session handles or the command
line. Prefer the root shell while it is live;
otherwise select a live PID from the Job's process list and verify its creation
`FILETIME`/generation before use. The helper calls `AttachConsole(target_pid)`,
installs a control handler that consumes its own CTRL_BREAK, then calls
`GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0)` so only processes attached to
that command's console receive it. It reports a checksum-framed result and
detaches. The helper receives neither the Job handle nor command stdio.
### 7.5 Linux diagnostics and resource profiles Apply `PROC_THREAD_ATTRIBUTE_HANDLE_LIST` to every same-session child creation.
Every database, log, listener, Job, unrelated pipe, and SCM handle is non-
inheritable. Length/type/generation/checksum-frame launcher/signal-helper
control messages and reject mismatched generations.
Poll active processes at a configurable interval outside pipe/network loops. Open the configured shell by canonical absolute path and pass it as explicit
Read readable `/proc` values for CPU time, RSS, I/O, state, CWD, and wait reason; `lpApplicationName`. Use one reviewed Windows argument-quoting routine and an
aggregate only clearly associated group/child data. Missing/unreadable values explicit Unicode environment block. Never invoke `%COMSPEC%`, search
remain absent. Set `suspected_hung` only after default 10 minutes without `PATH`/file associations, or let command environment overrides choose the
observable progress and label it diagnostic, not lifecycle. executable.
Make requested resource-profile handling explicit. On Linux the supervisor Associate the Job with an I/O completion port. Use
uses a delegated writable cgroup v2 for tree supervision whenever available, `JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO` plus capture-pipe EOF to establish complete
even without a profile. Profiles add administrator-defined CPU/memory/disk/ tree/output drain. Persist shell creation `FILETIME` before trusting a PID.
process controls to that cgroup. Without delegation, no-profile execution uses Recovery may reopen a process only to compare creation time/generation;
the watchdog/process-group fallback; a profile whose essential control cannot inability to prove ownership creates a dirty incident rather than risking
be applied is rejected as unsupported. No profile means no resource restriction, termination of a reused PID.
even when a supervisory cgroup exists.
Compile TOML resource profiles into immutable validated launch policies during Accept only `TERM`/`SIGTERM` and `KILL`/`SIGKILL`. TERM runs the verified
startup. Enforce `LIGHT` as exclusive; otherwise allow at most one CPU, one console-attach helper, waits the configured 10 seconds, then terminates the Job
memory, and one disk profile. On Linux, translate configured policy into the if necessary. KILL terminates it immediately. Emit the actual attach, delivery,
per-command cgroup's `cpu.max`/`cpu.weight`, `memory.max`/`memory.high`, wait, and escalation outcome. Root exit starts the common 5-second drain grace;
`pids.max`, and per-device `io.max` controls as applicable. Write and read back the terminal lifecycle event remains last.
all essential controls while the launcher is blocked. If any essential control
is unsupported or cannot be applied, destroy the empty command cgroup and
permanently reject the dispatch; never run partially constrained.
Define diagnostic progress as a change in process-tree CPU ticks, cumulative Compile requested TOML profiles before launch and apply CPU, memory, active
I/O counters, retained output/input activity, or lifecycle state. Persist only process, and supported I/O rate limits to the empty Job. Read back every required
the latest sample and no-progress start time, clear `suspected_hung` on the next limit before authorization. Permanently reject `UNSUPPORTED` rather than run
observed progress, and tolerate counter reset/process exit. Diagnostics must not partially constrained. Use process/Job accounting for CPU/RSS/I/O and omit
keep a command alive, change terminal status, or block the supervisor. Linux-only wait reasons.
### 7.6 Client tests ### 7.5 Windows desktop host and lifecycle
Use fake transport/clock plus real subprocess tests for shell defaults, CWD/env Build the executable with the Windows GUI subsystem so service/helper/tray modes
overlays, at-most-once duplicate dispatch, concurrent output with no newlines, never flash an unwanted console. Human-invoked `--help`, `--check-config`,
stdin ordering/close, process-group termination, reconnect/replay, offline install, uninstall, and configuration modes call
rolling truncation before sequence assignment, assigned-window pinning, script `AttachConsole(ATTACH_PARENT_PROCESS)`, reopen the inherited standard handles,
progress/checksum failure/cleanup, atomic tombstone replacement, queue limits, and use UTF-8 terminal diagnostics when attached; define native-dialog plus exit-
shutdown interruption, and `/proc` absence. Run race tests with multiple code behavior when no console exists and test both paths. `install-service` and
`uninstall-service` use `ShellExecuteEx(..., "runas", ...)` only when their caller
is not already elevated, validate the canonical executable/config paths, and
make idempotent SCM changes. The installed service command line selects only the internal
`service` mode and exact config path. At service entry, verify SCM startup rather
than accepting that mode from an ordinary process invocation. Hold one machine-
wide service/state lock and report SCM start/stop checkpoints within their
deadlines; lengthy spool recovery remains asynchronous and does not block
service start.
Register the tray with the machine-wide `Run` key, not Task Scheduler and not
service-created cross-session injection. Use one ACL-protected mutex per WTS
session so each logged-in user gets at most one tray while multiple RDP/console
sessions remain independent. Run its Win32 message pump on one locked OS thread,
recreate the icon after `TaskbarCreated`, and use bounded nonblocking IPC/UI
channels. The tooltip shows connection/readiness/dirty state without payloads.
`Exit` closes only that tray. The service continues through tray exit, user
logoff, Explorer restart, and periods with no interactive user.
Expose a versioned, length-bounded local named-pipe tray protocol with
`PIPE_REJECT_REMOTE_CLIENTS`, explicit SYSTEM/Administrators/interactive-user
ACLs, peer PID/session/token inspection, deadlines, and no command payloads.
The tray never reads the spool/database. Read-only status may be served to a
local interactive user. For a mutating service/config action, the tray launches
the narrow canonical `configure-service` mode with UAC; that helper sends one
authenticated request and exits. The service rechecks the helper token rather
than trusting a tray-supplied administrator claim.
Resolve defaults under `%ProgramData%\RVBox`; create the config, state, and log
directories with explicit Administrators/SYSTEM ACLs and reject insecure
existing objects. Create `%ProgramData%\RVBox\work` as a SYSTEM-owned root and
create identity-scoped child directories on demand with non-inherited ACLs for
SYSTEM plus the effective non-SYSTEM SID; no command identity receives state-
directory access. Give tray users only the documented read access to config/
current logs. On first installation, copy the annotated Windows
TOML template and leave the service live/not-ready until routing is valid. `Open
config` and `Open log` use exact resolved regular paths—never an unquoted shell
command—and show actionable errors. Config edits are administrator-only and
take effect after explicit service restart; v1 has no hot reload or TOML rewrite.
Install the service with `SERVICE_AUTO_START`. `configure-service` maps the tray
Automatic/Manual control directly to SCM start type; there is no
`windows.start_on_boot` TOML key or second desired-state store. Stop/restart is a
separate administrator action and reports that stopping the service terminates
all supervised Jobs. Uninstall first stops admission, performs bounded orderly
shutdown, removes the Run entry and service registration, and preserves config/
state/log data unless a separately confirmed purge operation is designed later.
Use bounded rotating service file logs because SCM has no useful interactive
stderr. Flush warnings/errors promptly, redact as elsewhere, and make `Open log`
target the current file after rotation. Add native icon/version/service metadata;
release signing and checksum publication are Phase 8 gates.
### 7.6 Windows-first client tests
Use fake transport/clock tests for shared runtime logic and real Windows
subprocess tests for both shells, CWD/env overlays, at-most-once duplicates,
concurrent output without newlines, stdin ordering/close, Job tree termination,
reconnect/replay, offline truncation, script verification/cleanup, tombstone
rotation, queue limits, and shutdown interruption. Run race tests with multiple
commands and forced network churn. commands and forced network churn.
Add a table-driven crash suite for every acceptance, script-upload, launch, Add a table-driven crash suite for every acceptance, script-upload, launch,
@@ -1038,15 +1237,45 @@ the same duplicate dispatch after each restart and assert exactly one of:
durable rejection before authorization, one supervised process, or an durable rejection before authorization, one supervised process, or an
`interrupted` uncertain launch—never a second execution. `interrupted` uncertain launch—never a second execution.
**Exit criteria:** a Linux client can stay alive through server loss/restart, Fault-inject daemon death around Job creation, suspended shell creation,
execute up to its capacity at most once, preserve/replay bounded history, and launcher pipe authentication, prepared/authorized fsync, release/resume, and
cleanly manage full command process trees. terminal drain; fault the signal helper before/after attach, control-event
delivery, and result. Test paths with spaces/non-ASCII, malicious
`PATH`/`COMSPEC`, nested descendants, hidden-console behavior, CTRL_BREAK
refusal/escalation, Job-limit rejection, cross-session output/stdin, and
inherited-handle leaks.
Add a native table for every hierarchy row: active standard user, traditional
split-token administrator, already-full administrator/UAC-off token, enabled
Administrator Protection where available, logged-out machine, ambiguous RDP
sessions, and logout between selection/revalidation. Assert normal active-user
execution is medium/non-admin; elevated attempts are ordered
`ACTIVE_USER_ELEVATED -> ACTIVE_SYSTEM -> LOCAL_SYSTEM`; no-user normal/elevated
use `LOCAL_SERVICE`/`LOCAL_SYSTEM`; and attempted/effective identities are
persisted and displayed. Inject failure at each token attempt and prove fallback
occurs only before `launch_prepared`, never as a process retry.
Test idempotent UAC installation/uninstallation, SCM Automatic/Manual/start/
stop/restart, preservation of state on uninstall, service boot before login,
service continuity through logon/logoff/tray exit, one tray per session, all tray
actions and authorization checks, malicious named-pipe clients, Explorer
restart, Run-key registration/removal, first-install bootstrap, ProgramData/work
ACLs, Server Core headless behavior, and log rotation on each supported Windows
version. Assert no Task Scheduler object is created or required.
**Exit criteria:** the native Windows client can stay alive through server
loss/restart, execute up to capacity at most once, preserve/replay bounded
history, manage complete Job trees, and satisfy tray/elevation/autostart behavior
on Windows CI, including all execution-hierarchy rows. Do not begin the Linux
supervisor phase until this gate passes.
## 8. End-to-end agent protocol (Phase 5) ## 8. End-to-end agent protocol (Phase 5)
Wire the server session layer and client runtime together before adding the CLI. Wire the server session layer and Windows client runtime together before adding
Use real protobuf bytes through an in-memory WebSocket test server first, then the CLI. Use real protobuf bytes through an in-memory test transport first.
through Docker Compose with nginx proxying a WSS endpoint. Then run Linux server/nginx/storage containers and connect a native Windows CI
runner to their short-lived WSS endpoint; do not substitute Wine or a
cross-compiled binary that was never executed on Windows.
Implement flows in this order: Implement flows in this order:
@@ -1087,9 +1316,10 @@ the visible CLI result. Include these cross-cutting cases:
- heartbeat control traffic remains serviceable while both data lanes are full - heartbeat control traffic remains serviceable while both data lanes are full
over a slow, intermittently writable WebSocket. over a slow, intermittently writable WebSocket.
**Exit criteria:** Compose tests demonstrate a complete background command, **Exit criteria:** a native Windows client against the Compose-hosted Linux
foreground follow, stdin interaction, signal, reconnect, and restart recovery server demonstrates a complete background command, history/follow behavior,
through the nginx WebSocket path. stdin interaction, signal, reconnect, and restart recovery through nginx WSS.
This is the first supported-client milestone and gates Linux supervisor work.
## 9. Server control plane and `rvc` (Phase 6) ## 9. Server control plane and `rvc` (Phase 6)
@@ -1221,70 +1451,72 @@ structured RVBox detail as gRPC.
stack; the Unix socket has mode `0600`; JSON-RPC behavior matches gRPC unary stack; the Unix socket has mode `0600`; JSON-RPC behavior matches gRPC unary
semantics and is off unless explicitly enabled. semantics and is off unless explicitly enabled.
## 10. Windows client implementation (Phase 7) ## 10. Linux client implementation (Phase 7)
Keep Windows code in platform-specific files/build tags so Linux builds never Begin this phase only after the Windows Phase 4/5 release gates pass. Keep Unix
import Windows APIs. Implement the same supervisor interface and event/spool code in platform-specific files/build tags and reuse the proven runtime/store/
runtime; only OS execution/diagnostics/resource enforcement differ. protocol contracts without changing their wire semantics to suit Linux.
1. Create a non-inheritable per-command Job Object, enable The Unix supervisor starts an RVBox launcher as leader of a new session/process
`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, and do not enable breakaway. group; after authorization the launcher creates the selected `sh`/`bash` child
2. Start an RVBox launcher suspended with `CREATE_NEW_CONSOLE`, inside the same group and remains its watchdog. It applies daemon environment
`CREATE_UNICODE_ENVIRONMENT`, and `EXTENDED_STARTUPINFO_PRESENT`. Assign it plus persisted overrides, validates the CWD, connects stdin/stdout/stderr, and
to the Job at creation through `PROC_THREAD_ATTRIBUTE_JOB_LIST`; inherit only records launcher/root/group identities. On shutdown or unclean-start recovery,
an explicit standard-I/O/control handle list and never the Job handle. terminate managed groups and emit `interrupted` rather than claiming pipe
3. Persist and flush `launch_prepared` with launcher PID, `GetProcessTimes` monitoring survived.
creation `FILETIME`, and launch generation; persist and flush
`launch_authorized` before `ResumeThread`. The launcher then creates the
requested shell suspended in its console with `CREATE_NEW_PROCESS_GROUP`,
reports its PID/group, connects the allowlisted pipes, and resumes it. A
failure after authorization terminates the Job and becomes interrupted.
4. Keep the launcher as an in-console signal proxy. This separation is
mandatory: Windows ignores `CREATE_NEW_PROCESS_GROUP` when combined with
`CREATE_NEW_CONSOLE`, and `GenerateConsoleCtrlEvent` reaches only groups
sharing the caller's console. On daemon crash, closing the sole Job handle
terminates the launcher and complete tree; restart never signals by PID alone.
5. Expose Job Object accounting snapshots. Children normally remain in the Job.
6. Accept only `SIGTERM` and `SIGKILL`. For TERM, have the launcher call
`GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, shell_group_id)`, wait 10
seconds, then terminate the Job. For KILL, terminate the Job immediately.
Report graceful-attempt/escalation outcome in the event.
7. Apply requested profile limits through Job Object limits. If the requested
control cannot be applied, reject the dispatch with structured `UNSUPPORTED`.
8. Use available process/Job Object telemetry for CPU/RSS/I/O; do not emit a
Linux-style wait reason.
Implement the Windows launcher as a small mode of the same RVBox binary, not a Implement launch with a private release/watchdog channel. Persist and fsync
searchable external helper. Pass it an inherited, random per-launch control launcher PID/process-group/platform birth identity as `launch_prepared`; recheck
pipe and fixed-size launch-generation token. Apply an explicit revision/cancellation, persist and fsync `launch_authorized`, then release user
`PROC_THREAD_ATTRIBUTE_HANDLE_LIST` so only stdin/stdout/stderr and that control code. Keep the channel open through tree cleanup. EOF before authorization exits
pipe cross creation; make every database, log, listener, Job, and unrelated without execution; EOF after release terminates the group. An authorized but
pipe handle non-inheritable. Frame ready/release/exec/error messages with length, uncertain launch is killed and interrupted, never retried.
type, generation, and checksum, and reject any mismatched generation.
Open the configured shell executable by canonical absolute path and pass it as On Linux create a per-command cgroup v2 for supervision whenever a delegated
the explicit `lpApplicationName`. Produce the command line with one reviewed writable cgroup exists, even without a resource profile. Put the blocked
Windows argument-quoting routine and construct an explicit Unicode environment launcher into it before release using `clone3(CLONE_INTO_CGROUP |
block. Do not invoke `%COMSPEC%`, search `PATH`/file associations, or let command CLONE_PIDFD)` where available; otherwise migrate only the still-blocked launcher
environment overrides influence executable selection. through `cgroup.procs`. Persist cgroup path, pidfd-derived identity where
available, PID/process group, `/proc/<pid>/stat` start time, and launch
generation. Recovery prefers `cgroup.kill`; a PID/process-group fallback is
allowed only after positive birth-identity verification.
Associate the Job with an I/O completion port and use Other Unix platforms use the same barrier plus watchdog/process-group fallback.
`JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO` plus pipe EOF for tree/drain completion. Document that descendants deliberately creating a new session may escape that
Persist the launcher and shell creation `FILETIME` identities before trusting fallback. The at-most-once authorization rule still applies.
any PID. On recovery, reopen a process only to compare creation time and
generation evidence; if ownership cannot be proven, record a dirty incident
rather than terminating an unrelated reused PID.
Fault-inject daemon/launcher death around Job creation, attribute-list process Treat root exit as the configured 5-second tree/output drain grace. Wait for
creation, prepared/authorized fsync, resume, requested-shell creation, and cgroup/process-group emptiness and capture EOF; terminate residual descendants,
terminal drain. Test both supported shells, paths with spaces/non-ASCII, drain, and sequence `OutputIncomplete` if EOF remains unprovable. Emit terminal
malicious `PATH`/`COMSPEC`, nested descendants, CTRL_BREAK refusal/escalation, lifecycle last.
Job limit rejection, and inherited-handle leaks on supported Windows versions.
Run build checks on every platform and dedicated Windows integration tests for Implement portable `HUP`, `INT`, `TERM`, `KILL`, `USR1`, and `USR2`, accepting
shell selection, process-tree kill, forced termination, profile rejection, optional `SIG` prefixes and mapping only through `SignalKind`. Reject native
reconnect, and output/spool behavior. No Windows release is supported until numbers, signal zero, unsupported names, and arbitrary PID targets. Derive group
these tests run on Windows CI. identity only from durable supervisor metadata.
Poll readable `/proc` data outside pipe/network loops for CPU time, RSS, I/O,
state, CWD, and wait reason, aggregating only clearly associated descendants.
Missing data remains absent. Define diagnostic progress as changes in tree CPU
ticks, cumulative I/O, retained input/output activity, or lifecycle. Clear
`suspected_hung` on progress and never let diagnostics alter lifecycle.
Compile profile policy before launch. A no-profile supervisory cgroup imposes
no limit. Profiles apply required `cpu.max`/`cpu.weight`,
`memory.max`/`memory.high`, `pids.max`, and per-device `io.max` values while the
launcher is blocked, then read them back. Without required delegation/control,
permanently reject as `UNSUPPORTED`; never run partly constrained.
Run native Linux integration/race/crash tests mirroring the shared Windows
suite: exact shell resolution, CWD/env, duplicate dispatch, output/stdin,
process-group/cgroup tree cleanup, launcher death at every durable boundary,
reconnect/replay, truncation, scripts, tombstones, queue limits, `/proc`
absence, profile failure, and shutdown interruption. Add explicit tests for
pidfd/`clone3` availability fallbacks and deliberate `setsid` escape behavior.
**Exit criteria:** the Linux client passes the same protocol/durability suite as
Windows, plus cgroup/process-group tests, without weakening the already shipped
Windows behavior or changing the v1 wire contract.
## 11. Reliability, observability, and operational delivery (Phase 8) ## 11. Reliability, observability, and operational delivery (Phase 8)
@@ -1313,6 +1545,9 @@ Provide:
policy, working directory, file descriptor limits, and least privilege; policy, working directory, file descriptor limits, and least privilege;
- example server/client configuration files with every default and an explicit - example server/client configuration files with every default and an explicit
JSON-RPC exposure warning; JSON-RPC exposure warning;
- Windows `rvbox.exe` GUI/icon/version resources, SCM service installation,
per-session tray registration, first-run TOML template, ACL setup, rotating-
file-log support, code-signing hook, and SHA-256 manifest;
- a backup/restore procedure for SQLite plus output/audit segment directories; - a backup/restore procedure for SQLite plus output/audit segment directories;
- an upgrade procedure that stops dispatch safely, snapshots data, migrates, - an upgrade procedure that stops dispatch safely, snapshots data, migrates,
and verifies recovery; and verifies recovery;
@@ -1324,8 +1559,9 @@ operationally important identity/listener/state/TLS/quota keys first in each
section. Add `--check-config` to both daemons: it must run the production strict section. Add `--check-config` to both daemons: it must run the production strict
decoder, defaulting, cross-field/profile/path/shell validation, print a redacted decoder, defaulting, cross-field/profile/path/shell validation, print a redacted
normalized summary, and exit without opening stores/listeners or changing normalized summary, and exit without opening stores/listeners or changing
state. CI parses both examples with this path on Linux; Windows CI additionally state. Linux CI parses the server and all-knob client reference without starting
validates the documented Windows path/shell variant. a client. Native Windows CI parses and normalizes `client.windows.toml` with the
production Windows path/shell/ACL checks.
Use `Delegate=yes` in the Linux client systemd unit when cgroup supervision is Use `Delegate=yes` in the Linux client systemd unit when cgroup supervision is
enabled and create a private writable cgroup subtree for the service. Refuse a enabled and create a private writable cgroup subtree for the service. Refuse a
@@ -1340,7 +1576,10 @@ Before v1 release, review path traversal, script temp-file permissions, command
logging, environment override redaction, malformed compression, decompression logging, environment override redaction, malformed compression, decompression
bombs, oversized frames, Unicode/ASCII ID validation, Unix socket ownership, bombs, oversized frames, Unicode/ASCII ID validation, Unix socket ownership,
JSON-RPC external bind warnings, SQL injection (all parameterized), segment JSON-RPC external bind warnings, SQL injection (all parameterized), segment
record corruption, and process-group PID reuse. Add fuzz tests for envelope record corruption, process-group PID reuse, UAC relaunch argument handling,
SCM service/Run-key executable and config quoting/ownership, ProgramData ACLs,
tray file-opening paths, global single-instance behavior, and Windows inherited
handles. Add fuzz tests for envelope
decode, compressed output validation, segment-tail recovery, pagination tokens, decode, compressed output validation, segment-tail recovery, pagination tokens,
and JSON-RPC parsing. and JSON-RPC parsing.
@@ -1356,10 +1595,12 @@ The recommended merge order is deliberately vertical:
2. Phase 1: domain/config validation and state machine. 2. Phase 1: domain/config validation and state machine.
3. Phase 2: SQLite/segments/audit/retention with recovery tests. 3. Phase 2: SQLite/segments/audit/retention with recovery tests.
4. Phase 3: server registration, fencing, heartbeat, and persisted dispatch. 4. Phase 3: server registration, fencing, heartbeat, and persisted dispatch.
5. Phase 4: Linux client spool/supervisor and at-most-once execution. 5. Phase 4: shared client spool plus Windows supervisor/tray and at-most-once
6. Phase 5: full WSS protocol, fault injection, and nginx end-to-end tests. execution on native Windows CI.
6. Phase 5: full WSS protocol and fault injection from native Windows to the
Compose-hosted Linux server/nginx stack.
7. Phase 6: gRPC Unix socket, `rvc`, and optional JSON-RPC. 7. Phase 6: gRPC Unix socket, `rvc`, and optional JSON-RPC.
8. Phase 7: Windows supervisor and Windows CI. 8. Phase 7: Linux supervisor/cgroup implementation and Linux client parity.
9. Phase 8: operational assets, stress/fuzz/recovery testing, release review. 9. Phase 8: operational assets, stress/fuzz/recovery testing, release review.
Do not merge a later vertical slice by stubbing a durability/safety invariant. Do not merge a later vertical slice by stubbing a durability/safety invariant.
@@ -1368,8 +1609,10 @@ not bypass persistence; client output may be truncated under the documented
caps, but must never block a child pipe; and a reconnection may replay work, caps, but must never block a child pipe; and a reconnection may replay work,
but may never re-execute an already accepted UUID. but may never re-execute an already accepted UUID.
The v1 release is ready only after all release-gate tests pass on clean Docker The Windows-client milestone is releasable only after Phases 0–6 plus its
environments, Linux server/client end-to-end behavior matches the design docs, applicable Phase 8 packaging/security gates pass on native Windows and a clean
Windows support is either fully tested or explicitly not shipped, and every Linux server environment. Windows support cannot be marked optional or replaced
accepted limitation (self-reported identity and unauthenticated optional by cross-compilation-only checks. Full v1 is ready after the later Linux client
JSON-RPC) is conspicuous in deployment documentation. also passes the common protocol/durability suite and Linux-specific cgroup/
process tests. Every release must conspicuously document self-reported identity,
the elevated Windows execution authority, and unauthenticated optional JSON-RPC.
+195 -29
View File
@@ -4,6 +4,10 @@ Daemon configuration uses strict TOML as specified in
[`configuration.md`](configuration.md); the annotated examples contain every [`configuration.md`](configuration.md); the annotated examples contain every
v1 knob and default. v1 knob and default.
The implementation order is Windows client first, Linux client second. The
sections below describe both final contracts; their document order does not
override that release gate.
## Unix-like clients ## Unix-like clients
The client starts `sh` or `bash` in a new session/process group. Unix signals The client starts `sh` or `bash` in a new session/process group. Unix signals
@@ -65,52 +69,210 @@ supervisory cgroup imposes no resource limit.
## Windows clients ## Windows clients
The minimum supported v1 Windows versions are Windows 10 and Windows Server The minimum supported v1 Windows versions are Windows 10 and Windows Server
2016. For every command, create a non-inheritable Job Object, set 2016. Desktop Experience is required only for the tray and active-session
`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, and do not enable breakaway. The daemon execution contexts; the service and Session 0 contexts may run headless on
starts an RVBox per-command launcher suspended with `CREATE_NEW_CONSOLE`, Server Core. Installation is an explicit UAC-elevated operation that registers
`CREATE_UNICODE_ENVIRONMENT`, and `EXTENDED_STARTUPINFO_PRESENT`; it assigns the one machine-wide Automatic SCM service. The service runs as `LocalSystem` before
launcher atomically through `PROC_THREAD_ATTRIBUTE_JOB_LIST`. Only an explicit login and alone owns configuration, WSS, durable state, logs, admission,
standard-I/O and launcher-control handle list is inherited, and the Job handle history, process handles, and Job Objects. Normal service startup never shows
is never inherited. UAC and is not blocked by tray or interactive-user availability.
The launcher invokes exactly the selected shell against the generated wrapper: The same signed `rvbox.exe` has explicit `service`, `tray`, `install-service`,
`uninstall-service`, `configure-service`, per-command launcher, and signal-
helper modes. Internal modes require SCM state or a service-created launch
proof. Task Scheduler is not used. The installer registers an unelevated per-
user tray launch through the machine-wide `Run` key. One tray may run in each
logged-in session; its exit, failure, disablement, or Explorer restart does not
stop the service or commands.
The executable uses the Windows GUI subsystem so service, tray, and internal
helpers do not flash consoles. Human-invoked console modes such as
`--check-config`, install, uninstall, and service configuration first call
`AttachConsole(ATTACH_PARENT_PROCESS)`, rebuild the standard handles, and emit
normal UTF-8 diagnostics when a parent console exists; otherwise they use a
native dialog or documented exit code. Internal launcher arguments are never
printed. This preserves usable terminal help/errors without adding a second
persistent executable or process.
The tray is a thin frontend over a local-only named pipe. It never opens the
SQLite store/spool or owns a server connection. The pipe rejects remote clients,
has explicit ACLs, and the service impersonates each caller for authorization.
Read-only health/status is available to an interactive local user. Service
start/stop/restart, Automatic/Manual startup changes, config editing, incident
resolution, and other machine-wide mutations require a locally elevated
administrator helper. `Exit` closes only that tray. Server Core and logged-out
machines simply have no tray.
When no config path was installed, resolve `FOLDERID_ProgramData` with the
Windows Known Folder API and use `RVBox\client.toml`, `RVBox\state`,
`RVBox\work`, and `RVBox\logs\rvbox.log` beneath it. Do not expand
`%ProgramData%` text from TOML. State and the work root are writable only by
SYSTEM and Administrators. When `cwd` is omitted, the service creates or opens
an identity-scoped child beneath the work root: a user-SID directory for either
active-user context, a LocalService directory, or a SYSTEM-only directory for
each SYSTEM context. Its non-inherited ACL grants only SYSTEM and the effective
non-SYSTEM SID the required access. The service validates the existing owner,
ACL, and reparse-point state before reuse, so one user cannot pre-create or
modify another identity's work area. Config/log read access needed by the tray
is separate from edit access. First installation writes the annotated template
and lets the service remain live but not ready until routing validates.
Configuration is restart-only. File opening uses exact resolved regular paths,
never a constructed shell command.
### Windows execution-context hierarchy
`ExecutionSpec.elevated` is the only caller-facing privilege choice. Windows
combines it with the presence of one usable active interactive session to select
an effective context. The ordered rules are:
| Login state | `elevated` | Ordered pre-launch contexts |
| --- | --- | --- |
| usable active user | false | `active_user` only |
| usable active user | true | `active_user_elevated` -> `active_system` -> `local_system` |
| no usable active user | false | `local_service` only |
| no usable active user | true | `local_system` only |
Here `active_user` is the selected user's deliberately non-elevated token;
`active_user_elevated` is that user's traditional full administrator token;
`active_system` is SYSTEM placed in the selected session; and the two `local_*`
contexts run in Session 0. “Usable” means the deterministic WTS selection and
token validation below succeeded, not merely that some disconnected session
record exists.
Fallback is allowed only during token/session selection before
`launch_prepared`. Failure to create a launcher/shell, an uncertain launch, CWD
or executable rejection, or failure after authorization never tries another
identity. The persisted attempt list and selection detail state why an elevated
request reached SYSTEM. If a formerly active user logs out during selection,
the service re-enumerates once and applies the no-user row; it never retargets a
different user silently.
The LocalSystem service obtains `local_service` with passwordless
`LogonUserW("LocalService", "NT AUTHORITY", NULL, LOGON32_LOGON_SERVICE, ...)`.
`local_system` duplicates the service token. To find an active user, enumerate
WTS sessions and obtain the selected token with `WTSQueryUserToken`. Prefer a
valid active physical-console session; if none exists, accept exactly one
`WTSActive` interactive session. Multiple remaining candidates are ambiguous,
so there is no usable active user rather than a nondeterministic choice.
Immediately before release, require the same session ID, logon SID, and user SID.
For `active_user_elevated`, inspect `TokenElevationType`. A traditional limited
administrator token must expose a linked full token; an already-full
administrator token is usable as-is. A standard user returns
a `CODE_ELEVATION_UNAVAILABLE` attempt reason. Windows Administrator Protection, or
another policy requiring interactive approval rather than exposing a reusable
full token, records a bounded policy-specific attempt reason. V1 never waits
for a UAC/Hello prompt; an elevated request proceeds to `active_system`.
That context duplicates the service token and sets its `TokenSessionId`
explicitly. If session-scoped SYSTEM construction also fails before preparation,
the final context is Session 0 `local_system`. This fallback intentionally
bypasses user-scoped approval because the installed service already holds
SYSTEM; it must be conspicuous in status/audit history. The final
`local_system` fallback supplies elevation but not access to the interactive
desktop: a display, audio, or other session-scoped command may therefore fail
normally in Session 0. Selection fallback is not a promise that the requested
operation is meaningful in the resulting context.
For normal `active_user`, use the filtered token when Windows supplies one. If
an administrator is logged in with only a full token (for example traditional
UAC is disabled), create and verify a LUA-style restricted medium-integrity
token with administrator SIDs deny-only and unnecessary privileges removed.
Never let `elevated=false` inherit a full administrator token merely because of
machine policy; if a verified non-elevated token cannot be built, reject rather
than use SYSTEM or LocalService while that active session remains selected.
Build the base environment from the effective token and then apply persisted
overrides. Session 0 contexts do not inherit a person's profile, mapped drives,
user certificates, or per-user proxy settings. LocalService may use ordinary
DNS/TCP/HTTP(S), localhost sockets, and ACL-permitted local files, but presents
anonymous credentials to remote Windows resources. Active-user contexts load
that user's profile/environment as needed and unload only after the whole Job
exits. `active_system` remains SYSTEM: putting it in the user's session does not
give it that user's HKCU, profile, mapped drives, or network credentials.
### Windows process launch and supervision
For every command, create a non-inheritable Job Object, set
`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, and do not enable breakaway. Use a short-
lived stateless per-command launcher mode of the same executable for every
context. This uniform path is required because Windows does not permit normal
inherited handles across Terminal Services sessions. The launcher owns no
durable state or policy and remains inside the command Job only to bridge
standard I/O and watch its shell.
The service creates local named pipes for launcher control and stdin/stdout/
stderr using unpredictable per-command names, `PIPE_REJECT_REMOTE_CLIENTS`, and
ACLs limited to SYSTEM plus the selected token SID. It creates the launcher
suspended with `CreateProcessAsUser`, no inherited handles, the selected token/
session, and an opaque channel identifier. Before resuming it, the service
assigns the launcher to the empty Job. On connection, verify the named-pipe
client PID, its `GetProcessTimes` creation `FILETIME`, expected token SID,
session ID, and launch generation; a same-user process racing for a pipe cannot
satisfy that complete identity.
The launcher opens only those pipes, creates the selected shell suspended in
its own dedicated console with `CREATE_NEW_CONSOLE`, `CREATE_SUSPENDED`,
`CREATE_UNICODE_ENVIRONMENT`, and `EXTENDED_STARTUPINFO_PRESENT`, and uses
`STARTF_USESHOWWINDOW`/`SW_HIDE`. It inherits only the explicit standard-I/O
handles through `PROC_THREAD_ATTRIBUTE_HANDLE_LIST`. Non-breakaway Job
membership propagates from the launcher. It reports the suspended shell PID and
creation `FILETIME` and waits for the service's release/watchdog channel.
Invoke exactly the selected shell against the generated wrapper:
`cmd.exe /D /S /C` for a `.cmd` wrapper, or `powershell.exe` with `-NoLogo`, `cmd.exe /D /S /C` for a `.cmd` wrapper, or `powershell.exe` with `-NoLogo`,
`-NoProfile`, `-NonInteractive`, and `-File` for a `.ps1` wrapper. Application `-NoProfile`, `-NonInteractive`, and `-File` for a `.ps1` wrapper. Application
paths and argument quoting are constructed by the Windows launcher, never by paths and argument quoting are constructed by one reviewed Windows routine,
concatenating an untrusted command line. There is no fallback between shells. never by concatenating an untrusted command line. There is no fallback.
Persist and flush `launch_prepared` with the launcher PID, Persist and flush `launch_prepared` with requested elevation, attempted/effective
`GetProcessTimes` creation `FILETIME`, and launch generation. Persist and flush context, selection detail, effective token SID, selected session/user SID where
`launch_authorized` before calling `ResumeThread`. The launcher then starts the applicable, launcher and shell PID/creation times, and launch generation while
requested `cmd` or `powershell` suspended in its console with the shell remains suspended.
`CREATE_NEW_PROCESS_GROUP`, reports the shell PID/group through the private Persist and flush `launch_authorized` before sending the release token that
control channel, connects the allowlisted pipes, and resumes it. This two-step makes the launcher call `ResumeThread`. Control-channel EOF before authorization
shape is required because `CREATE_NEW_PROCESS_GROUP` is ignored when combined exits without running user code; EOF afterward terminates the Job. A failure
with `CREATE_NEW_CONSOLE`, and console control events reach only groups sharing after authorization is interrupted and never redispatched. The service retains
the caller's console. Failure after authorization terminates the Job and is the sole Job handle, so an unclean service exit terminates launcher, suspended
reported as interrupted; it never redispatches the UUID. or running shell, and descendants. Recovery never kills by persisted PID alone.
The launcher remains the in-console signal proxy and calls
`GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, shell_group_id)` on request. The
daemon retains the sole Job handle, so an unclean daemon exit closes the last
handle and terminates the launcher, shell, and descendants. Recovery never
kills by persisted PID alone; the PID/creation-time tuple is diagnostic evidence
for PID reuse or cleanup anomalies. Child processes normally join the Job.
Job Object limits enforce requested profiles and `KILL_ON_JOB_CLOSE` protects Job Object limits enforce requested profiles and `KILL_ON_JOB_CLOSE` protects
against lost supervision. against lost supervision.
Do not request `CREATE_NEW_PROCESS_GROUP` with `CREATE_NEW_CONSOLE`; Windows
ignores that combination. TERM instead launches a short-lived private mode of
the same `rvbox.exe` under the command's effective token and session. It uses a
separate PID-verified local named-pipe handshake, not inherited cross-session
handles, to receive the target PID/creation-time/generation. It attaches to the
command's dedicated console, installs a handler that consumes its own
CTRL_BREAK, and calls
`GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0)`. Prefer the live root PID; after
root exit, select and birth-verify a live PID from the Job list. Because each
command owns its console, this does not address an unrelated command. The
helper detaches and reports delivery; it receives neither the Job handle nor
command stdio.
Root-process exit begins the same drain grace period. Completion waits for the Root-process exit begins the same drain grace period. Completion waits for the
Job Object to reach zero active processes; after the grace period RVBox Job Object to reach zero active processes; after the grace period RVBox
terminates the Job, drains its capture handles, records any incomplete-output terminates the Job, drains its capture handles, records any incomplete-output
marker, and emits the terminal lifecycle event last. marker, and emits the terminal lifecycle event last.
Only `TERM`/`SIGTERM` and `KILL`/`SIGKILL` are accepted. `TERM` attempts `CTRL_BREAK_EVENT` Only `TERM`/`SIGTERM` and `KILL`/`SIGKILL` are accepted. `TERM` attempts the
and waits 10 seconds, then calls Job Object termination if the job persists; console-helper `CTRL_BREAK_EVENT` and waits 10 seconds, then calls Job Object
termination if the job persists;
`SIGKILL` calls Job Object termination immediately. A console signal is `SIGKILL` calls Job Object termination immediately. A console signal is
best-effort, so callers receive an explicit escalation result. Windows status best-effort, so callers receive an explicit escalation result. Windows status
uses process and Job Object accounting APIs; it does not claim Linux-only uses process and Job Object accounting APIs; it does not claim Linux-only
diagnostics such as an I/O wait channel. diagnostics such as an I/O wait channel.
Display topology APIs that require the console desktop, DDC/CI monitor
enumeration, and active-user audio policy normally require an active context;
SYSTEM privilege in Session 0 does not substitute for session visibility.
Localhost TCP is machine-wide and normally works in every context, subject to
the listener's own authentication. Fixed local-drive access follows NTFS ACLs;
the client never broadens a requested directory automatically. Every command
record and `stat` view includes requested elevation, attempted/effective
contexts, selection detail, effective token SID, and target session ID/user SID
when present.
## Storage and recovery ## Storage and recovery
SQLite runs in WAL mode. Every append-only segment has a SQLite-owned SQLite runs in WAL mode. Every append-only segment has a SQLite-owned
@@ -186,6 +348,10 @@ crossing one rejects new unreserved allocations even if the logical quota has
headroom. Already-reserved terminal/loss closeout remains writable while bytes headroom. Already-reserved terminal/loss closeout remains writable while bytes
physically remain. physically remain.
The Windows service installs with Automatic startup. Its bounded file logging
defaults to a 10 MiB current file and five retained sealed files under the
resolved ProgramData log directory; tray lifecycle does not affect log output.
These bounds protect RVBox's own loops; they cannot make arbitrary child These bounds protect RVBox's own loops; they cannot make arbitrary child
commands harmless when no resource profile is requested. Operators should commands harmless when no resource profile is requested. Operators should
enable resource profiles for untrusted or expensive workloads and keep nginx, enable resource profiles for untrusted or expensive workloads and keep nginx,
+22
View File
@@ -59,6 +59,14 @@ on `issue_uuid`. A client whose queue is full sends a transient capacity
rejection, which the server requeues with backoff. A permanent validation or rejection, which the server requeues with backoff. A permanent validation or
unsupported-platform rejection makes the server command terminal `rejected`; unsupported-platform rejection makes the server command terminal `rejected`;
it is not retried or mislabeled as a launched-process failure. it is not retried or mislabeled as a launched-process failure.
`ExecutionSpec.elevated=false` requests normal privilege and true requests the
platform's elevated policy; non-Windows clients reject true in v1. Windows
resolves the effective token/session immediately before launch. With a usable
active user, false selects a verified non-elevated `ACTIVE_USER`; true tries
`ACTIVE_USER_ELEVATED`, `ACTIVE_SYSTEM`, then `LOCAL_SYSTEM`. With no usable
active user, false selects `LOCAL_SERVICE` and true selects `LOCAL_SYSTEM`.
Fallback is confined to token selection before `launch_prepared`; it is never a
process retry. Token/session attempts and effective identity are durable history.
An exact UUID/request-hash hit in the compact tombstone ledger returns An exact UUID/request-hash hit in the compact tombstone ledger returns
`CODE_ALREADY_EXECUTED`; the server suppresses dispatch and reconciles its stale `CODE_ALREADY_EXECUTED`; the server suppresses dispatch and reconciles its stale
state instead of representing the prior execution as a new rejection. state instead of representing the prior execution as a new rejection.
@@ -137,6 +145,20 @@ authorization. User cancellation in that interval emits `cancelled`; an
uncertain crash window emits `interrupted`. None is mislabeled as process uncertain crash window emits `interrupted`. None is mislabeled as process
`failed`. `failed`.
On Windows, a context-selection rejection or the `running` lifecycle event
carries `WindowsExecutionIdentity`; later lifecycle events repeat it unchanged.
The server persists it into `CommandRecord`. If selection exhausted every
allowed context, `effective_context` and the effective/session identity fields
are absent. Session 0 effective contexts omit session fields.
Active contexts include both the target session/user SID and actual process-
token SID so `ACTIVE_SYSTEM` cannot be mistaken for execution as the desktop
owner. The ordered `attempted_contexts` and bounded `selection_detail` explain
fallback caused by a standard user, absent linked token, Administrator
Protection, or failed active-SYSTEM token construction. `selection_detail` and
every individual reason embedded in it are subject to the common 4 KiB detail
limit. Token selection and this event are downstream of durable acceptance but
upstream of requested code execution.
## Flow control and failure containment ## Flow control and failure containment
No receive loop runs an executor, database write, decompressor, or slow socket No receive loop runs an executor, database write, decompressor, or slow socket
+1
View File
@@ -40,6 +40,7 @@ message ClientHello {
string daemon_version = 3; string daemon_version = 3;
Platform platform = 4; Platform platform = 4;
string architecture = 5; string architecture = 5;
// Omitted-CWD default on Unix; protected per-identity work root on Windows.
string daemon_cwd = 6; string daemon_cwd = 6;
repeated ShellType supported_shells = 7; repeated ShellType supported_shells = 7;
// Generated once and persisted in the client state directory. // Generated once and persisted in the client state directory.
+36
View File
@@ -35,6 +35,16 @@ enum ShellType {
SHELL_POWERSHELL = 4; SHELL_POWERSHELL = 4;
} }
// Effective Windows process token/session context selected by the client.
enum WindowsExecutionContext {
WINDOWS_EXECUTION_CONTEXT_UNSPECIFIED = 0;
WINDOWS_EXECUTION_CONTEXT_LOCAL_SERVICE = 1;
WINDOWS_EXECUTION_CONTEXT_LOCAL_SYSTEM = 2;
WINDOWS_EXECUTION_CONTEXT_ACTIVE_USER = 3;
WINDOWS_EXECUTION_CONTEXT_ACTIVE_USER_ELEVATED = 4;
WINDOWS_EXECUTION_CONTEXT_ACTIVE_SYSTEM = 5;
}
enum Compression { enum Compression {
COMPRESSION_UNSPECIFIED = 0; COMPRESSION_UNSPECIFIED = 0;
COMPRESSION_NONE = 1; COMPRESSION_NONE = 1;
@@ -101,6 +111,24 @@ message ExecutionSpec {
string command_text = 5; string command_text = 5;
ScriptDescriptor script = 6; ScriptDescriptor script = 6;
} }
// Requests the platform's elevated execution policy. False is the normal
// least-privilege policy. Non-Windows clients reject true in v1.
bool elevated = 7;
}
// Effective Windows identity captured at launch. session_id and
// session_user_sid are absent for Session 0 contexts. effective_user_sid is
// the actual process-token user, not the owner of the target desktop session.
message WindowsExecutionIdentity {
// Absent when every allowed context failed before launch preparation.
optional WindowsExecutionContext effective_context = 1;
optional uint32 session_id = 2;
string session_user_sid = 3;
string effective_user_sid = 4;
// Ordered contexts considered during pre-launch selection, including the
// effective final context. This is never a record of process retries.
repeated WindowsExecutionContext attempted_contexts = 5;
string selection_detail = 6;
} }
message CommandRecord { message CommandRecord {
@@ -121,6 +149,9 @@ message CommandRecord {
ControlError rejection = 14; ControlError rejection = 14;
uint64 command_revision = 15; uint64 command_revision = 15;
bool late_after_expiry = 16; bool late_after_expiry = 16;
// Present after Windows context selection was attempted, including a
// pre-launch rejection for which effective_context is absent.
WindowsExecutionIdentity windows_execution_identity = 17;
} }
message LifecycleChange { message LifecycleChange {
@@ -128,6 +159,9 @@ message LifecycleChange {
optional int32 exit_code = 2; optional int32 exit_code = 2;
string detail = 3; string detail = 3;
uint64 command_revision = 4; uint64 command_revision = 4;
// Set on a Windows context-selection rejection or RUNNING, then repeated
// unchanged on later lifecycle events.
WindowsExecutionIdentity windows_execution_identity = 5;
} }
// data is compressed according to compression. uncompressed_size is mandatory // data is compressed according to compression. uncompressed_size is mandatory
@@ -235,6 +269,8 @@ message ControlError {
TRANSIENT = 8; TRANSIENT = 8;
INTERNAL = 9; INTERNAL = 9;
CODE_ALREADY_EXECUTED = 10; CODE_ALREADY_EXECUTED = 10;
CODE_EXECUTION_CONTEXT_UNAVAILABLE = 11;
CODE_ELEVATION_UNAVAILABLE = 12;
} }
Code code = 1; Code code = 1;
string message = 2; string message = 2;
+1
View File
@@ -35,6 +35,7 @@ message ClientSummary {
Platform platform = 7; Platform platform = 7;
string architecture = 8; string architecture = 8;
string daemon_version = 9; string daemon_version = 9;
// Omitted-CWD default on Unix; protected per-identity work root on Windows.
string daemon_cwd = 10; string daemon_cwd = 10;
repeated ShellType supported_shells = 11; repeated ShellType supported_shells = 11;
string client_instance_id = 12; string client_instance_id = 12;