docs: finalize v1 design and Windows service model
This commit is contained in:
+44
-13
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user