From 21dce2b0abd6f741091aa64b63ad88e6c3326967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Schemaitat?= Date: Sat, 10 Oct 2026 08:16:35 +0000 Subject: [PATCH] docs: add static architecture diagrams to the site --- .github/mkdocs/hooks.py | 4 +++ README.md | 2 ++ docs/design.md | 12 +++++++ docs/images/launch.svg | 59 +++++++++++++++++++++++++++++++ docs/images/security-boundary.svg | 44 +++++++++++++++++++++++ docs/security.md | 14 +------- 6 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 docs/images/launch.svg create mode 100644 docs/images/security-boundary.svg diff --git a/.github/mkdocs/hooks.py b/.github/mkdocs/hooks.py index 9a31e3c..eef7574 100644 --- a/.github/mkdocs/hooks.py +++ b/.github/mkdocs/hooks.py @@ -65,6 +65,10 @@ def _rewrite(target, page, config): site = _site_path(resolved) if site is not None: return posixpath.relpath(site, posixpath.dirname(page.file.src_uri) or ".") + fragment + # Files under docs/ are published with the site, not served from GitHub. + if resolved.startswith("docs/") and (ROOT / resolved).is_file(): + asset = resolved[len("docs/"):] + return posixpath.relpath(asset, posixpath.dirname(page.file.src_uri) or ".") + fragment branch = "blob" if (ROOT / resolved).is_file() else "tree" return f"{config['repo_url'].rstrip('/')}/{branch}/main/{resolved}{fragment}" diff --git a/README.md b/README.md index faffc06..b678c75 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ OpenCode runs every tool call in its server, not in the TUI, so each agent gets two sandboxes: a **server sandbox** (OpenCode's private server and every tool it runs) and a **client sandbox** (the TUI). +![Launch flow: Herdr starts an action that opens an agent pane; its bridge launches separate server and client sandboxes, verifies both from the host, and records the result.](docs/images/launch.svg) + | | Server sandbox: server and tools | Client sandbox: TUI | | --- | --- | --- | | Project files | Read-write: the worktree or git repository of the focused pane | Same | diff --git a/docs/design.md b/docs/design.md index 8061b58..77f5232 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,5 +1,17 @@ # Design +## Launch and verification + +The action runs briefly under Herdr; the bridge stays in the agent pane for +the lifetime of the session. The server and client run in separate nono +sandboxes, while verification and the pane mapping stay outside both. + +![Launch flow: Herdr starts the action, which opens a pane and saves a mapping; the bridge starts a client and a private server in separate sandboxes, then verifies them from the host and records the outcome.](images/launch.svg) + +The client can reach only its private server port; the server runs tools and +uses nono's proxy for allowed provider hosts. See [Security](security.md) for +the network and trust boundaries. + ## Two plugin processes Herdr runs plugin actions as short-lived processes without a terminal, so diff --git a/docs/images/launch.svg b/docs/images/launch.svg new file mode 100644 index 0000000..15f086d --- /dev/null +++ b/docs/images/launch.svg @@ -0,0 +1,59 @@ + + Agent launch and verification + Herdr starts the action, which saves a pane mapping and starts the bridge. The bridge starts a private server and client in separate nono sandboxes, verifies both from the host, and saves the result. + + + + + One agent, two sandboxes + Host processes and state remain outside the sandboxes. + + + + + + + + + + + + + + Herdr action + Split pane; start bridge + Short-lived host process + Pane mapping + Provisional → running + → exited / failed + Bridge + Runs in the agent pane + Owns both lifecycles + Server sandbox + opencode serve on private port P + Model loop and every tool call + Client sandbox + opencode --server 127.0.0.1:P + Interactive TUI in the pane + Host verification + Check both process trees via /proc + + + + + + + + + + + + + + save + read + start server, then client + port P + password + record result + + diff --git a/docs/images/security-boundary.svg b/docs/images/security-boundary.svg new file mode 100644 index 0000000..a3a8471 --- /dev/null +++ b/docs/images/security-boundary.svg @@ -0,0 +1,44 @@ + + OpenCode sandbox network boundary + The network-blocked client connects only to its sandboxed server using a private port and password. The server runs tools and reaches allowed provider hosts through the nono proxy. The unsandboxed host service is denied. + + + + + OpenCode network boundary + + + + + + + + + Client sandbox + OpenCode TUI + Network blocked + Only private port P open + Server sandbox + opencode serve on port P + Tools: bash, edits, MCP + Provider hosts via nono proxy + nono proxy + Allowed hosts only + GitHub Copilot + and model catalog + Unsandboxed host service + 127.0.0.1:4096 — unreachable + + + + + + + + + + P + password + HTTPS + + Denied (including via proxy) + diff --git a/docs/security.md b/docs/security.md index ff9d29a..b19e0ec 100644 --- a/docs/security.md +++ b/docs/security.md @@ -19,19 +19,7 @@ The plugin picks the port `P` and a fresh password (`OPENCODE_PASSWORD`) on every launch; the server rejects requests without it. `--server` is a required argument that configuration cannot replace. -```mermaid -flowchart LR - subgraph client["client sandbox: network blocked"] - CLIENT["opencode --server http://127.0.0.1:P
(TUI)"] - end - subgraph server["server sandbox: provider hosts via nono proxy"] - SERVER["opencode serve --port P"] --> TOOLS["tools: bash, edits, ..."] - end - SVC["OpenCode host service
127.0.0.1:4096 (unsandboxed)"] - CLIENT -->|"port P + password"| SERVER - SERVER --> PROXY["nono proxy"] --> NET(("GitHub Copilot")) - TOOLS -. "denied, directly and via the proxy" .-> SVC -``` +![Security boundary: the network-blocked client reaches only its private server port; the sandboxed server runs tools and reaches allowed provider hosts through nono's proxy. Neither sandbox can reach the unsandboxed OpenCode host service.](images/security-boundary.svg) ## What each sandbox can reach