From 35a9b14ed0fad935e754962fa154ec9f6f241dee Mon Sep 17 00:00:00 2001 From: David McKay Date: Fri, 21 Aug 2026 17:12:01 -0700 Subject: [PATCH 1/2] Catch the changelog and docs up to what shipped The Unreleased notes already tracked most of the recent work, since each change carried its own line in. This fills the gaps and fixes what went stale. Two merged changes had no line. The address guard's alternate-encoding refusal: it turned away the metadata and private addresses as usually written but not the same ones spelled as an IPv6-mapped or NAT64 form, an integer, or with a trailing dot, and it now canonicalises before it checks and refuses the container credential endpoints even with the private-host opt-in on. And the supervisor refusing to adopt a container it did not create, so a shared Docker host cannot hand it a stranger's container with the computer token. Docs that drifted: the README and the Cloud Run note still said one replica, which the deployment doc's own Replicas section now contradicts, so both point at the real remaining constraint instead, which is the shared browser. And AUDIT_RETENTION_DAYS and COMPUTER_SANDBOX were configurable and documented in the changelog and the README but missing from the configuration table. The knowledge back-out left one more orphan the removal missed: agents/invocation.ts routed a built-in agent to the knowledge agent that is gone, and nothing live constructs it. Deleted with its test. The changelog line that said the local index's connector "is going away" is now "has been removed", because it has been. README stays a build doc; none of this adds history to it. --- CHANGELOG.md | 15 +++++++++++++- README.md | 3 +-- docs/configuration.md | 2 ++ docs/deployment.md | 4 +++- server/src/agents/invocation.ts | 20 ------------------ server/tests/agent-invocation.test.ts | 30 --------------------------- 6 files changed, 20 insertions(+), 54 deletions(-) delete mode 100644 server/src/agents/invocation.ts delete mode 100644 server/tests/agent-invocation.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 387f75a9..90f70474 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,7 +69,7 @@ Sessions survive and nobody signs in again. that system's own search as the person asking, so the vendor decides what they may see and there is no second copy of anybody's documents here to keep in step, to secure, or to leave behind when somebody is removed. The local index that was being filled — `documents`, `chunks` and - `document_acls` — is read by nothing, and the connector that filled it is going away. Retrieval over + `document_acls` — is read by nothing, and the connector that filled it has been removed. Retrieval over a copy of a customer's corpus is not a thing OpenBot does. ### Added @@ -184,6 +184,19 @@ Sessions survive and nobody signs in again. is unavailable never blocks a sign-in. ### Fixed +- **A Bot could reach the deployment's own network by writing the address a different way.** The + guard refused `169.254.169.254` and the private ranges as usually written, but not the same + addresses spelled as an IPv6-mapped or NAT64 form, an integer, or with a trailing dot, so a Bot + talked into fetching one still reached cloud metadata or an internal host. The address is + canonicalised before it is checked now, the mapped form of `0.0.0.0` (which reaches every local + port) is refused, and the container credential endpoints a hosted deployment must never expose — + ECS and Fargate's `169.254.170.2`, Alibaba's `100.100.100.200` — are refused even when the + private-host opt-in is on. The same guard backs agent registration, so it is closed there too. +- **The supervisor could adopt a container it did not create.** When starting a Bot's computer hit a + name already taken, it started whatever held the name and handed it the deployment's computer + token, so on a Docker host shared with anything else it could drive a stranger's container as a + Bot's. It now refuses a container that does not carry its own namespace label, read from the + container rather than inferred, so a second deployment on the same host is never adopted. - **Removing somebody left the credentials they had granted this deployment sitting in the vault.** Removing them from the People screen ended their sessions and stopped the next sign-in, and left the refresh token behind, unrevoked. They could not use it — the account comes from a session they no diff --git a/README.md b/README.md index 26de4ccc..e3c8632c 100644 --- a/README.md +++ b/README.md @@ -108,8 +108,7 @@ docker run -p 3001:3001 --env-file .env \ ``` Leave `EMBEDDED_POSTGRES` off and set `DATABASE_URL` to point at a database you already run. -[docs/deployment.md](docs/deployment.md) has the minimum sizes, the platform notes, and why this runs -as one replica for now. +[docs/deployment.md](docs/deployment.md) has the minimum sizes, the platform notes, and how it behaves behind more than one replica. ## Try it diff --git a/docs/configuration.md b/docs/configuration.md index 163661d8..37265f88 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -54,6 +54,7 @@ at `agent-langgraph` on a laptop. | `AGENT_STALL_TIMEOUT_MS` | unset (off) | How long a Bot's stream may produce nothing before the turn is ended for it. | | `AGENT_TOOL_TOKEN` | unset | The secret a framework Bot presents when it calls a granted tool back through this server. | | `APP_DIST_DIR` | unset | Where the built app is, when this process serves it. Set inside the container image; unset in development, where Vite serves the app. | +| `AUDIT_RETENTION_DAYS` | unset | Whole number of days to keep audit rows; older ones are removed. Unset keeps the trail forever. | **`AGENT_STALL_TIMEOUT_MS`** watches for the failure a Bot has that nothing else in the trail can show: a stream that stops producing anything. Every other audit row is something that happened, and @@ -169,6 +170,7 @@ where `` is `google`, `microsoft` or `okta`. | `AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS` | Local-only private-host browsing when `true`. Cloud metadata addresses are still refused. | | `AGENT_COMPUTER_POLICY` | JSON action policy: `{"mode":"enforce","deny":[...],"allow":[...]}`. | | `COMPUTER_RUNTIME` | Set to `runsc` to run supervised computers under gVisor. | +| `COMPUTER_SANDBOX` | Set to `on` to enable Chromium's own sandbox where the host permits user namespaces. Which way it went is printed at start-up. | `agent-computer` also reads: diff --git a/docs/deployment.md b/docs/deployment.md index 60bf2802..09e4ec35 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -111,7 +111,9 @@ supervisor is still not in this image, so every replica shares the one browser i ## Platform notes -**Google Cloud Run.** Set memory to at least 2 GB and max instances to 1. Cloud Run runs every +**Google Cloud Run.** Set memory to at least 2 GB. More than one instance is fine (see Replicas +above); each instance has its own browser, so a Bot's logins stay on whichever instance served them. +Cloud Run runs every container under gVisor, which Chromium is sensitive to; test a navigation before trusting it. `gcloud run compose up` will also deploy the whole compose file if you want a throwaway database alongside. diff --git a/server/src/agents/invocation.ts b/server/src/agents/invocation.ts deleted file mode 100644 index 592405b6..00000000 --- a/server/src/agents/invocation.ts +++ /dev/null @@ -1,20 +0,0 @@ -type Agent = { - id: string; - type: "built_in" | "remote_ag_ui"; - available: boolean; - reason?: string; -}; -type Response = { text: string; citations: unknown[] }; - -export function createAgentInvoker(ports: { - knowledge: (question: string) => Promise; - remote: (agentId: string, question: string) => Promise; -}) { - return async (agent: Agent, question: string) => { - if (!agent.available) - throw new Error(agent.reason ?? "Agent is unavailable."); - return agent.type === "built_in" - ? ports.knowledge(question) - : ports.remote(agent.id, question); - }; -} diff --git a/server/tests/agent-invocation.test.ts b/server/tests/agent-invocation.test.ts deleted file mode 100644 index b2f6a8db..00000000 --- a/server/tests/agent-invocation.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { expect, test } from "bun:test"; -import { createAgentInvoker } from "../src/agents/invocation"; - -test("invokes the selected available built-in agent", async () => { - const invoke = createAgentInvoker({ - knowledge: async () => ({ text: "Answer", citations: [] }), - remote: async () => ({ text: "unused", citations: [] }), - }); - await expect( - invoke({ id: "knowledge", type: "built_in", available: true }, "question"), - ).resolves.toEqual({ text: "Answer", citations: [] }); -}); - -test("rejects unavailable agents before invocation", async () => { - const invoke = createAgentInvoker({ - knowledge: async () => ({ text: "unused", citations: [] }), - remote: async () => ({ text: "unused", citations: [] }), - }); - await expect( - invoke( - { - id: "knowledge", - type: "built_in", - available: false, - reason: "Model credential is not configured.", - }, - "question", - ), - ).rejects.toThrow("Model credential is not configured."); -}); From 0343d570c7581afd56e0ccefe52527509b0e2acf Mon Sep 17 00:00:00 2001 From: David McKay Date: Fri, 21 Aug 2026 17:42:43 -0700 Subject: [PATCH 2/2] Reconcile with the table drops that landed after #126 and #127 dropped the document index and the old connector tables, so the changelog line that said the index was "read by nothing" now understates it: the tables are gone. Say dropped, and add the one Upgrading note that matters, which is that those migrations destroy that data and cannot be rolled back. architecture.md still listed connector state among what the database holds; #127 removed it, so the line goes too. The README's database line never named those tables, so it needs nothing. #123 is CI and build hardening, not a deployment behavior, so it earns no changelog line. --- CHANGELOG.md | 7 ++++++- docs/architecture.md | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f70474..c73817c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,11 @@ digit. The same rule container and volume names have always followed. A deployme `AUDIT_RETENTION_DAYS` is new and unset, which keeps the audit trail forever, as before. Set it to a whole number of days to have old rows removed. +The local document index and the old connector tables are dropped by migration. `documents`, +`chunks`, `document_acls` and the four connector-bookkeeping tables are removed and their rows go +with them; this cannot be rolled back. A deployment that had been syncing into the local index loses +that copy, which is the point: answering now goes through a live system's own search. + **An MCP server pointed at a credential that no longer exists loses the pointer.** `mcp_servers` now names its credential with a real foreign key, where the column was `text` against a `uuid` primary key with nothing checking it — so a deployment is allowed to be holding a pointer to a vault @@ -69,7 +74,7 @@ Sessions survive and nobody signs in again. that system's own search as the person asking, so the vendor decides what they may see and there is no second copy of anybody's documents here to keep in step, to secure, or to leave behind when somebody is removed. The local index that was being filled — `documents`, `chunks` and - `document_acls` — is read by nothing, and the connector that filled it has been removed. Retrieval over + `document_acls` — and the connector that filled it have both been dropped. Retrieval over a copy of a customer's corpus is not a thing OpenBot does. ### Added diff --git a/docs/architecture.md b/docs/architecture.md index 50d75228..b9aa4e8e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -19,7 +19,7 @@ Regenerate it with `bun run diagram` after changing anything it shows. | `agent-bot` | 4200 | Proof-of-concept AG-UI Bot. | | `agent-langgraph` | 4201 | LangGraph AG-UI Bot. | | `supervisor` | 4500 host / 4300 container | Creates, stops, resets, and lists per-Bot computer containers. | -| PostgreSQL with pgvector | 5432 | Product data, audit rows, credentials, policy, grants, channels, components, and connector state. | +| PostgreSQL with pgvector | 5432 | Product data, audit rows, credentials, policy, grants, channels, and components. | | CopilotKit Intelligence | external | Durable threads, memory, and realtime gateway. | `scripts/start.sh` starts PostgreSQL, `agent-computer`, `agent-bot`, `agent-langgraph`, and the supervisor through Docker Compose, then starts `server` and `app` on the host.