Run telegram as its own account in a runtime of its own, keep its offset per update, and keep no copy of a secret on the desk (hq ADR 0259 §8)

This commit is contained in:
jochen
2026-10-09 11:03:36 +02:00
committed by jschoubben
parent cc5ddf86c1
commit 50f437fbb1
11 changed files with 49 additions and 26 deletions
+15 -17
View File
@@ -30,19 +30,18 @@ as the operator.
D2 and D3 (a reopening said by an edit; a clearing edits the first message) are the router's, and D9 the
watcher's.
## How it keeps the token from the agents (novox/hq ADR 0259 §8)
## How it keeps the token from the agents (novox/hq ADR 0259 §8, as revised)
- **It runs as the account `telegram`**, which the module makes (no shell, no login). The tool runner, which
runs as root, starts this bundle as that account (`MESH_RUN_AS`) and refuses root and the operator's
account.
- **Its state directory is the account's, 0700, and its secret `telegram-token` the account's, 0600.** The
operator's account — which every agent runs as — reads neither, and cannot attach to its process.
- **The bus credential stays the runner's**, root's only. The bundle reaches the bus through its own
standard input and output, and the runner publishes for it only its own kind's subjects.
- **An agent reaches the runner only through the mesh MCP server's tools**, which make tool calls; no tool
says a choice, a link or a proof.
- **If an agent holds root on the machine, none of this holds**: root reads the token and the runner's bus
credential and can publish anything.
- **It runs as the account `telegram`, in a runtime of its own, on a bus account of its own** (`runs-as`). The
machine's runtime runs as the operator's account — which every agent runs as — and never carries it; the
controller refuses to compose it there.
- **Its state directory, its bot token and its bus credential belong to `telegram`**, 0700 and 0600
(`secrets-owner`). The operator's account reads none of them, and cannot attach to its process.
- **Only this kind's holder says this kind's choices, links and codes**: the bus grants those subjects to its
own account alone.
- **The router believes it proves its sender only so placed**, and only while the controller's self-check
finds no agent able to become root without a person on its machine or the router's (`agent-root`).
- **If an agent holds root on the machine, none of this holds**: root reads the token and every credential.
## Where it runs
@@ -54,13 +53,12 @@ never inject one.
1. **A bot**: in Telegram, talk to @BotFather, `/newbot`, a name and a username. BotFather answers with the
bot's token. Keep it on the phone; never paste it into a chat with an agent.
2. **The token, to the vault**, as this module's own secret `telegram-token` (novox/hq ADR 0259 §10, the
vault's verb for a secret given at the desk; until it is built, `secret accept <control node> telegram
telegram-token` at a terminal on the control node, which reads it without echoing it). Then push the
control node.
2. **The token, to the vault**, as this module's own secret `telegram-token`: the vault's verb for a secret
given at the desk, or `secret accept <control node> telegram telegram-token` at a terminal on the control
node, which reads it without echoing it. Then push the control node.
3. **The link**: send the bot `/start`. A six-digit code appears on the desk; send it to the bot within ten
minutes. The account answers acknowledgements at once and approves an hour later; every channel says so.
`messenger_identities` shows it, `messenger_unlink` undoes it.
`messenger_identities` shows it. `messenger_unlink` asks the linked account itself whether to unlink it.
## Tools
+3 -3
View File
@@ -354,8 +354,8 @@ func (c *Channel) Poll(wait time.Duration) error {
if u.UpdateID >= offset {
offset = u.UpdateID + 1
}
}
if len(updates) > 0 {
// Kept after each update, not after the batch: a restart in the middle takes again only the one it
// was handling (the review of 2026-10-08), and the router acts on an answer once by its id anyway.
if err := c.Store.SetOffset(offset); err != nil {
c.Logf("[telegram] the update offset could not be kept; a restart may take some updates again: %v", err)
}
@@ -447,7 +447,7 @@ func (c *Channel) Status() map[string]any {
"up to 4096 characters arrive whole; longer is cut and the cut said",
"warnings and clearings arrive without sound; urgent messages and questions ring",
"a rate limit is waited out; a refusal is said to the router as permanent; an edit to the same text is a success",
"it runs as its own account, which alone reads the bot token",
"it runs as its own account, in a runtime of its own on a bus account of its own, never the machine's runtime; that account alone reads the bot token",
}
return out
}
@@ -23,6 +23,7 @@ func TestItRunsAsItsOwnAccountWhichAloneHoldsTheToken(t *testing.T) {
}
Own map[string]string `json:"own-secrets"`
Owner string `json:"secrets-owner"`
RunsAs string `json:"runs-as"`
Resources []map[string]any
Build struct {
Artifacts []struct {
@@ -44,8 +45,9 @@ func TestItRunsAsItsOwnAccountWhichAloneHoldsTheToken(t *testing.T) {
}
}
env := m.Build.Artifacts[0].Env
if env["MESH_RUN_AS"] != "telegram" || env["MESH_TELEGRAM_TOKEN_FILE"] != m.Own["telegram-token"] || m.Owner != "telegram" {
t.Fatalf("env %v, secret %v, owner %q", env, m.Own, m.Owner)
if m.RunsAs != "telegram" || env["MESH_TELEGRAM_TOKEN_FILE"] != m.Own["telegram-token"] || m.Owner != "telegram" ||
m.Own["broker"] == "" {
t.Fatalf("runs as %q, env %v, secrets %v, owner %q", m.RunsAs, env, m.Own, m.Owner)
}
var account, state bool
for _, r := range m.Resources {
+1 -1
View File
@@ -2,4 +2,4 @@ module telegram
go 1.22
require git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261008145004-62367ce15ad6
require git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261008163924-7c32dfbdf969
+2
View File
@@ -1,2 +1,4 @@
git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261008145004-62367ce15ad6 h1:JT7xM1bnLNInW7/oImV2OlXTrcQ4/GSM0Y8tAb+AhmY=
git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261008145004-62367ce15ad6/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261008163924-7c32dfbdf969 h1:qivAvJIYHFx3yNA+bt52yNRZkGioHLOw9dqs9zw4OJ8=
git.novox.be/novox/mesh-sdk/go v0.1.8-0.20261008163924-7c32dfbdf969/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
+2 -1
View File
@@ -2,6 +2,7 @@
"module": "telegram",
"version": "1",
"slug": "tgrm",
"runs-as": "telegram",
"claims": [
{
"name": "channel",
@@ -33,6 +34,7 @@
}
],
"own-secrets": {
"broker": "${dir:state}/broker",
"telegram-token": "${dir:state}/telegram-token"
},
"secrets-owner": "telegram",
@@ -68,7 +70,6 @@
"telegram"
],
"env": {
"MESH_RUN_AS": "telegram",
"MESH_TELEGRAM_TOKEN_FILE": "${dir:state}/telegram-token"
}
}