Skip to content

Commit b11c171

Browse files
committed
fix(policy): clarify attestation trust boundary
1 parent 0b06044 commit b11c171

7 files changed

Lines changed: 27 additions & 21 deletions

File tree

docs/cli/policy.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ Policy config lives under `plugins.entries.policy.config`.
161161
Set `plugins.entries.policy.config.enabled` to `false` to disable policy checks
162162
for a workspace while leaving the plugin installed.
163163

164+
`expectedHash` and `expectedAttestationHash` are enforcement inputs only when
165+
OpenClaw config is controlled by a trusted operator, gateway, or supervisor. If
166+
the governed workspace can edit its own OpenClaw config, those values are
167+
advisory audit locks: they can still detect drift, but they do not by
168+
themselves create a boundary against that workspace.
169+
164170
Tool metadata requirements are authored in `policy.jsonc` with
165171
`tools.requireMetadata`, for example `["risk", "sensitivity", "owner"]`.
166172

@@ -251,15 +257,18 @@ these form the audit tuple for this policy check.
251257

252258
If a later gateway or supervisor uses policy to block, approve, or annotate a
253259
runtime action, it should record the attestation hash from the last clean policy
254-
check. `checkedAt` stays in JSON output for audit logs, but is not part of the
255-
stable attestation hash.
260+
check in a config source the governed workspace cannot silently rewrite.
261+
`checkedAt` stays in JSON output for audit logs, but is not part of the stable
262+
attestation hash.
256263

257264
Use this lifecycle when accepting policy state:
258265

259266
1. Author or review `policy.jsonc`.
260267
2. Run `openclaw policy check --json`.
261-
3. If the result is clean, record `attestation.policy.hash` as `expectedHash`.
262-
4. Record `attestation.attestationHash` as `expectedAttestationHash`.
268+
3. If the result is clean, record `attestation.policy.hash` as `expectedHash`
269+
in trusted OpenClaw config.
270+
4. Record `attestation.attestationHash` as `expectedAttestationHash` in trusted
271+
OpenClaw config.
263272
5. Re-run `openclaw doctor --lint` in CI or release gates.
264273

265274
`policy diff` compares two saved `policy check --json` outputs to explain what
@@ -272,9 +281,11 @@ approval requests: policy path/hash, configured expected hash when present, the
272281
accepted attestation hash when present, the current policy evidence hash, and
273282
the target tool reference. Gateway approval request, list, and resolve events
274283
preserve that metadata so supervisors can audit the decision against the policy
275-
and workspace state that produced it. If the current attestation no longer
276-
matches `expectedAttestationHash`, the runtime gate fails closed before asking
277-
for approval and reports both the current and expected attestation hashes.
284+
and workspace state that produced it. If trusted config supplies
285+
`expectedAttestationHash` and the current attestation no longer matches it, the
286+
runtime gate fails closed before asking for approval and reports both the
287+
current and expected attestation hashes. If the governed workspace owns the
288+
config value, treat this as drift detection rather than a security boundary.
278289

279290
If policy rules change intentionally, update both accepted hashes from a clean
280291
check. If workspace settings change intentionally but policy stays the same,
@@ -445,8 +456,8 @@ The runtime gate:
445456

446457
- blocks tool calls if the enabled policy artifact is missing or does not match
447458
`expectedHash`;
448-
- blocks tool calls if `expectedAttestationHash` is configured and the current
449-
policy evidence no longer matches the accepted clean policy check;
459+
- blocks tool calls if trusted config supplies `expectedAttestationHash` and the
460+
current policy evidence no longer matches the accepted clean policy check;
450461
- blocks governed tool calls whose required metadata is missing or invalid;
451462
- asks for approval for governed tools marked `risk:critical` or
452463
`IRREVERSIBLE_EXTERNAL`;

docs/plugins/reference/policy.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ stable attestation hash.
3636
When `runtimeToolPolicy` is enabled, the Policy plugin registers a trusted tool
3737
policy that blocks unverifiable governed tool calls and requests approval for
3838
critical or irreversible governed tools. If `expectedAttestationHash` is
39-
configured, the same gate fails closed when current policy evidence no longer
40-
matches the accepted clean policy check.
39+
configured by a trusted operator, gateway, or supervisor, the same gate fails
40+
closed when current policy evidence no longer matches the accepted clean policy
41+
check. If the governed workspace can edit its own OpenClaw config, the hash is
42+
an advisory audit lock rather than a boundary against that workspace.
4143

4244
Policy findings do not have to be backed by oc-path. Config and workspace
4345
findings use `oc://` targets only when the system can point to a resolvable

extensions/policy/openclaw.plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
},
3131
"expectedHash": {
3232
"type": "string",
33-
"description": "Optional sha256 hash for hash-locking the approved policy artifact."
33+
"description": "Optional sha256 hash for hash-locking the approved policy artifact from trusted config."
3434
},
3535
"expectedAttestationHash": {
3636
"type": "string",
37-
"description": "Optional sha256 hash for the last accepted clean policy check."
37+
"description": "Optional sha256 hash for the last accepted clean policy check from trusted config."
3838
},
3939
"path": {
4040
"type": "string",

extensions/policy/src/cli.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { promises as fs } from "node:fs";
22
import { tmpdir } from "node:os";
33
import { join } from "node:path";
4-
import { clearHealthChecksForTest } from "openclaw/plugin-sdk/plugin-test-runtime";
54
import { clearConfigCache } from "openclaw/plugin-sdk/runtime-config-snapshot";
65
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
76
import { policyCheckCommand, policyDiffCommand, policyWatchCommand } from "./cli.js";

src/gateway/protocol/schema/plugin-approvals.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export const PluginApprovalRequestParamsSchema = Type.Object(
2828
turnSourceTo: Type.Optional(Type.String()),
2929
turnSourceAccountId: Type.Optional(Type.String()),
3030
turnSourceThreadId: Type.Optional(Type.Union([Type.String(), Type.Number()])),
31-
metadata: Type.Optional(PluginJsonValueSchema),
3231
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, maximum: MAX_PLUGIN_APPROVAL_TIMEOUT_MS })),
3332
twoPhase: Type.Optional(Type.Boolean()),
3433
},

src/gateway/server-methods/plugin-approval.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ export function createPluginApprovalHandlers(
7575
turnSourceTo?: string | null;
7676
turnSourceAccountId?: string | null;
7777
turnSourceThreadId?: string | number | null;
78-
metadata?: unknown;
7978
timeoutMs?: number;
8079
twoPhase?: boolean;
8180
};

src/infra/plugin-approvals.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import type { ExecApprovalDecision } from "./exec-approvals.js";
2-
import {
3-
policyApprovalMetadataLines,
4-
type ApprovalMetadataValue,
5-
} from "./policy-approval-metadata.js";
2+
import { policyApprovalMetadataLines } from "./policy-approval-metadata.js";
63

74
type PluginApprovalJsonValue =
85
| string
@@ -27,7 +24,6 @@ export type PluginApprovalRequestPayload = {
2724
turnSourceTo?: string | null;
2825
turnSourceAccountId?: string | null;
2926
turnSourceThreadId?: string | number | null;
30-
metadata?: ApprovalMetadataValue;
3127
};
3228

3329
export type PluginApprovalRequest = {

0 commit comments

Comments
 (0)