Back to engineering notes
Engineering note · ClawKeeper

Inside ClawKeeper: testing permission boundaries before an agent acts

A tested walkthrough of ClawKeeper’s deterministic agent policy, tenant checks, payment approval gate and known trust boundaries.

Published
Collection
January–March 2026 · covers March 2026
Project
ClawKeeper
Inspected revision
f4d7d05

I want a finance agent to propose work without being able to authorize itself. That requires a decision outside its generated answer. ClawKeeper's policy module provides an inspectable example: it evaluates the proposed task against tenant context, capabilities, roles, and approval requirements, then returns a structured decision.[1]

For this breakdown, I inspected revision f4d7d05d1a7b84da791a38249073e5d360539093 and ran its seven policy tests locally on October 29, 2026. All seven passed. They use synthetic inputs and do not move money, call payment providers, or connect to a client database. This article is retrospective coverage in the January–March collection, published October 29, 2026.

The smallest useful boundary

The function evaluate_agent_policy accepts a task, authenticated tenant context, the agent's granted capabilities, and optional approval and request identifiers. It produces one of three statuses: allow, deny, or requires_approval. It also returns findings, the risk tier, approval reasons, and an audit-event object.[1]

I would start a demonstration with those inputs and outputs. A visitor does not need to inspect a large agent hierarchy to understand the core control. The proposed action carries a set of requirements. A separate function decides whether those requirements have been met.

The distinction matters when an agent is asked to process a financial document. Extracting a field, preparing a report, and initiating a payment are different capabilities. Giving an agent permission to summarize an invoice should not automatically give it permission to move money. The source represents that separation through capability policies declared in a manifest.[2]

What the tests actually demonstrated

The first test creates a synthetic reporting task with matching tenant context, a tenant-admin role, and the reporting capability. The policy returns allow with a low risk tier. The execution assertion accepts that decision.[3]

The next test proposes payment processing without an approval identifier. It returns requires_approval, sets allowed to false, and causes the execution assertion to throw. That is a concrete blocked path. The policy does not turn the absence of approval into an instruction for the agent to decide for itself.

A third test provides an approval identifier for a payment task. The decision becomes allow, while the response still records that approval was required and includes the identifier in the audit-event object. That preserves the distinction between an action that needed approval and an action that could proceed without it.

The remaining tests reject a mismatched tenant for a non-platform user, reject a task requiring a capability the agent does not possess, reject a sample guardrail-bypass instruction, and redact specified personal and credential fields. The observed run finished with seven passes and zero failures. Those are results from this single test file, not a claim that the full application or every security boundary passed.

Tenant checks and their exception

The inspected function compares the task's tenant identifier with the authenticated tenant context. A mismatch produces a critical finding unless the context role is super_admin. A critical finding leads to a denied decision.[1]

That exception should be visible during an architecture review. A statement such as “cross-tenant requests are always blocked” would be broader than the code. The implemented rule distinguishes ordinary tenant activity from a platform-level role. The integration must protect how that role is established and who can use it.

This module-level test also does not prove database isolation. The repository describes PostgreSQL row-level security separately, but the local run here did not start PostgreSQL or exercise database policies.[4] I would require a separate database test before claiming that a user cannot retrieve another tenant's records. Application policy and database enforcement deserve separate evidence.

Approval is a reference, not a verified ceremony

The payment test exposes the most important limitation in this review. The policy checks whether an approval identifier is present. In this function, it does not fetch an approval record, verify a reviewer, compare an approved amount with the current amount, or check expiry.[1]

The README explicitly says approval metadata is trusted once supplied and describes an approval workbench as roadmap work.[4] That means the module can demonstrate an approval gate while still depending on another component to establish the approval's legitimacy. I would explain both facts together.

Before using that boundary for a real consequential action, I would require the caller to supply a verified approval bound to the exact task, tenant, action, amount, and destination. If any of those change, the decision should be evaluated again. Those are implementation requirements I would add around the module; this article does not claim they are already enforced by the inspected function.

The same caution applies to replay. A valid reference used once should not automatically authorize an unrelated later task. A production design needs an explicit policy for reuse, expiry, revocation, and repeated execution. The seven tests run here do not establish those properties.

Pattern checks have a limited job

The policy serializes selected task fields and checks them against patterns for instructions such as bypassing approval, disabling guardrails, or revealing protected instructions. The supplied bypass example is rejected in the executed test.[1][3]

That demonstrates recognition of the tested pattern. It does not show comprehensive resistance to prompt injection. A new wording, a different representation, or a malicious instruction arriving through an unexamined input can present a different case. The project documentation itself notes that pattern-based detection does not catch novel techniques.[4]

I would retain the pattern checks as one control and keep authorization independent of their success. An unrecognized malicious sentence should still lack the capabilities, tenant authority, or verified approval needed for a write. The source-code lesson is to inspect those boundaries individually instead of treating a single detection result as a general safety guarantee.

Redaction and audit evidence

The redaction helper recursively handles strings, arrays, and object fields. It replaces recognized email, social-security-number, and payment-card patterns in strings. Keys matching secret, token, password, API-key, or credential patterns are replaced with a secret marker. The executed test checked synthetic examples of those transformations.[1][3]

The helper's behavior is useful evidence, but a passing helper test does not establish that every logging path calls it. I would inspect the actual writer, sample the persisted record, and attempt a failure-path log before making a statement about end-to-end redaction.

Likewise, the policy function returns an audit-event object; returning that object is different from persisting it durably. The object includes task, tenant, user, role, required capabilities, status, risk tier, approval identifier, and timestamp. An integration still needs to write it, protect access, and decide what happens when the write fails. Database append-only controls described elsewhere in the project were not exercised by this local test.

A useful implementation review

For a team evaluating this pattern, I would bring three artifacts to the review: the exact proposed task, the policy decision, and the evidence of what the executor did afterward. An allowed decision should be traceable to a bounded action. A denied decision should be accompanied by evidence that no external write occurred.

I would also test a changed task after approval, a revoked role, a missing tenant context, an unknown capability, and an unavailable audit sink. Those are proposed follow-up cases. The seven passing tests described above are the cases actually executed for this article.

ClawKeeper is presented here as an own-project engineering example with a verified policy-test result. It is not a client deployment story, a financial-control certification, or proof that autonomous payment processing is safe. Its most useful contribution to this collection is a concrete boundary that can be read, executed with synthetic data, and challenged where it still trusts its caller.

Inspect ClawKeeper's policy source alongside the tests before deciding which parts belong in your own implementation.

Sources

  1. [1]Policy and redaction implementationgithub.com/Alexi5000/ClawKeeper/blob/f4d7d05d1a7b84da791a38249073e5d360539093/src/openclaw/policy.ts
  2. [2]Capability manifestgithub.com/Alexi5000/ClawKeeper/blob/f4d7d05d1a7b84da791a38249073e5d360539093/src/openclaw/manifest.ts
  3. [3]Policy tests executed for this reviewgithub.com/Alexi5000/ClawKeeper/blob/f4d7d05d1a7b84da791a38249073e5d360539093/test/openclaw.policy.test.ts
  4. [4]Project README, security limitations and roadmapgithub.com/Alexi5000/ClawKeeper/blob/f4d7d05d1a7b84da791a38249073e5d360539093/README.md

More engineering notes

I write these notes about my own projects. If you have a product build that needs to work for a real customer, pilot, or launch, send me the brief.

Product implementation

Show me what exists, what is stuck, and what needs to work. I take ownership of the implementation, test the agreed user journey, and document the handoff.

Scope and price are agreed before work starts.

Send the project brief