Repository navigation
docs: document spawned-process containment and egress contract decision #104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| //! Every process spawn in this workspace is listed in | ||
| //! `docs/technical/specs/process-containment.md`, so a new one cannot slip in | ||
| //! unsandboxable without a documented decision. | ||
|
|
||
| #![allow(clippy::unwrap_used, clippy::expect_used)] | ||
|
|
||
| use std::fs; | ||
| use std::path::{Path, PathBuf}; | ||
|
|
||
| fn rust_files(dir: &Path, out: &mut Vec<PathBuf>) { | ||
| let Ok(entries) = fs::read_dir(dir) else { | ||
| return; | ||
| }; | ||
| for entry in entries.flatten() { | ||
| let path = entry.path(); | ||
| if path.is_dir() { | ||
| rust_files(&path, out); | ||
| } else if path.extension().is_some_and(|e| e == "rs") { | ||
| out.push(path); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn every_spawning_file_is_documented() { | ||
| let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); | ||
| let doc = fs::read_to_string(root.join("docs/technical/specs/process-containment.md")) | ||
| .expect("process-containment.md exists"); | ||
| let mut files = Vec::new(); | ||
| rust_files(&root.join("crates"), &mut files); | ||
| let mut undocumented = Vec::new(); | ||
| for file in files { | ||
| let rel = file | ||
| .strip_prefix(&root) | ||
| .unwrap() | ||
| .to_string_lossy() | ||
| .replace('\\', "/"); | ||
| if rel.contains("/tests/") | ||
| || rel.contains("_tests.rs") | ||
| || rel.contains("tinycomputer-examples") | ||
| { | ||
| continue; | ||
| } | ||
| let Ok(text) = fs::read_to_string(&file) else { | ||
| continue; | ||
| }; | ||
| let spawns = text.contains("Command::new("); | ||
| if !spawns { | ||
| continue; | ||
| } | ||
| // The doc names files as `crates/<crate>/src/<path>`, sometimes with | ||
| // `{a,b}` grouping, so match on the file name within its crate. | ||
| let name = file.file_stem().unwrap().to_string_lossy().to_string(); | ||
| let krate = rel.split('/').nth(1).unwrap(); | ||
| let listed = doc.lines().any(|l| l.contains(krate) && l.contains(&name)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win The documentation match is loose and can hide an undocumented spawn. The check passes if any single line contains the crate name and the file stem. Common stems such as 🤖 Prompt for AI AgentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
A new spawn in a reused filename can bypass this guard: for example, Useful? React with 👍 / 👎. |
||
| if !listed { | ||
| undocumented.push(rel); | ||
| } | ||
| } | ||
| assert!( | ||
| undocumented.is_empty(), | ||
| "spawn sites missing from process-containment.md: {undocumented:?}" | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,8 @@ After the specification is accepted, create a linked implementation plan in | |
| [`../plans/`](../plans/README.md). Keep code snippets small enough to clarify | ||
| the contract; production code still belongs under `src/`. | ||
|
|
||
| See [`process-containment.md`](process-containment.md) for which spawned processes the host cannot contain. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Adding the link only to the specifications index leaves the new document absent from both AGENTS.md reference: AGENTS.md:L419-L420 Useful? React with 👍 / 👎. |
||
|
|
||
| See [`desktop-module-contract.md`](desktop-module-contract.md) for a complete | ||
| example, [`jev-intent-flows.md`](jev-intent-flows.md) for the flow runtime, and | ||
| [`unified-agent.md`](unified-agent.md) for browser and desktop behind one task API, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # Process containment and egress | ||
|
|
||
| Status: Accepted. Owner: tinycomputer maintainers. | ||
| Part of tinyhumansai/openhuman#7328 (phase 5, sandbox coverage). | ||
|
Comment on lines
+3
to
+4
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The document is marked Accepted while omitting the specification template's goals/non-goals, proposed behavior, invariants, acceptance criteria, and open questions, and this commit implements the inventory test without creating the required linked implementation plan. Complete those sections and add the plan so the decision and its verification remain auditable. AGENTS.md reference: AGENTS.md:L416-L418 Useful? React with 👍 / 👎. |
||
|
|
||
| ## Problem | ||
|
|
||
| A host that sandboxes spawned processes (OpenHuman's tinysecurity and tinybox | ||
| work) needs to know which processes this module starts, what network it can | ||
| reach, and which of those the host can contain. A process that is silently | ||
| skipped looks contained when it is not. | ||
|
|
||
| ## Decision | ||
|
|
||
| tinycomputer is a native module loaded into the host process. It drives the | ||
| user's real desktop and a real Chrome, so it cannot run inside a jail that | ||
| hides those. This module therefore **does not expose a sandbox, proxy or egress | ||
| contract**: none of it is enforceable from inside the module, and a contract | ||
| that is not enforced is worse than none. The host treats the whole module as | ||
| unsandboxed-by-design and gates it at the member level (approval, payment stop, | ||
| `POLICY_DENIED`). This document is the explicit list of what is not contained. | ||
|
|
||
| ## Process inventory | ||
|
|
||
| `crates/tinycomputer-core/tests/process_inventory.rs` fails if a crate under | ||
| `crates/` spawns a process from a file that is not named here. | ||
|
|
||
| | Process | Spawned from | Contract | | ||
| | --- | --- | --- | | ||
| | Chrome (browser sessions) | agent-browser (`vendor/agent-browser`, `native/cdp/chrome.rs`), started by an explicit `launch` | Unsupported. Runs as the user with their network. agent-browser can take a `proxy` launch option, but this module does not set or forward one, so egress is not enforceable here. Xvfb and certutil helpers are agent-browser's. | | ||
| | Cursor overlay helper (`tinycomputer-cursor-overlay`) | `crates/tinycomputer-cursor/src/screen/process.rs` | Unsupported. Draws on screen, reads commands on stdin, opens no network. Optional; absent means no drawn cursor. | | ||
| | Accessibility helper (Swift, macOS) | `crates/tinycomputer-accessibility/src/helper/process.rs`, `globe.rs`, `build.rs` | Unsupported. Needs the macOS accessibility grant of the host app, so it cannot be jailed. Opens no network. | | ||
| | `osascript`, `open`, `cmd`, `xcrun`, `swiftc` | `crates/tinycomputer-accessibility/src/{focus,paste,permissions}.rs`, `helper/process.rs` | Unsupported. Fixed argv, no model-supplied command line. Local desktop actions only. | | ||
| | agent-browser plugins, Lightpanda, Safari/Appium drivers, upgrade | vendor/agent-browser | Not used by this module's launch path; owned by agent-browser. | | ||
|
|
||
| ## Egress | ||
|
|
||
| The module's own outbound traffic is the decision model, planner, rescue and | ||
| output model calls (the `jev`, `planner` config routes), made from the host | ||
| process, plus whatever sites the browser is told to open. Host-side egress | ||
|
Comment on lines
+38
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| policy applies to the first. The second is user-directed browsing and is not | ||
| restricted by the module (`origins` checks classify pages, they do not block | ||
| network). | ||
|
|
||
| ## Revisit when | ||
|
|
||
| agent-browser or the module can pass a launch-time proxy or network namespace | ||
| and the module can verify it took effect. Only then add a contract member, in | ||
| `tinycomputer-bus`, with a test that the browser's traffic goes through it. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file is placed under
tinycomputer-core/tests, but it neither exercises that crate's public API nor follows the required*_tests.rsintegration-test naming; instead it crawls unrelated workspace source and documentation. Move this repository-wide policy check into CI/tooling, rather than couplingcargo test -p tinycomputer-coreto the checkout layout.AGENTS.md reference: AGENTS.md:L365-L368
Useful? React with 👍 / 👎.