docs: integrate domain-scoped multi-instance multiplexing and persistent overlay sharing
Overhaul the architectural specification (specification.md) and reorganize the developer journals to incorporate the new Domain Anchor model. This scopes sandbox container names, lockfiles, control sockets, and State Anchor persistent overlay filesystem layers to unique (agent, workspace) hash pairings. - Add Domain Anchor and Domain Root definitions to glossary and expansion tokens. - Specify the Ephemeral Session Coordinator pattern with hold-to-bind locking. - Document Git Sentry nested read-only mount layout replacing the legacy Git Shadow. - Renamed and reorganized research sub-journals, embedding executable Python logic in-place. - Include `redb` for embedded transactional state.
This commit is contained in:
parent
d3096285d4
commit
f7ba1d539f
1 changed files with 253 additions and 128 deletions
|
|
@ -7,15 +7,21 @@
|
||||||
|
|
||||||
| Term | Definition |
|
| Term | Definition |
|
||||||
| :-------------------- | :--------- |
|
| :-------------------- | :--------- |
|
||||||
| **Agent Workspace** | The bind-mounted host project directory inside the Agent Sandbox (at `/workspace`). |
|
| **Agent Workspace** | The bind-mounted host project directory inside the **Agent Sandbox** (at `/workspace`). |
|
||||||
| **Agent Sandbox** | The gVisor (`runsc`) container running a profile. |
|
| **Agent Sandbox** | The gVisor (`runsc`) container running an agent profile, scoped uniquely to a **Domain Anchor** (representing a specific agent and canonical workspace path pairing). |
|
||||||
| **Network Sidecar** | The shell-less gateway container (from `scratch`) running the Netlink router under `runc`. |
|
| **Client Session** | A dynamic pairing of an active host `lazaret` CLI process and its corresponding containerized shell session inside the **Agent Sandbox**, secured by a **Session Anchor** and a **Trust Anchor**. |
|
||||||
| **Git Shadow** | The tmpfs masking + read-only shadow mount of the host `.git` + selective write-back binds + symlink redirection for config and HEAD. |
|
| **Session Anchor** | The ephemeral asymmetric keypair $(K_{pub\_proc}, K_{priv\_proc})$ generated by a host CLI process upon startup, used to cryptographically sign process-level escalation requests. |
|
||||||
| **Host Escalation** | The secure UDS channel (with ephemeral mTLS) from the Escalation Client to the host TUI for approved host actions. |
|
| **Session Coordinator**| The background-detached, decentralized host coordinator process that manages the container, **Network Sidecar**, and **State Anchor** overlays for a specific **Domain Anchor**. |
|
||||||
| **Escalation Client** | The statically linked `lazaret-escalate` binary. |
|
| **Network Sidecar** | The shell-less gateway container (from `scratch`) running the Netlink router under `runc`, scoped uniquely to a **Domain Anchor** to enforce network egress policies. |
|
||||||
| **Escalation Socket** | The profile-specific Unix domain socket. |
|
| **Git Sentry** | The hybrid nesting layout consisting of the read-write mounted host `.git` under `/workspace/.git`, paired with a transient `tmpfs` mask over `hooks`, and a read-only bind mount of `.git/config`. |
|
||||||
| **Profile Root** | `<data_root>/<profile_name>`. |
|
| **State Anchor** | The host-side `fuse-overlayfs` structure (`usr_changes`, `usr_work`, `usr_merged`) that merges a read-only container base OS directory with a persistent, writable host layer (scoped under the **Domain Root**), bind-mounted over container system paths (e.g., `/usr`) to preserve state for that specific agent-workspace pairing across container restarts. |
|
||||||
| **Storage Root** | The configured `data_root` for persistent profile data. |
|
| **Trust Anchor** | The in-memory, local ephemeral Certificate Authority (CA) managed by the host orchestrator or **Session Coordinator** on startup to issue short-lived cryptographic client certificates to sandboxes for secure mutual TLS (mTLS) identity verification over the **Escalation Socket**. |
|
||||||
|
| **Host Escalation** | The secure UDS channel (secured by mTLS via the **Trust Anchor**) from the **Escalation Client** to the host TUI for approved host actions. |
|
||||||
|
| **Escalation Client** | The statically compiled `lazaret-escalate` binary, volume-mounted read-only inside the sandbox. |
|
||||||
|
| **Escalation Socket** | The UNIX domain socket scoped to a **Domain Anchor** used for routing **Host Escalation** commands. |
|
||||||
|
| **Domain Anchor** | The deterministic, cryptographically derived boundary (representing the pairing of a specific agent profile and a unique absolute host workspace path) used to scope and namespace the sandbox container name, the **Session Coordinator**'s control socket, the advisory lockfile, and the persistent **State Anchor** storage. |
|
||||||
|
| **Domain Root** | The directory on the host filesystem (`<data_root>/domains/<domain_anchor_hash>/`) under the **Storage Root** where the **State Anchor** and other domain-scoped files are stored. |
|
||||||
|
| **Storage Root** | The host directory configured via the `data_root` property, serving as the base folder for all persistent agent profiles and domain storage. |
|
||||||
|
|
||||||
## Fixed In-Container Paths
|
## Fixed In-Container Paths
|
||||||
|
|
||||||
|
|
@ -25,34 +31,22 @@ Lazaret-managed files and sockets inside every Agent Sandbox live under `/run/la
|
||||||
| :---------------------- | :---------- |
|
| :---------------------- | :---------- |
|
||||||
| `/run/lazaret/escalate` | The Escalation Client binary (volume-mounted, read-only). |
|
| `/run/lazaret/escalate` | The Escalation Client binary (volume-mounted, read-only). |
|
||||||
| `/run/lazaret/escalate.sock`| The Escalation Socket. |
|
| `/run/lazaret/escalate.sock`| The Escalation Socket. |
|
||||||
| `/run/lazaret/git` | Target directory for the Git Shadow (read-only bind). |
|
| `/run/lazaret/certs` | Ephemeral Trust Anchor client certificates (memory-backed tmpfs, read-only). |
|
||||||
| `/run/lazaret/certs` | Ephemeral mTLS client certificates (memory-backed tmpfs, read-only). |
|
|
||||||
|
|
||||||
The **Agent Workspace** is mounted at `/workspace`.
|
The **Agent Workspace** is mounted at `/workspace`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Problem Statement & Goals
|
## 1. System Objectives & Goals
|
||||||
|
|
||||||
### 1.1 The Security vs. Capability Dilemma
|
The primary objectives of Lazaret are to establish a secure, rootless, and isolated sandbox for untrusted AI agent execution while maintaining a functional developer experience through secure host escalation, persistent state, and real-time Git synchronization.
|
||||||
Modern AI agents require powerful execution capabilities (e.g., running code, interacting with git, testing microservices). However, running untrusted agent-generated code directly on a developer's host machine poses severe security risks:
|
|
||||||
* **Host Privilege Escalation:** Untrusted code can access SSH keys, local credentials, or modify system files.
|
|
||||||
* **Network Escapes:** A rogue agent can scan local subnets, exfiltrate data, or interact with sensitive internal services (RFC 1918 ranges).
|
|
||||||
* **Git Hook Injection:** An agent can write a malicious git hook (e.g., `.git/hooks/post-checkout`) that executes code on the host machine when the developer runs simple git commands.
|
|
||||||
* **Git Config Exploitation:** An agent can write a malicious git pager config (e.g., `core.pager = malicious_command`) which executes on the host when the developer runs git log or git diff.
|
|
||||||
|
|
||||||
### 1.2 The Developer Experience (DX) Friction
|
### 1.1 Core Objectives
|
||||||
Existing container-based isolation solutions (like standard Docker/Podman running on runc) introduce high friction:
|
* **Deep Virtualization-Based Isolation:** Run AI agent workspaces in rootless Podman containers utilizing the **gVisor (`runsc`)** sandbox runtime to establish a host-container kernel boundary.
|
||||||
* **Terminal Degradation:** Terminfo mismatches (e.g., running Ghostty, Kitty, or Alacritty) cause terminal applications to crash inside the container.
|
* **Network Sidecar:** Route container traffic through a statically compiled, shell-less network gateway container (`scratch`) running a custom Netlink router that dynamically applies egress policies.
|
||||||
* **Transient State Loss:** Re-authenticating with developer tools, subscriptions, or package manager registries (npm, cargo, etc.) on every sandbox restart is highly disruptive.
|
* **Persistent State:** Provide configurable persistence folders via a **State Anchor** to persist designated system and workspace directories across container restarts.
|
||||||
* **Impoverished Git Integration:** Masking the entire `.git` directory prevents agents from running basic queries (like `git diff` or `git status`), severely crippling their capabilities.
|
* **Git Hook & Config Protection:** Enforce the **Git Sentry** isolation pattern to block git hook writes and config pager modifications, while permitting standard local git commands and tracking upstream configuration changes.
|
||||||
|
* **Host Escalation:** Provide a secure UNIX Domain Socket (UDS) **Escalation Socket** channel, verified via the **Trust Anchor** (using mTLS), to route structured command execution requests from the **Escalation Client** inside the Agent Sandbox to the host orchestrator for user approval or modification.
|
||||||
### 1.3 Core Objectives
|
|
||||||
* **Deep Virtualization-Based Isolation:** Run AI agent workspaces in rootless Podman containers utilizing the **gVisor (`runsc`)** sandbox runtime, establishing a strong kernel boundary.
|
|
||||||
* **Network Sidecar:** Route container traffic through a statically compiled, shell-less network gateway container (`scratch`) running a custom Netlink router that dynamically applies egress policies, bypassing gVisor Netlink capabilities limits.
|
|
||||||
* **Persistent State & Subscriptions:** Provide configurable persistence folders so that packages, tools, and developer authentications (OAuth, CLI subscriptions) survive container restarts via host-persistent directory mapping.
|
|
||||||
* **Git Hook & Config Protection with Full Git DX:** Neutralize git hook injection and config pager-script exploits to protect the host, while maintaining complete local git write (commits, staging, branch checking) and read functionality inside the sandbox, ensuring real-time configuration visibility.
|
|
||||||
* **Host Escalation:** Provide a secure **Escalation Socket** (UDS) channel with ephemeral mTLS that allows the **Escalation Client** inside the Agent Sandbox to request host-bound actions, displaying an interactive terminal prompt to the user for approval or modification.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -61,7 +55,7 @@ Existing container-based isolation solutions (like standard Docker/Podman runnin
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
subgraph Host Machine
|
subgraph Host Machine
|
||||||
Orchestrator[Lazaret Host CLI / Daemon]
|
Orchestrator[Lazaret Host CLI Process]
|
||||||
Config[~/.config/lazaret/config.toml]
|
Config[~/.config/lazaret/config.toml]
|
||||||
Storage[Sandbox Storage Root / NVMe]
|
Storage[Sandbox Storage Root / NVMe]
|
||||||
TUI[Interactive HITL TUI Prompt]
|
TUI[Interactive HITL TUI Prompt]
|
||||||
|
|
@ -70,7 +64,7 @@ graph TD
|
||||||
|
|
||||||
subgraph Container Isolation Boundary
|
subgraph Container Isolation Boundary
|
||||||
subgraph Gateway Sidecar (scratch - runc)
|
subgraph Gateway Sidecar (scratch - runc)
|
||||||
RouteDaemon[Netlink Router Daemon]
|
RouteProcess[Netlink Router Process]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Agent Sandbox (Debian Trixie - runsc/gVisor)
|
subgraph Agent Sandbox (Debian Trixie - runsc/gVisor)
|
||||||
|
|
@ -81,11 +75,11 @@ graph TD
|
||||||
|
|
||||||
Orchestrator -->|Loads| Config
|
Orchestrator -->|Loads| Config
|
||||||
Orchestrator -->|Initializes Storage| Storage
|
Orchestrator -->|Initializes Storage| Storage
|
||||||
Orchestrator -->|Spins up Sidecar| RouteDaemon
|
Orchestrator -->|Spins up Sidecar| RouteProcess
|
||||||
Orchestrator -->|Launches Sandbox| AgentProcess
|
Orchestrator -->|Launches Sandbox| AgentProcess
|
||||||
|
|
||||||
RouteDaemon -->|Configures Netlink| GatewaySidecarNetwork
|
RouteProcess -->|Configures Netlink| GatewaySidecarNetwork
|
||||||
AgentProcess -->|Binds Network Namespace| RouteDaemon
|
AgentProcess -->|Binds Network Namespace| RouteProcess
|
||||||
|
|
||||||
AgentProcess -->|Requests host command| EscalateCLI
|
AgentProcess -->|Requests host command| EscalateCLI
|
||||||
EscalateCLI -->|JSON Request via UDS| UDS_Server
|
EscalateCLI -->|JSON Request via UDS| UDS_Server
|
||||||
|
|
@ -105,7 +99,7 @@ Configuration files are parsed hierarchically (lowest precedence to highest):
|
||||||
2. Workspace local override: `./lazaret.toml` (in the active working directory)
|
2. Workspace local override: `./lazaret.toml` (in the active working directory)
|
||||||
3. Direct override command-line argument: `--config <file>`
|
3. Direct override command-line argument: `--config <file>`
|
||||||
|
|
||||||
A concrete template demonstrating all 5 MVP agent profiles is located in the repository as `example.toml`.
|
A configuration template demonstrating representative agent profiles is located in the repository as `example.toml`.
|
||||||
|
|
||||||
### 3.1 Schema Contract Specification
|
### 3.1 Schema Contract Specification
|
||||||
|
|
||||||
|
|
@ -123,8 +117,8 @@ A concrete template demonstrating all 5 MVP agent profiles is located in the rep
|
||||||
#### Section `[escalation]`
|
#### Section `[escalation]`
|
||||||
| Property | Type | Required | Description |
|
| Property | Type | Required | Description |
|
||||||
| :--- | :--- | :--- | :--- |
|
| :--- | :--- | :--- | :--- |
|
||||||
| `enabled` | Boolean | Yes | Activates or suspends the host-side escalation daemon. |
|
| `enabled` | Boolean | Yes | Activates or suspends the host-side **Escalation Socket** listener during the lifecycle of the CLI command. |
|
||||||
| `socket_dir` | String | Yes | Path on the host filesystem (e.g. `/run/user/1000/lazaret/` or `/tmp/lazaret`) where profile-specific Unix escalation sockets are created. |
|
| `socket_dir` | String | Yes | Path on the host filesystem (e.g. `/run/user/1000/lazaret/` or `/tmp/lazaret`) where profile-specific Unix **Escalation Socket**s are created. |
|
||||||
| `auto_approve` | Array of Strings | No | List of user-defined CLI commands allowed to execute on the host automatically without interactive confirmation. |
|
| `auto_approve` | Array of Strings | No | List of user-defined CLI commands allowed to execute on the host automatically without interactive confirmation. |
|
||||||
|
|
||||||
#### Section `[profiles.<profile_name>]`
|
#### Section `[profiles.<profile_name>]`
|
||||||
|
|
@ -143,10 +137,10 @@ A concrete template demonstrating all 5 MVP agent profiles is located in the rep
|
||||||
|
|
||||||
### 3.2 Variable Expansion, Templating & Sanitation Specification
|
### 3.2 Variable Expansion, Templating & Sanitation Specification
|
||||||
|
|
||||||
To maintain a secure sandbox quarantine and provide an ergonomic configuration experience, Lazaret implements a custom, zero-dependency, safe variable substitution engine rather than a heavy template engine like Jinja or Handlebars. This minimizes the attack surface and compilation footprint.
|
Lazaret implements a custom, zero-dependency, safe variable substitution engine for configuration parsing.
|
||||||
|
|
||||||
#### 3.2.1 Linux-Only Tilde (`~`) Expansion
|
#### 3.2.1 Linux-Only Tilde (`~`) Expansion
|
||||||
Because the core orchestration dependencies of Lazaret (**rootless Podman** and **gVisor `runsc`**) run exclusively on Linux architectures, the codebase is compiled and optimized solely for Linux systems. Path resolution and tilde expansion are handled using Linux standards:
|
Lazaret supports only Linux-based host environments. Path resolution and tilde (`~`) expansion must conform to Linux directory standards:
|
||||||
* Lazaret retrieves the host user's home directory by reading the `$HOME` environment variable, falling back to query the host's `/etc/passwd` entry if `$HOME` is unpopulated.
|
* Lazaret retrieves the host user's home directory by reading the `$HOME` environment variable, falling back to query the host's `/etc/passwd` entry if `$HOME` is unpopulated.
|
||||||
* **Strict Validation:** Tilde is only expanded if it is the absolute first character of the path string (e.g. `~/config` resolves to `/home/user/config`, but `some/path/~` is treated as a literal directory named `~`).
|
* **Strict Validation:** Tilde is only expanded if it is the absolute first character of the path string (e.g. `~/config` resolves to `/home/user/config`, but `some/path/~` is treated as a literal directory named `~`).
|
||||||
|
|
||||||
|
|
@ -154,10 +148,12 @@ Because the core orchestration dependencies of Lazaret (**rootless Podman** and
|
||||||
Lazaret parses and expands a small, strictly defined whitelist of local variables inside configuration fields:
|
Lazaret parses and expands a small, strictly defined whitelist of local variables inside configuration fields:
|
||||||
* `{profile_name}`: Evaluates to the lowercase alphanumeric name of the active agent profile.
|
* `{profile_name}`: Evaluates to the lowercase alphanumeric name of the active agent profile.
|
||||||
* `{profile_dir}`: Expands dynamically on the host to: `{data_root}/{profile_name}`.
|
* `{profile_dir}`: Expands dynamically on the host to: `{data_root}/{profile_name}`.
|
||||||
* `{workspace_dir}`: Evaluates to the current host working directory (`std::env::current_dir()`).
|
* `{domain_hash}`: Evaluates to the 12-character SHA-256 hexadecimal hash representing the Domain Anchor of the active `(agent_name, canonical_workspace_path)` pair.
|
||||||
|
* `{domain_dir}`: Expands dynamically on the host to the Domain Root directory: `{data_root}/domains/{domain_hash}`.
|
||||||
|
* `{workspace_dir}`: Evaluates to the current host working directory.
|
||||||
|
|
||||||
#### 3.2.3 Sanitation & Directory Traversal Protection
|
#### 3.2.3 Sanitation & Directory Traversal Protection
|
||||||
Because AI agents might attempt to manipulate configuration templates or profile names to trigger directory escapes on the host machine, Lazaret enforces three strict structural validation boundaries:
|
Lazaret enforces three structural validation boundaries for configuration paths:
|
||||||
|
|
||||||
1. **Profile Name Sanitation:**
|
1. **Profile Name Sanitation:**
|
||||||
Profile names must strictly match the regex pattern: `^[a-z0-9_-]{2,32}$`. Any profile name containing dots (`.`), slashes (`/`, `\`), or shell metacharacters is immediately rejected, preventing injection attacks when creating paths like `{data_root}/{profile_name}`.
|
Profile names must strictly match the regex pattern: `^[a-z0-9_-]{2,32}$`. Any profile name containing dots (`.`), slashes (`/`, `\`), or shell metacharacters is immediately rejected, preventing injection attacks when creating paths like `{data_root}/{profile_name}`.
|
||||||
|
|
@ -166,138 +162,253 @@ Because AI agents might attempt to manipulate configuration templates or profile
|
||||||
Before resolving any expanded path, Lazaret passes the resulting string to a sanitation function that checks for the presence of relative path segments. If a resolved path contains a dot-dot segment (`..`), the execution is aborted immediately with a security panic, preventing the agent from escaping the storage boundaries.
|
Before resolving any expanded path, Lazaret passes the resulting string to a sanitation function that checks for the presence of relative path segments. If a resolved path contains a dot-dot segment (`..`), the execution is aborted immediately with a security panic, preventing the agent from escaping the storage boundaries.
|
||||||
|
|
||||||
3. **Absolute Path Verification:**
|
3. **Absolute Path Verification:**
|
||||||
All host paths used for mounts, storage, and volumes are canonicalized using Rust's `std::fs::canonicalize` or resolved to absolute paths before being passed to Podman execution commands. This prevents relative symlink tricks or path obfuscation from bypassing rootless boundaries.
|
All host paths used for mounts, storage, and volumes are canonicalized (resolving all symlinks and relative segments to absolute paths) before being passed to Podman execution commands. This prevents relative symlink tricks or path obfuscation from bypassing rootless boundaries.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Key Security & Functional Mechanisms
|
## 4. Key Security & Functional Mechanisms
|
||||||
|
|
||||||
### 4.1 Git Hook & Config Isolation (Git Shadow)
|
### 4.1 Git Hook & Config Isolation (Git Sentry)
|
||||||
To completely secure the developer's host environment against git hook injection and config pager exploits while preserving complete Git capabilities and real-time configuration visibility, Lazaret implements a strict filesystem mount decoupling strategy.
|
Lazaret isolates the container's Git environment and prevents modifications to host configuration and hook files using a nested ("Inside Mount") layout called **Git Sentry**.
|
||||||
|
|
||||||
Direct single-file read-only bind mounts of `.git/config` are prohibited due to the Unix **stale inode problem**: when git configuration is updated on the host, Git atomically unlinks and replaces `.git/config`, causing single-file bind mounts to remain permanently bound to the stale, unlinked file inode.
|
When a git configuration is updated on the host, Git atomically unlinks and replaces `.git/config`. A direct single-file read-only bind mount of `.git/config` remains bound to the stale, unlinked file inode inside the container namespace (the Unix stale inode problem). To ensure that the container namespace dynamically reflects host-side updates, the read-only file mount must be nested inside a parent read-write bind-mounted directory, allowing the kernel to resolve the updated inode path dynamically.
|
||||||
|
|
||||||
#### 4.1.1 The Decoupling Protocol
|
#### 4.1.1 The Decoupling Protocol
|
||||||
Rather than mounting `.git` directly as a read-write or simple read-only directory, the host orchestrator constructs a hybrid virtual directory structure inside the container's mount namespace:
|
The host orchestrator constructs a nested virtual layout inside the container's mount namespace to isolate the Git configuration and hook directories:
|
||||||
|
|
||||||
1. **Agent Workspace Mount:** The active **Agent Workspace** is bind-mounted read-write (with the standard `:z` flag for rootless SELinux compatibility).
|
1. **Agent Workspace Mount:** The active **Agent Workspace** is bind-mounted read-write at `/workspace` (utilizing the `:z` flag for rootless SELinux compatibility). This exposes the host's `.git/index` to enable standard git status and staging operations from within the container.
|
||||||
2. **Git Directory Masking:** The orchestrator overlays a memory-backed `tmpfs` volume directly over the `.git` directory inside the Agent Workspace namespace, hiding the host's physical `.git` directory.
|
2. **Hooks Sandbox Mount:** A memory-backed `tmpfs` volume is mounted over `/workspace/.git/hooks` inside the container to isolate hook creation and execution to transient container memory.
|
||||||
3. **Shadow Mount Injection:** The host's actual `.git` directory is bind-mounted **read-only** to the Git Shadow target inside the container.
|
3. **Config Read-Only Mount:** The host's `.git/config` file is bind-mounted read-only over `/workspace/.git/config`. Because this mount is nested inside the parent bind-mounted `/workspace` directory, parent directory resolution propagates host-side atomic replacements of `.git/config` to the container namespace, bypassing the stale inode problem.
|
||||||
4. **Write-Back Bind Mounts:** To support standard Git modifications inside the Agent Workspace (such as staging changes, recording commits, and managing branches), the host's writeable Git directories are bind-mounted read-write directly into the memory-backed `.git/` inside the Agent Workspace:
|
|
||||||
* Host objects directory → write-back object database inside the Agent Workspace.
|
|
||||||
* Host refs directory → write-back refs inside the Agent Workspace.
|
|
||||||
* Host logs directory → write-back logs inside the Agent Workspace.
|
|
||||||
5. **Read-Only Symlink Redirection:** Critical configurations inside the Agent Workspace are exposed using symbolic links pointing to the secure, read-only Git Shadow target:
|
|
||||||
* `.git/config` is symlinked to the Git Shadow.
|
|
||||||
* `.git/HEAD` is symlinked to the Git Shadow.
|
|
||||||
|
|
||||||
#### Filesystem Mount Mapping Table
|
#### Filesystem Mount Mapping Table
|
||||||
| Path in Agent Workspace | Mount Type | Properties / Permissions | Purpose |
|
| Path in Agent Workspace | Mount Type | Properties / Permissions | Purpose |
|
||||||
| :--- | :--- | :--- | :--- |
|
| :--- | :--- | :--- | :--- |
|
||||||
| `/workspace` | Bind Mount | Read / Write (`z` shared) | Shares the active project folder with the Agent Sandbox. |
|
| `/workspace` | Bind Mount | Read / Write (`z` shared) | Shares the active project folder with the Agent Sandbox. |
|
||||||
| `/workspace/.git` | `tmpfs` | Memory-only, transient | Masks the git folder inside the Agent Workspace, blocking direct host hook writes. |
|
| `/workspace/.git/hooks` | `tmpfs` | Memory-only, transient | Masks the git hooks folder inside the Agent Workspace, blocking direct host hook writes. |
|
||||||
| `/workspace/.git/objects`| Bind Mount | Read / Write (`z` shared) | Write-back object database (host objects mounted into Agent Workspace). |
|
| `/workspace/.git/config`| Bind Mount | Read-Only (`ro`, `z` shared) | Prevents modification of git configurations, while allowing dynamic updates from host. |
|
||||||
| `/workspace/.git/refs` | Bind Mount | Read / Write (`z` shared) | Write-back refs (branches/stashes) into the Agent Workspace. |
|
|
||||||
| `/workspace/.git/logs` | Bind Mount | Read / Write (`z` shared) | Write-back reflogs into the Agent Workspace. |
|
|
||||||
| `/workspace/.vscode` | `tmpfs` | Memory-only | Masks user's host VSCode preferences inside the Agent Workspace. |
|
| `/workspace/.vscode` | `tmpfs` | Memory-only | Masks user's host VSCode preferences inside the Agent Workspace. |
|
||||||
| `/workspace/.idea` | `tmpfs` | Memory-only | Masks user's host IntelliJ configurations inside the Agent Workspace. |
|
| `/workspace/.idea` | `tmpfs` | Memory-only | Masks user's host IntelliJ configurations inside the Agent Workspace. |
|
||||||
| `/run/lazaret/git` | Bind Mount | Read-Only (`ro`, `z` shared) | Git Shadow target directory (read-only). |
|
|
||||||
|
|
||||||
#### 4.1.2 Security & Operational Guarantees
|
#### 4.1.2 Security & Operational Guarantees
|
||||||
* **Immutable Target Integrity:** Any direct write or append attempt targeting `.git/config` inside the Agent Workspace fails at the kernel level with `EROFS` (Read-only file system) because the resolved symlink target resides on the read-only Git Shadow.
|
* **Immutable Target Integrity:** Write or append attempts targeting `/workspace/.git/config` inside the container must fail at the kernel level with `EROFS`.
|
||||||
* **Container Bypass Neutralization:** If a malicious agent process deletes the symlink and writes a new, rogue configuration file at that path, **the write remains entirely isolated within the local memory-backed `tmpfs`.** The host's actual `.git/config` is never touched, and host commands remain completely secure.
|
* **Deletion Blocked:** Deletion or unlinking of `/workspace/.git/config` inside the container must be blocked by the kernel VFS layer with `EBUSY` (Device or resource busy).
|
||||||
* **Instant Configuration Visibility:** Because the Git Shadow target is mounted as a **directory**, any host-side atomic replacements of files within it (such as a developer running `git config` on the host, which unlinks and replaces `.git/config`) are instantly visible inside the Git Shadow. The container's symlink immediately resolves to the updated configuration without getting stuck on a stale, unlinked inode.
|
* **Instant Configuration Visibility:** Updates to `.git/config` on the host must propagate instantly to the container's `/workspace/.git/config` mount via parent directory resolution.
|
||||||
* **Safe Isolated Branch Switching:** The Agent Sandbox can safely execute `git checkout` to switch branches. Git writes branch reference updates to the write-back refs location (which safely propagates back to the host), but writes the active branch state to `HEAD`. If the write to the read-only `HEAD` symlink fails, the container's git CLI simply deletes the symlink and writes a real, local `HEAD` file to the writeable `tmpfs`. This isolates the active sandbox branch state, preventing container-side branch switches from disrupting the host developer's active branch workspace.
|
* **State Propagation:** Branch modifications and reference writes made by the container to `/workspace/.git/HEAD` and `/workspace/.git/refs` must propagate to the host in real-time.
|
||||||
|
|
||||||
### 4.2 Sandbox Git Worktree/Branch Isolation
|
### 4.2 Sandbox Git Worktree/Branch Isolation
|
||||||
To prevent the agent from cluttering the developer's working index, Lazaret supports working on isolated branch namespaces:
|
Lazaret isolates sandbox git actions to a dedicated branch namespace:
|
||||||
* **Dedicated Branch:** Lazaret can auto-derive a branch `lazaret/agent-<name>` from the active host commit.
|
* **Dedicated Branch:** Lazaret must automatically derive an agent-specific branch name (`lazaret/agent-<name>`) from the active host commit.
|
||||||
* **Stash Protection:** The developer's host working directory remains intact. The developer can inspect changes on the host with `git diff lazaret/agent-<name>` or merge them explicitly.
|
* **Stash Protection:** The host workspace active working tree must remain unmodified during sandbox execution. Changes made by the agent within the sandbox must be committed or written only to the dedicated branch, allowing the user to inspect differences on the host with `git diff` or merge changes explicitly.
|
||||||
|
|
||||||
### 4.3 Image-Agnostic State Persistence via Host-Side `fuse-overlayfs`
|
### 4.3 State Scoping & Multi-Instance Lifecycle via Domain Anchor
|
||||||
When a sandbox initializes, Lazaret provides state persistence (e.g., preserving installed packages, dependencies, and tools) across container deletions and recreations without copying out entire filesystems or bloating host storage. This is achieved by orchestrating a host-side user-space overlay filesystem (`fuse-overlayfs`) under the rootless container namespace.
|
|
||||||
|
|
||||||
#### 4.3.1 Directory Layout on Host
|
Lazaret scopes persistent state, socket routing, and container lifecycles to a cryptographically derived **Domain Anchor**.
|
||||||
For each profile, the host orchestrator establishes the following directory structure under `{data_root}/{profile_name}/` (which host volume `{profile_dir}` evaluates to):
|
|
||||||
* `usr_changes/`: The persistent, writeable layer (`upperdir`) that stores user-installed packages, configurations, and modifications. Initially empty.
|
#### 4.3.1 Domain Anchor Derivation Protocol
|
||||||
|
For every CLI invocation, the host orchestrator must compute a unique, deterministic 12-character hexadecimal identifier (the `domain_hash`) representing the active boundary.
|
||||||
|
The hash is derived as follows:
|
||||||
|
1. Retrieve the lowercase alphanumeric agent profile name.
|
||||||
|
2. Canonicalize the absolute filesystem path of the active **Agent Workspace** on the host.
|
||||||
|
3. Compute the SHA-256 digest of the UTF-8 encoded string: `"<agent_name>:<canonical_workspace_path>"`.
|
||||||
|
4. Truncate the resulting hexadecimal string to its first 12 characters.
|
||||||
|
|
||||||
|
This identifier must be used to isolate host-side resources. The resource mapping rules are defined below:
|
||||||
|
|
||||||
|
| Resource Type | Host Pathing Rule | Format / Name Scheme |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Domain Root** | `{data_root}/domains/{domain_hash}/` | Path to persistent domain storage. |
|
||||||
|
| **Control Socket** | `/tmp/lazaret_coord_{domain_hash}.sock` | Unix socket for Coordinator control. |
|
||||||
|
| **Lock File** | `/tmp/lazaret_coord_{domain_hash}.lock` | Path used for advisory locking. |
|
||||||
|
| **Container Name** | `lazaret-coord-{domain_hash}` | Scoped Podman container instance name. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4.3.2 Host-Side Directory Layout
|
||||||
|
The State Anchor directory structure is established on the host under the **Domain Root** (`{data_root}/domains/{domain_hash}/`):
|
||||||
|
* `usr_changes/`: The persistent, writable layer (`upperdir`) storing user-installed packages and modifications. Initially empty.
|
||||||
* `usr_work/`: The private metadata workspace directory (`workdir`) required by overlayfs.
|
* `usr_work/`: The private metadata workspace directory (`workdir`) required by overlayfs.
|
||||||
* `usr_merged/`: The virtual, merged mount point directory (`mergeddir`) exposed to the sandbox.
|
* `usr_merged/`: The virtual, merged mount point directory (`mergeddir`) exposed to the sandbox.
|
||||||
|
|
||||||
#### 4.3.2 Host-Side Mounting Protocol
|
---
|
||||||
Because gVisor (`runsc`) restricts overlayfs mounts inside the sandbox virtual kernel, and standard host overlays require root privileges, Lazaret runs mounting operations on the host inside the rootless container namespace:
|
|
||||||
1. **Locate Base Image Mount:** The orchestrator creates a lightweight placeholder container and retrieves its base-image filesystem mount path on the host using `podman mount` (e.g., `/home/user/.local/share/containers/storage/overlay/.../merged/`).
|
|
||||||
2. **Mount User-Space Overlay (`fuse-overlayfs`):** The orchestrator executes `fuse-overlayfs` on the host, setting the base image's system directory as the read-only `lowerdir`, and the profile-specific `usr_changes` and `usr_work` as the writable layers. To support rootless execution and avoid permission errors, this command is executed within the rootless user namespace:
|
|
||||||
```bash
|
|
||||||
podman unshare fuse-overlayfs -o lowerdir=<base_image_mount>/usr,upperdir=usr_changes,workdir=usr_work usr_merged
|
|
||||||
```
|
|
||||||
3. **Sandbox Mount Injection:** The host orchestrator launches the gVisor sandbox, bind-mounting the virtual, merged directory (`usr_merged`) over the container's `/usr` directory:
|
|
||||||
```bash
|
|
||||||
podman run --runtime=runsc -v <data_root>/<profile_name>/usr_merged:/usr:z ...
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.3.3 Resilience to Image Updates, Drift & Podman Compatibility
|
#### 4.3.3 State Anchor Mounting Protocol
|
||||||
Unlike static copy-out methods which seal system libraries in time and bypass container patches, this overlay structure naturally inherits base image updates:
|
The host orchestrator establishes a user-space overlay filesystem (such as `fuse-overlayfs`) on the host within the rootless user namespace before launching the container:
|
||||||
* When the base OCI image is updated (e.g., pulling a new release containing critical CVE patches), the orchestrator mounts the new image's read-only `/usr` as the `lowerdir` on the next run.
|
1. **Locate Base Image Mount:** The orchestrator must query the OCI runtime to resolve the base-image read-only root mount path on the host.
|
||||||
* `fuse-overlayfs` merges this new, patched baseline with the existing `usr_changes` layer dynamically.
|
2. **Mount User-Space Overlay:** The orchestrator must mount a user-space overlay filesystem (e.g. `fuse-overlayfs`) within the rootless user namespace, using the base-image mount `/usr` directory as the read-only lower directory (`lowerdir`), and the domain-scoped directories `usr_changes/` and `usr_work/` as the `upperdir` and `workdir`, respectively.
|
||||||
* Because standard system libraries (such as glibc) maintain strict backwards-compatibility, user-installed packages survive base image updates and execute flawlessly on top of the updated, secure OS.
|
3. **Sandbox Mount Injection:** The orchestrator must launch the container with the merged user-space overlay directory (`usr_merged/`) bind-mounted read-write over the container's `/usr` path.
|
||||||
* **Podman Integration Compatibility Blacklist:** When applying user-space overlays over system directories (such as `/etc`), Lazaret dynamically blacklists and bypasses Podman-critical dynamic files (specifically `/etc/resolv.conf` and `/etc/hosts`) to allow Podman to inject standard network configurations seamlessly on startup, ensuring name resolution inside the sandbox. The detailed blacklist rules are managed in the development journal.
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4.3.4 Image Drift Mitigation & Podman Compatibility
|
||||||
|
The State Anchor overlay structure dynamically tracks updates to the underlying OCI container base image.
|
||||||
|
|
||||||
|
* **Base Image Update Inheritance:** Upon pulling an updated base image, the orchestrator uses the new image's `/usr` directory as the read-only `lowerdir` for subsequent user-space overlay mounts, preserving modifications in `usr_changes/`.
|
||||||
|
* **Podman Integration Compatibility Exclusion:** When applying State Anchor overlays over system directories (such as `/etc`), the orchestrator must exclude runtime-injected configuration files (specifically `/etc/resolv.conf`, `/etc/hosts`, and `/etc/hostname`) from the overlay merge to preserve container DNS and hostname resolution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4.3.5 Ephemeral Session Coordinator & Multi-Instance Multiplexing
|
||||||
|
|
||||||
|
Lazaret coordinates concurrent CLI processes interacting with the same **Domain Anchor** using a decentralized, background-detached **Session Coordinator** pattern.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
%% Nodes
|
||||||
|
subgraph Host OS Namespace
|
||||||
|
CLI_A["CLI Session A<br>(Leader)"]
|
||||||
|
CLI_B["CLI Session B<br>(Follower)"]
|
||||||
|
|
||||||
|
Lock["Advisory Lockfile<br>(LOCK_EX)"]
|
||||||
|
|
||||||
|
Coord["Session Coordinator<br>(Background Process)"]
|
||||||
|
Socket["Control Socket<br>(AF_UNIX)"]
|
||||||
|
|
||||||
|
Overlay["State Anchor<br>(fuse-overlayfs)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph gVisor Sandbox Namespace
|
||||||
|
Container["gVisor Container<br>(lazaret-coord-{hash})"]
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Interactions (Leader Sequence)
|
||||||
|
CLI_A -->|1. Acquires LOCK_EX| Lock
|
||||||
|
CLI_A -->|2. Spawns| Coord
|
||||||
|
Coord -->|3. Mounts usr_merged| Overlay
|
||||||
|
Coord -->|4. Boots| Container
|
||||||
|
Coord -->|5. Binds socket| Socket
|
||||||
|
Coord -.->|6. Releases LOCK_EX| Lock
|
||||||
|
|
||||||
|
%% Interactions (Follower Sequence)
|
||||||
|
CLI_B -->|1. Blocks on LOCK_EX| Lock
|
||||||
|
Lock -.->|2. Unblocks after socket bind| CLI_B
|
||||||
|
CLI_B -->|3. Connects| Socket
|
||||||
|
CLI_B -->|4. podman exec| Container
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 1: The "Hold-to-Bind" Advisory Locking Protocol
|
||||||
|
During CLI startup, the process must handle concurrency via Linux advisory file locking (`flock`):
|
||||||
|
1. **Acquire Lock:** The process attempts to acquire an exclusive, non-blocking lock (`LOCK_EX | LOCK_NB`) on `/tmp/lazaret_coord_{domain_hash}.lock`.
|
||||||
|
2. **If Successful (Leader):**
|
||||||
|
* The process must check if the `Control Socket` exists. If not, it must spawn the **Session Coordinator** in the background, fully detached from the parent's process group (e.g., creating a new session via standard POSIX `setsid` or equivalent OS APIs, detaching it from the controlling terminal).
|
||||||
|
* The leader process must **poll** the host filesystem for the `Control Socket` to bind (up to 10 seconds).
|
||||||
|
* The leader must not release its exclusive lock on the lockfile until the control socket has successfully bound and is ready to accept connections.
|
||||||
|
3. **If Unsuccessful (Follower):**
|
||||||
|
* The process blocks on the lock (`LOCK_EX`) until the leader completes coordinator setup and releases the lock.
|
||||||
|
* Once unblocked, the follower immediately releases the lock (`LOCK_UN`) and safely connects to the active `Control Socket`.
|
||||||
|
|
||||||
|
##### Step 2: Reference Counted Teardown
|
||||||
|
The Session Coordinator keeps an active map of connected CLI processes.
|
||||||
|
* Whenever a CLI process terminates, it disconnects from the control socket.
|
||||||
|
* When the active connection count on the Coordinator reaches **zero**, the Coordinator must initiate an automatic clean teardown: stopping the sandboxed container, unmounting the `fuse-overlayfs` State Anchor, and unlinking the control socket.
|
||||||
|
|
||||||
|
##### Step 3: Session Identity & Escalation Routing via Session Anchor
|
||||||
|
To maintain exact terminal isolation and prevent spoofing or PID-reuse hijacking during interactive prompts:
|
||||||
|
1. The host CLI process generates a **Session Anchor** (an ephemeral asymmetric keypair $(K_{pub\_proc}, K_{priv\_proc})$) and registers $K_{pub\_proc}$ alongside a unique `LAZARET_SESSION_ID` (represented by a cryptographically secure unique identifier) with the Session Coordinator.
|
||||||
|
2. When launching a shell or command session inside the container (via `podman exec`), the host CLI process injects a cryptographically signed capability token or delegates a short-lived keypair derived from $K_{priv\_proc}$ into that specific process's environment.
|
||||||
|
3. When the in-container **Escalation Client** (`lazaret-escalate`) triggers an escalation request, it must sign the JSON payload (including the request ID and `LAZARET_SESSION_ID`) with the injected session credential.
|
||||||
|
4. The Session Coordinator receives the request over mTLS, verifies the container certificate, retrieves the registered $K_{pub\_proc}$ matching the `LAZARET_SESSION_ID`, and validates the signature.
|
||||||
|
5. Once verified, the Session Coordinator routes the interactive terminal user interface (TUI) prompt exclusively to the corresponding host terminal socket matching that verified ID.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.4 Embedded State Store & Audit Trail Persistence
|
||||||
|
|
||||||
|
Lazaret implements a local persistence layer using an embedded transactional key-value store to manage session state transitions, lock statuses, and host escalation audit trails.
|
||||||
|
|
||||||
|
#### 4.4.1 Single-Process Exclusive Database Ownership
|
||||||
|
The state database file (stored inside the Domain Root) is strictly owned and opened exclusively by the background-detached Session Coordinator process.
|
||||||
|
* **Process-Level Exclusive Locking:** The embedded transactional key-value store enforces absolute process-level exclusive locking on the physical file. Any attempt by other processes (such as secondary CLI follower processes or standalone status query commands) to open the state file directly will fail immediately.
|
||||||
|
* **IPC-Facade Query Pattern:** Consequently, all secondary CLI follower sessions, dashboard widgets, or status utilities must never access the state store directly on the filesystem. Instead, they must communicate with the active Session Coordinator over the Control Socket using Unix Domain Sockets, and the Coordinator must perform all queries, registrations, and updates on their behalf.
|
||||||
|
|
||||||
|
#### 4.4.2 Asynchronous Event Loop Thread Integration
|
||||||
|
To prevent async event loop starvation from blocking database operations, all database transactions must be executed on a designated synchronous threadpool managed by the asynchronous runtime.
|
||||||
|
|
||||||
|
#### 4.4.3 Manual Schema Migration Protocol
|
||||||
|
To maintain zero external C-dependencies for frictionless static compilation, the persistence layer utilizes a lightweight, typed key-value table layout.
|
||||||
|
* **Manual Evolution:** The store does not support automated relational schema migration engines.
|
||||||
|
* **Table-to-Table Copying:** Any schema evolution or configuration structural changes across feature updates must be coordinated by manual, programmatically written migration routines. These routines must query old tables, convert the underlying serializations, and write them to newer table versions inside a single atomic transaction.
|
||||||
|
|
||||||
|
#### 4.4.4 Division of Labor between State Store, Session Coordinator, and OS
|
||||||
|
To prevent race conditions, memory leaks, and stale process tracking, the active runtime state is decoupled from passive storage:
|
||||||
|
|
||||||
|
1. **Passive ACID State Store:** Responsible solely for persisting completed session metadata, verified escalation approvals, and tamper-safe host command audit logs. It does not track real-time process heartbeats or actively coordinate system actions.
|
||||||
|
2. **Active Session Coordinator:** Acts as the exclusive in-memory broker, managing async UNIX sockets, mTLS handshake parsing, active client connection reference counting, and executing actual container launches or workspace mounts.
|
||||||
|
3. **OS Kernel Primitives:** Responsible for process death detection. The Session Coordinator and CLI processes utilize advisory file locking (`flock`) on the Lock File to manage active-leader lifecycles and clean up sockets upon process termination (including hard crashes such as `SIGKILL`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Agent-to-Host Escalation Protocol
|
## 5. Agent-to-Host Escalation Protocol
|
||||||
|
|
||||||
### 5.1 Defining Escalation Boundaries
|
### 5.1 Cryptographic Identity Verification & Message Transport
|
||||||
AI agents inside the container run as `root` in their user namespace. They can execute standard operations (such as installing packages with `apt-get install` or compiling binaries) locally within their sandbox.
|
The **Escalation Client** is placed inside the Agent Sandbox. It communicates with the host orchestrator over the **Escalation Socket** (UDS) utilizing a secure Mutual TLS (mTLS) handshake verified by the **Trust Anchor** to cryptographically prove sandbox identity.
|
||||||
|
|
||||||
**Escalation is strictly required when the agent needs access to resources outside the gVisor quarantine:**
|
The host orchestrator creates the **Escalation Socket** file inside a secure, host-private directory owned by the host developer (defined by `socket_dir` in the configuration) and bind-mounts only that socket into the Agent Sandbox, keeping it decoupled from the writable **Agent Workspace**.
|
||||||
* **Credential Delegation:** Querying host-level credential helpers (e.g. AWS token fetchers) or host keys.
|
|
||||||
* **Host Resource Modification:** Accessing host-level files outside the Agent Workspace or requesting configuration changes to host services.
|
|
||||||
|
|
||||||
### 5.2 JSON Message Schema over UDS with Ephemeral mTLS
|
#### 5.1.1 Cryptographic Identity Verification via Trust Anchor and Session Anchor
|
||||||
The **Escalation Client** is placed inside the Agent Sandbox. It communicates with the host orchestrator over the **Escalation Socket** (UDS) utilizing a secure Mutual TLS (mTLS) handshake to cryptographically prove sandbox identity.
|
Lazaret establishes process-level and container-level identities using the **Trust Anchor** and **Session Anchor** protocols:
|
||||||
|
1. **Host CA Generation:** On startup, the host orchestrator instantiates the **Trust Anchor** as an in-memory, local ephemeral Certificate Authority (CA), generating a self-signed root CA certificate and private key in-memory.
|
||||||
|
2. **Sandbox Client Certificate:** For each active sandbox container, the **Trust Anchor** generates a unique, short-lived client certificate and private key. This certificate encodes the active container's profile name and unique sandbox ID inside its Subject Alternative Name (SAN).
|
||||||
|
3. **Mount Injection:** The host orchestrator mounts these Trust Anchor certificates into the container namespace using a memory-backed, read-only `tmpfs` volume (see Fixed In-Container Paths).
|
||||||
|
4. **mTLS Handshake Check:** When the **Escalation Client** initiates a request, it performs a complete mutual TLS handshake over the **Escalation Socket**. The host verifies that the client certificate presented by the connection is signed by the active **Trust Anchor** root CA, and checks the profile SAN to confirm exact container identity before any payloads are parsed.
|
||||||
|
5. **Session Signature Verification:** Once the mTLS connection is verified, the Session Coordinator reads the payload's `session_id`, retrieves the public key of the corresponding **Session Anchor** registered on startup, and verifies the payload's cryptographic `signature`. Only requests carrying a valid signature produced by that session's injected private key are approved for TUI routing or auto-approval.
|
||||||
|
|
||||||
To prevent container-based file manipulation or hijacking, the host orchestrator creates the **Escalation Socket** file inside a secure, host-private directory owned by the host developer (defined by `socket_dir` in the configuration) and bind-mounts only that socket into the Agent Sandbox, leaving it completely decoupled from the writable **Agent Workspace**.
|
#### 5.1.2 Command Request (Sandbox -> Host JSON payload over mTLS)
|
||||||
|
* `request_id`: A cryptographically secure unique identifier.
|
||||||
#### 5.2.1 Cryptographic Identity Verification (Local Ephemeral CA)
|
* `session_id`: The `LAZARET_SESSION_ID` corresponding to the requesting **Client Session**.
|
||||||
To prevent PID-reuse spoofing vulnerabilities (CWE-377 / CWE-362) and host socket hijacking risks, Lazaret establishes container identity cryptographically rather than relying solely on kernel-level or process-level credentials:
|
|
||||||
1. **Host CA Generation:** On startup, the host orchestrator acts as a private, ephemeral Certificate Authority (CA), generating a self-signed root CA certificate and private key in-memory.
|
|
||||||
2. **Sandbox Client Certificate:** For each active sandbox container, the orchestrator generates a unique, short-lived client certificate and private key. This certificate encodes the active container's profile name and unique sandbox ID inside its Subject Alternative Name (SAN).
|
|
||||||
3. **Mount Injection:** The host orchestrator mounts these certificates into the container namespace using a memory-backed, read-only `tmpfs` volume (see Fixed In-Container Paths).
|
|
||||||
4. **mTLS Handshake & Host Credentials Pre-flight Check:** When the **Escalation Client** initiates a request, it performs a complete mutual TLS handshake over the **Escalation Socket**. The host verifies that the client certificate presented by the connection is signed by the host's active in-memory root CA, and checks the profile SAN to confirm exact container identity before any payloads are parsed. To provide defense-in-depth, the host also runs a lightweight pre-flight credentials check (`SO_PEERCRED`) on the socket to ensure the socket connection is initiated exclusively by the local developer's rootless user space.
|
|
||||||
|
|
||||||
#### Command Request (Sandbox -> Host JSON payload over mTLS)
|
|
||||||
* `request_id`: Unique identifier (e.g., UUID or hashed string).
|
|
||||||
* `profile`: The current agent profile requesting escalation.
|
* `profile`: The current agent profile requesting escalation.
|
||||||
* `command`: Raw CLI command to run on the host.
|
* `command`: Raw CLI command to run on the host.
|
||||||
* `justification`: Detailed justification explaining why host resources are needed.
|
* `justification`: Detailed justification explaining why host resources are needed.
|
||||||
|
* `signature`: Cryptographic signature of the request payload produced by the process's injected private credential, verifiable using the Session Anchor's registered public key.
|
||||||
|
|
||||||
#### Command Response (Host -> Sandbox JSON payload over mTLS)
|
#### 5.1.3 Command Response (Host -> Sandbox JSON payload over mTLS)
|
||||||
* `request_id`: Matches the original command request ID.
|
* `request_id`: Matches the original command request ID.
|
||||||
* `approved`: Boolean indicating if the user permitted execution.
|
* `approved`: Boolean indicating if the user permitted execution.
|
||||||
* `exit_code`: Process exit code (only set if approved is true).
|
* `exit_code`: Process exit code (only set if approved is true).
|
||||||
* `stdout`: Combined standard output text.
|
* `stdout`: Combined standard output text.
|
||||||
* `stderr`: Combined standard error text.
|
* `stderr`: Combined standard error text.
|
||||||
|
|
||||||
### 5.3 HITL Interactive Console Interface & Whitelisting
|
### 5.2 HITL Interactive Console Interface & Whitelisting
|
||||||
When a request is received on the host, the `lazaret` orchestrator temporarily halts standard TTY forwarding and renders a dedicated interactive console prompt using `ratatui`. The prompt clearly isolates the request ID, profile, justification, and the requested command, offering the user four distinct actions:
|
When a request is received on the host, the `lazaret` orchestrator must temporarily suspend standard TTY forwarding and render a dedicated interactive terminal user interface prompt. The interface must isolate the request ID, profile, justification, and the requested command, offering the user four distinct actions:
|
||||||
* **[A] Approve & Run:** Immediately runs the command on the host.
|
* **[A] Approve & Run:** Executes the command on the host.
|
||||||
* **[D] Deny Request:** Rejects the escalation, returning an access denied response to the sandbox.
|
* **[D] Deny Request:** Rejects the escalation, returning an access-denied response to the sandbox.
|
||||||
* **[E] Edit Command:** Prompts the developer to edit the command in-place before executing it.
|
* **[E] Edit Command:** Prompts the developer to edit the command in-place before execution.
|
||||||
* **[Q] Abort:** Stops the daemon and suspends container operation.
|
* **[Q] Abort:** Terminates the CLI orchestrator process, initiating a graceful shutdown of the container and **Network Sidecar** operations.
|
||||||
|
|
||||||
#### 5.3.1 Whitelisting & Injection Prevention (Argument Vector Prefix Matching)
|
```mermaid
|
||||||
To provide high developer experience (DX) without introducing command injection vulnerabilities, the orchestrator implements a secure command validation pipeline:
|
graph TD
|
||||||
|
Request[Escalation Request from Sandbox] --> Metachar{Contains Shell<br>Metacharacters?}
|
||||||
|
|
||||||
|
%% Non-Shell Path
|
||||||
|
Metachar -->|No| Whitelist{Matches Auto-Approve<br>Prefix Vector?}
|
||||||
|
Whitelist -->|Yes| Auto[Auto-Approve Path]
|
||||||
|
Whitelist -->|No| Prompt[Force Human HITL Prompt]
|
||||||
|
|
||||||
|
%% Shell Path
|
||||||
|
Metachar -->|Yes| Prompt
|
||||||
|
|
||||||
|
%% Execution Paths
|
||||||
|
Auto -->|Executes Directly| Execve[Direct execve Execution<br>No shell spawned]
|
||||||
|
|
||||||
|
Prompt -->|User Approves| ExecType{Has Shell<br>Metacharacters?}
|
||||||
|
Prompt -->|User Denies| Deny[Return Access Denied]
|
||||||
|
|
||||||
|
ExecType -->|No| Execve
|
||||||
|
ExecType -->|Yes| ShellExec[Execute in Host Shell]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2.1 Whitelisting & Injection Prevention (Argument Vector Prefix Matching)
|
||||||
|
The orchestrator implements a command validation pipeline:
|
||||||
|
|
||||||
1. **Shell Metacharacter Auto-Approve Bypass:**
|
1. **Shell Metacharacter Auto-Approve Bypass:**
|
||||||
Any requested command containing shell-interpreted metacharacters, control characters, or redirections (such as `;`, `|`, `&`, `$()`, `` ` ` ``, `>`, `<`, `\n`, `\r`, or backslashes) is strictly ineligible for auto-approval. This bypasses the automatic whitelisting engine and demotes the command to require interactive human-in-the-loop (HITL) approval, ensuring that no command chaining, backgrounding, or output redirection can be executed on the host without explicit developer inspection.
|
Any requested command containing shell-interpreted metacharacters, control characters, or redirections (including `;`, `|`, `&`, `$()`, `` ` ` ``, `>`, `<`, `\n`, `\r`, and backslashes) must bypass the automatic whitelisting engine. Such commands must require interactive human-in-the-loop (HITL) approval to prevent arbitrary command chaining, background execution, or output redirection on the host.
|
||||||
|
|
||||||
2. **Argument Vector Prefix Whitelisting:**
|
2. **Argument Vector Prefix Whitelisting:**
|
||||||
A requested command string is parsed into an argument vector utilizing a POSIX-compliant shell words parser (e.g., the `shell-words` crate in Rust) to resolve quotes and escape spaces correctly. The requested vector is compared against the parsed vectors of the configured `auto_approve` list. To prevent **argument injection vulnerabilities** (where an agent appends malicious flags like `--exec-path` or `--pager` to hijack general binaries), the auto-approve check verifies that the whitelisted vector is a strict prefix vector of the requested vector.
|
The requested command string must be parsed into an argument vector using a POSIX-compliant shell words parser to resolve quotes and escape spaces. This requested vector must be validated against the parsed vectors defined in the `auto_approve` configuration.
|
||||||
* **Unsafe / Banned Commands:** Broad binaries or commands that support execution redirection (such as raw `"git"` or any general `"git "` subcommands) are strictly banned from the auto-approve whitelist.
|
To prevent argument injection vulnerabilities (where arbitrary parameters are appended to authorized binaries), the auto-approve verification must assert that the whitelisted vector is a strict prefix of the requested vector.
|
||||||
* **Safe Whitelist Example:** Whitelist prefix vector `["aws", "sts", "get-caller-identity"]` safely matches requested command vector `["aws", "sts", "get-caller-identity", "--query", "Account"]` but rejects any other executable.
|
* **Banned Commands:** Executables that permit arbitrary command redirection (including raw `git` or any `git` subcommands) must not be permitted on the auto-approve whitelist.
|
||||||
|
|
||||||
3. **Direct Execve Execution:**
|
3. **Direct Execution:**
|
||||||
Once verified, the orchestrator passes the literal argument vector directly to the host's execution engine (`execve` or Rust `std::process::Command`), launching the binary directly without invoking any intermediate host shell, rendering any argument-based shell escape vectors completely inert.
|
Following verification, the orchestrator must pass the literal argument vector directly to the host's native process execution engine (such as the standard `execve` system call or equivalent system process APIs), launching the binary directly without invoking an intermediate host shell.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -309,13 +420,27 @@ To provide high developer experience (DX) without introducing command injection
|
||||||
| **FR-1** | TOML Profile Loading | Dynamically parses and loads config from `~/.config/lazaret/config.toml` or override paths. |
|
| **FR-1** | TOML Profile Loading | Dynamically parses and loads config from `~/.config/lazaret/config.toml` or override paths. |
|
||||||
| **FR-2** | Podman & gVisor Integration | Configures container executions using `--runtime=runsc` for sandboxes and `--runtime=runc` for sidecars. |
|
| **FR-2** | Podman & gVisor Integration | Configures container executions using `--runtime=runsc` for sandboxes and `--runtime=runc` for sidecars. |
|
||||||
| **FR-3** | Network Sidecar Lifecycle | Ensures the shell-less **Network Sidecar** running in `runc` is active before spawning any Agent Sandbox. |
|
| **FR-3** | Network Sidecar Lifecycle | Ensures the shell-less **Network Sidecar** running in `runc` is active before spawning any Agent Sandbox. |
|
||||||
| **FR-4** | Git Hooks Isolation | Automatically isolates the container git environment using the Git Shadow. |
|
| **FR-4** | Git Hooks Isolation | Automatically isolates the container git environment using the Git Sentry. |
|
||||||
| **FR-5** | Host Escalation | Establishes private host **Escalation Socket** listener with ephemeral mTLS certificate verification and SO_PEERCRED pre-flight checks, parses JSON requests, and displays interactive HITL terminal prompt. |
|
| **FR-5** | Host Escalation | Establishes private host **Escalation Socket** listener with ephemeral mTLS certificate verification, parses JSON requests, and displays interactive HITL terminal prompt. |
|
||||||
|
| **FR-6** | Domain Scoped Multiplexing | Orchestrates concurrent multi-session execution within a **Domain Anchor** utilizing a decentralized **Session Coordinator**, hold-to-bind locking, and identity-aware **Host Escalation** prompt routing. |
|
||||||
|
|
||||||
### 6.2 Non-Functional Requirements
|
### 6.2 Non-Functional Requirements
|
||||||
| ID | Requirement | Description |
|
| ID | Requirement | Description |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
| **NFR-1** | Minimal Sidecar Footprint | The **Network Sidecar** binary must compile statically (via `musl`) with minimal external dependencies to maintain a small footprint suitable for `scratch`. |
|
| **NFR-1** | Minimal Sidecar Footprint | The **Network Sidecar** binary must compile statically (via `musl`) with minimal external dependencies to maintain a small footprint suitable for `scratch`. |
|
||||||
| **NFR-2** | Robust Async Cancellation | Utilizes Tokio's task cancellation to cleanly close containers, clean up **Network Sidecar** instances, and close sockets on SIGINT. |
|
| **NFR-2** | Robust Async Cancellation | Utilizes native asynchronous task cancellation and signal-handling mechanisms to cleanly close containers, clean up **Network Sidecar** instances, and close sockets on SIGINT or other termination events. |
|
||||||
| **NFR-3** | Dynamic Terminfo Mapping | Locates host terminal terminfo configurations (Ghostty, Alacritty, Kitty) and bind-mounts them read-only into the Agent Sandbox. |
|
| **NFR-3** | Dynamic Terminfo Mapping | Locates host terminal terminfo configurations (Ghostty, Alacritty, Kitty) and bind-mounts them read-only into the Agent Sandbox. |
|
||||||
| **NFR-4** | Zero Shell Footprint | The **Network Sidecar** container has no shell (`sh`, `bash`), preventing any remote execution/hijacking. |
|
| **NFR-4** | Zero Shell Footprint | The **Network Sidecar** container has no shell (`sh`, `bash`), preventing any remote execution/hijacking. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Library Roles Map
|
||||||
|
|
||||||
|
The following table maps the abstract architectural roles used in this specification to their concrete library dependencies.
|
||||||
|
|
||||||
|
| Architectural Role | Abstract Prose Reference | Concrete Library | Justification |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **Embedded Transactional Key-Value Store** | "embedded transactional key-value store" | `redb` (v4.1.0) | Pure-Rust, transactional store enabling static cross-compilation with robust ACID/MVCC features. |
|
||||||
|
| **Terminal User Interface Library** | "interactive terminal user interface library" or "terminal user interface" | `ratatui` | Terminal rendering and layout primitives for the interactive prompt. |
|
||||||
|
| **Asynchronous Runtime Event Loop** | "asynchronous runtime event loop" | `tokio` (v1.52.3) | Asynchronous execution runtime for socket and multiplexer coordination. |
|
||||||
|
| **Cryptographically Secure Unique Identifiers** | "cryptographically secure unique identifier" or "unique session identifier" | `uuid` (v1.23.4) | Generation and validation of unique identifiers. |
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue