feat(policy): check authored message routing (#111087)

* feat(policy): check authored message routing

* fix(policy): normalize routing probe comparisons

* Policy: keep routing evidence types internal

* Docs: refresh policy map

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
This commit is contained in:
Omar Shahine
2026-07-20 17:31:29 -07:00
committed by GitHub
co-authored by Omar Shahine
parent cde2e71d43
commit e27e2d08fd
26 changed files with 1220 additions and 3 deletions
+52
View File
@@ -18,6 +18,7 @@ does not attest per-agent credential stores such as `auth-profiles.json`.
Policy checks configured channels, MCP servers, model providers, network SSRF
posture, ingress/channel access, Gateway exposure and node command posture,
authored message-routing probes,
agent workspace access, sandbox posture, data-handling posture, secret
provider/auth profile posture, and governed tool metadata (`TOOLS.md`). Use it
when a workspace needs a durable, checkable statement such as "Telegram must
@@ -68,6 +69,23 @@ example covering every supported section:
"allow": false,
},
},
"routing": {
"requireBindings": true,
"requireConfiguredChannels": true,
"probes": [
{
"id": "family-dm",
"route": {
"channel": "imessage",
"peer": { "kind": "direct", "id": "+15555550123" },
},
"expect": {
"agentId": "family",
"matchedBy": ["binding.peer"],
},
},
],
},
"ingress": {
"session": {
"requireDmScope": "per-channel-peer",
@@ -183,6 +201,11 @@ Cross-cutting notes not obvious from the rule tables below:
setting). It does not inspect logs, telemetry exports, transcripts, or
memory files, and a clean result does not prove that no personal data or
secrets exist in them.
- Routing probes reuse OpenClaw's runtime binding resolver. Routing evidence
records only the probe id, resolved agent, match kind, and redacted binding
metadata. It never records peer, account, guild, team, or role identifiers.
Adding a routing section intentionally changes the policy and attestation
hashes; policies without routing keep their existing evidence shape.
### Policy rule reference
@@ -310,6 +333,27 @@ Every scope present in `policy.jsonc` must be valid and enforceable.
| ------------------------------ | ----------------------------------- | ------------------------------------------------------------------ |
| `network.privateNetwork.allow` | Private-network SSRF escape hatches | Set to `false` to require private-network access to stay disabled. |
#### Message routing
| Policy field | Observed state | Use when |
| ----------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------- |
| `routing.requireBindings` | Channel route bindings, excluding ACP bindings | Require at least one message-routing binding. |
| `routing.requireConfiguredChannels` | Binding channel ids and configured `channels.*` ids | Detect stale or misspelled binding channel ids. |
| `routing.probes[].route` | The public OpenClaw route resolver | Describe a representative inbound route without sending a message. |
| `routing.probes[].expect.agentId` | Resolved agent id | Require the route to reach the reviewed agent. |
| `routing.probes[].expect.matchedBy` | Resolver match kind | Require peer, account, channel, or other reviewed binding specificity. |
Probe ids must be unique. A route supports `channel`, optional `accountId`,
`peer`, `parentPeer`, `guildId`, `teamId`, and `memberRoleIds`. Peer kinds are
`direct`, `group`, and `channel`. `matchedBy` may contain one or more runtime
match kinds, including `binding.peer`, `binding.account`, `binding.channel`,
or `default`.
Routing checks are conformance checks only. They do not change startup,
message delivery, binding precedence, or fallback behavior. Findings require
operator review because automatically changing a binding could redirect
private messages.
#### Ingress and channel access
| Policy field | Observed state | Use when |
@@ -498,6 +542,10 @@ organization-authored policy; the checked policy may add stricter values or
extra rules. A top-level checked rule can satisfy a scoped baseline rule when
it is equally or more restrictive. Scope names do not need to match between
files; comparison is keyed by selector (`agentIds`/`channelIds`) and field.
For routing probes, every baseline probe id must remain with the same route
and expected agent. A checked policy may add probes or narrow `matchedBy`, but
removing a probe, changing its route or agent, or widening its accepted match
kinds is weaker.
Clean compare (`--json`):
@@ -760,6 +808,10 @@ the interval.
| `policy/models-denied-provider` | A configured model provider or model ref uses a denied provider. |
| `policy/models-unapproved-provider` | A configured model provider or model ref is outside the allowlist. |
| `policy/network-private-access-enabled` | A private-network SSRF escape hatch is enabled when policy denies it. |
| `policy/routing-bindings-required` | Policy requires a channel route binding, but none is configured. |
| `policy/routing-binding-channel-unconfigured` | A route binding names a channel absent from `channels.*`. |
| `policy/routing-agent-mismatch` | An authored route resolves to a different agent. |
| `policy/routing-match-kind-mismatch` | An authored route matches at an unexpected binding specificity. |
| `policy/ingress-dm-policy-unapproved` | A channel DM policy is outside the policy allowlist. |
| `policy/ingress-dm-scope-unapproved` | `session.dmScope` does not match the policy-required DM isolation scope. |
| `policy/ingress-open-groups-denied` | A channel group policy is `open` while policy denies open group ingress. |
+1
View File
@@ -1912,6 +1912,7 @@ Do not edit it by hand; run `pnpm docs:map:gen`.
- H4: MCP servers
- H4: Model providers
- H4: Network
- H4: Message routing
- H4: Ingress and channel access
- H4: Gateway
- H4: Agent workspace