Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a1bb131
fix: name the element in a browser refusal, neutral mcp notwithstanding
anygivenfriday Aug 25, 2026
6b026c2
feat: test a boundary rule against history before saving it
anygivenfriday Aug 25, 2026
a6bada9
Channel pin and soft delete, and a Notion connector over hosted MCP (…
guidovizoso Aug 25, 2026
88078a4
Run OpenBot on Kubernetes: Bots and all, proven on EKS (#235)
davidmckayv Aug 26, 2026
291bae6
Show a dot on a channel a Bot has spoken in unseen (#259)
guidovizoso Aug 26, 2026
b941385
Update azure/setup-helm action to v5 (#260)
renovate[bot] Aug 26, 2026
a4549be
Say what a strict content-security-policy has to allow (#225)
zopeVaibhav Aug 26, 2026
951d20f
Point the test at the database the project actually has (#234)
zopeVaibhav Aug 26, 2026
c0638c7
Let the API reach Intelligence and sign-in when a NetworkPolicy is on…
zopeVaibhav Aug 26, 2026
cbab27e
Refuse a credential written into the rest of the address (#230)
beardthelion Aug 26, 2026
8f68eaa
Spend an MCP token only for its own server, and only at its own addre…
beardthelion Aug 26, 2026
a46b5f9
Carry the per-Bot egress proxy as far as the process that reads it (#…
beardthelion Aug 26, 2026
0403be1
Refuse the shell and a workspace write while a person holds the wheel…
beardthelion Aug 26, 2026
615a041
Stop grant queries polling the placeholder Bot (#240)
anygivenfriday Aug 26, 2026
43ea5c1
Refuse a port that answers but is not OpenBot, and stop compose blank…
anygivenfriday Aug 26, 2026
50949d6
Record why a message was not routed, not only where it went (#248)
Hotragn Aug 26, 2026
c6ed702
Keep a finished suspension for the idle window, not for a day (#254)
beardthelion Aug 26, 2026
7fda66a
Say how a Bot's egress proxy reaches a computer on Kubernetes (#252)
beardthelion Aug 26, 2026
09fa2b3
Merge branch 'v241' into integ
davidmckayv Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@ KEY_ENCRYPTION_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
# one to leave alone until somebody has decided otherwise: the trail is append-only and nothing else
# can remove a row, so this is the only way it ever shrinks.
# AUDIT_RETENTION_DAYS=365
# Two names for one number, and they have to agree.
#
# The server reads PORT (server/src/index.ts). scripts/start.sh reads SERVER_PORT, because it also
# has to know where the app should proxy and which port to report free -- and docs/configuration.md
# documents SERVER_PORT as the setting. Only PORT shipped here, so moving the server by editing this
# line left the script still looking at 3001: it found whatever else was there, accepted the first
# 200 as proof, and failed several stages later parsing that stranger's HTML as JSON.
#
# Change both, or neither.
PORT=3001
SERVER_PORT=3001
TENANT_PACKAGE_DIR=../examples/fintech
# What this deployment calls itself, when more than one shares an Intelligence project. A copy of a
# deployment made for development uses the same project key, and threads are listed per Bot with
Expand Down Expand Up @@ -230,8 +240,13 @@ COMPUTER_TOKEN=
#
# This is attribution, not anonymity, and it is not a boundary by itself: it gives a security team a
# per-Bot address for network rules alongside AGENT_COMPUTER_POLICY.
# EGRESS_PROXY_DEFAULT=http://user:password@proxy.internal:8080
# EGRESS_PROXY_SALES_BOT=http://sales.proxy.internal:8080
#
# These go in `egress.env` beside this file, NOT here. The names are per-Bot, so Compose cannot
# list them the way it lists every variable below, and it hands a container only what it is told to.
# In `.env` they reach no process and the browser goes out directly with nothing saying so.
#
# EGRESS_PROXY_DEFAULT=http://user:password@proxy.internal:8080
# EGRESS_PROXY_SALES_BOT=http://sales.proxy.internal:8080


# The managed coworker AG-UI endpoint. Optional: use an HTTP(S) URL, and set MANAGED_AGENT_TOKEN
Expand Down
86 changes: 85 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,90 @@ jobs:
- run: bun run typecheck
working-directory: ${{ matrix.package }}

chart:
name: chart (${{ matrix.target }})
runs-on: ubuntu-latest
strategy:
# One red target must not hide whether another is red too.
fail-fast: false
matrix:
target: [self-hosted, eks, eks-sandbox, gke, aks]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v3.19.0
# For the coherence check below, which is a Bun script like everything else here.
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.3.14
# Nothing rendered this chart until now, which is how four values files that produce a server
# unable to start were shipped and stayed shipped. Rendering is the cheap half; the refusals in
# validation.yaml are the half that catches a missing value before a cluster does.
- run: helm dependency build charts/openbot
# Structure only. `helm lint` reports a template `fail` as an INFO line and exits 0 even under
# `--strict`, which was driven and confirmed, so it cannot gate the refusals below. Rendering
# does: `helm template` exits non-zero on one.
- run: helm lint charts/openbot --values charts/openbot/ci/${{ matrix.target }}-values.yaml
# A key encryption key is a real 32 bytes rather than a placeholder, because the chart checks
# its shape. Generated here so no example key is ever a literal in this repository.
- name: Render
run: |
helm template ci charts/openbot \
--values charts/openbot/ci/${{ matrix.target }}-values.yaml \
--set-string secrets.keyEncryptionKey="$(openssl rand -base64 32)" \
--api-versions agents.x-k8s.io/v1beta1/Sandbox \
--api-versions extensions.agents.x-k8s.io/v1beta1/SandboxTemplate \
> rendered.yaml
# Rendering proves the templates run. This proves the result is coherent, which is a different
# question: every secret key a container demands has to be one the chart actually writes.
# Getting that wrong is invisible until a pod starts, and every shipped target had it wrong.
- run: bun scripts/check-rendered-chart.ts rendered.yaml
# And that the refusals are load-bearing rather than decorative. A chart full of `fail`
# messages nothing ever triggers is a chart that has never been shown to refuse anything, and
# every one of these describes a state that shipped in a values file at some point.
- name: Refusals fire
run: |
set -uo pipefail
refuses() {
local why="$1"; shift
if helm template ci charts/openbot \
--values charts/openbot/ci/${{ matrix.target }}-values.yaml \
--set-string secrets.keyEncryptionKey="$(openssl rand -base64 32)" \
--api-versions agents.x-k8s.io/v1beta1/Sandbox \
--api-versions extensions.agents.x-k8s.io/v1beta1/SandboxTemplate \
"$@" >/dev/null 2>&1; then
echo "::error::The chart rendered $why, which it is supposed to refuse."
return 1
fi
echo "refused: $why"
}
# The public example key, which the server will not start with. Only where this chart
# holds the secret: with a store, the value is not readable at template time, so the
# refusal is deliberately not armed and asserting it here would be asserting a bug.
if ! grep -qE '^ *enabled: true' <(sed -n '/^externalSecrets:/,/^[a-z]/p' charts/openbot/ci/${{ matrix.target }}-values.yaml); then
refuses "the public example encryption key" \
--set-string secrets.keyEncryptionKey="$(head -c 32 /dev/zero | base64)"
else
echo "skipped: the example-key refusal is not armed when the secret comes from a store"
fi
# A Bot endpoint with nothing on the request that says who is calling. Armed whether the
# secret is this chart's or a store's, because the key list is readable either way.
refuses "a managed agent URL with no token" \
--set-string config.managedAgent.url=http://agent.default:8000/ag-ui
# A browser inside every replica of a replicated API.
refuses "an embedded browser across several replicas" \
--set server.embeddedComputer=true --set server.replicaCount=2
# A Bot's egress proxy on a port the computer's own network policy does not allow. The
# variables reach the computer through extraEnv, so nothing else notices that the policy
# then refuses to let it be reached.
refuses "an egress proxy the network policy blocks" \
--set networkPolicy.enabled=true \
--set computers.extraEnv[0].name=EGRESS_PROXY_DEFAULT \
--set-string computers.extraEnv[0].value=http://proxy.internal:3128

test:
name: tests
runs-on: ubuntu-latest
Expand Down Expand Up @@ -261,7 +345,7 @@ jobs:
name: verify
runs-on: ubuntu-latest
if: always()
needs: [static, deployables, test, build, migrations, image]
needs: [static, deployables, chart, test, build, migrations, image]
steps:
- name: Require every check
env:
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ docs/plans/
.env
.env.*
!.env.example
# Per-Bot egress proxies. Carries credentials in the URL, like .env does.
egress.env
node_modules/
**/dist/
app/src/lib/generated/application-config.ts
Expand All @@ -21,3 +23,8 @@ app/src/lib/generated/application-config.ts

# TanStack Router scratch output
app/.tanstack/

# Helm subchart tarballs, fetched by `helm dependency build`. The lock beside them is NOT ignored:
# it is what makes that fetch reproducible, and ignoring it meant every build resolved the dependency
# afresh, so CI and a customer install could take different subchart versions with no diff to show it.
charts/*/charts/
Loading