DEV Community

willamhou
willamhou

Posted on

How to Add Tamper-Evident Audit Trails to Your OpenClaw Assistant

Your OpenClaw assistant just deleted a file, sent an email, or ran a shell command on your machine. Can you prove what it did? When? Authorized by whom?

Standard log files don't answer that. They can be edited. They can be rotated. They can be deleted. After an incident, "the agent did X" is your word against the runtime that produced the log.

This post walks through adding cryptographic audit trails to OpenClaw using @signet-auth/openclaw-plugin. Every tool call gets:

  • An Ed25519 signature over the canonical action payload (RFC 8785 JCS → SHA-256 → Ed25519)
  • A hash-chained entry in ~/.signet/audit/*.jsonl — deletion or reordering breaks the chain
  • Optional policy enforcement (deny dangerous tools before they run)
  • Optional encryption of tool params at rest

Total setup time: under a minute.

What you need

  • OpenClaw (>=2026.3.24-beta.2) — the gateway you already run
  • The signet CLI on $PATH. Install via cargo install signet-cli, or grab a release binary
  • Two minutes

That's it.

Step 1: Create a signing identity

The plugin signs with a local Ed25519 key. Generate one:

signet identity generate --name openclaw-agent --owner you@example.com
Enter fullscreen mode Exit fullscreen mode

This writes the keypair to ~/.signet/identities/openclaw-agent/. The private key stays on disk; the public key is what verifiers (auditors, you, anyone) use to check signatures later.

If you want the key passphrase-protected, add --passphrase. We'll set the passphrase env var below.

Step 2: Install the plugin

openclaw plugins install @signet-auth/openclaw-plugin
Enter fullscreen mode Exit fullscreen mode

OpenClaw checks ClawHub first, falls back to npm.

Step 3: Configure

Add the plugin entry to ~/.openclaw/config.json:

{
  "plugins": {
    "entries": {
      "signet": {
        "config": {
          "keyName": "openclaw-agent",
          "target": "openclaw://gateway/local"
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Two fields are enough to get started. Everything else has sensible defaults.

If your key is passphrase-protected, export the passphrase before launching the gateway:

export SIGNET_PASSPHRASE='...'
openclaw start
Enter fullscreen mode Exit fullscreen mode

Step 4: Use OpenClaw normally

Run any task that exercises tools — file operations, web search, shell commands, anything. Every tool call now produces a signed receipt before execution. Tool errors are logged. Nothing in your workflow changes.

While OpenClaw runs, watch the gateway log for lines like:

[signet] signed file_read (call=tc_abc123 session=ses_xyz789) → rec_a1b2c3d4...
[signet] signed shell_exec (call=tc_def456 session=ses_xyz789) → rec_e5f6a7b8...
Enter fullscreen mode Exit fullscreen mode

Each rec_... is a receipt id derived from the signature itself.

Step 5: Verify the audit trail

After OpenClaw has done some work, verify the chain:

signet audit --verify
Enter fullscreen mode Exit fullscreen mode

Output:

Hash chain integrity:    valid (records=42)
Signature verification:  42 / 42 valid
Enter fullscreen mode Exit fullscreen mode

That second line is the cryptographic guarantee: 42 receipts, every one of them signed by the key whose public half lives in ~/.signet/identities/openclaw-agent/openclaw-agent.pub.json. Modify any field of any receipt and the verification fails. Delete a record and the chain breaks.

To browse what your assistant actually did:

signet explore
Enter fullscreen mode Exit fullscreen mode

Press a tool name to see the full action payload, params, timestamp, and the run id that ties together the before_tool_call and after_tool_call events.

What a receipt looks like

Each line of ~/.signet/audit/audit.jsonl is one record. The signed receipt inside looks like this:

{
  "v": 1,
  "id": "rec_a1b2c3d4e5f6a7b8...",
  "action": {
    "tool": "shell_exec",
    "params": { "command": "git status" },
    "params_hash": "sha256:...",
    "target": "openclaw://gateway/local",
    "transport": "stdio"
  },
  "signer": {
    "name": "openclaw-agent",
    "pubkey": "ed25519:...",
    "owner": "you@example.com"
  },
  "ts": "2026-04-26T14:30:00.000Z",
  "nonce": "rnd_...",
  "sig": "ed25519:..."
}
Enter fullscreen mode Exit fullscreen mode

The signature covers the entire payload. Modify action.params.command from "git status" to "rm -rf /" and the signature stops verifying.

Adding policy enforcement

Audit-after-the-fact is good. Blocking dangerous calls before they run is better.

Create a policy at ~/.signet/policies/openclaw.yaml:

version: 1
name: openclaw-safe
default_action: allow

rules:
  - id: deny-rm-rf
    match:
      tool: shell_exec
      params:
        command:
          contains: "rm -rf"
    action: deny
    reason: "destructive command  never run without human approval"

  - id: rate-limit-network
    match:
      tool:
        one_of: [http_request, fetch_url]
    action: rate_limit
    rate_limit:
      window_secs: 60
      max_calls: 10
Enter fullscreen mode Exit fullscreen mode

Wire it into the plugin config:

{
  "plugins": {
    "entries": {
      "signet": {
        "config": {
          "keyName": "openclaw-agent",
          "target": "openclaw://gateway/local",
          "policy": "~/.signet/policies/openclaw.yaml"
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Restart OpenClaw. When the policy denies a call, you'll see:

[signet] policy denied shell_exec: destructive command — never run without human approval
Enter fullscreen mode Exit fullscreen mode

OpenClaw skips the tool call entirely. The denial itself isn't signed — that's by design. Only allowed actions produce receipts. The denial is logged at warn level so it's still observable.

What this gives you

Without Signet With Signet
"OpenClaw ran shell_exec" — log entry, editable Ed25519 signature proving exactly what command, when, by which key
Ordering can be falsified Hash chain breaks if any entry is removed or reordered
Trust your local logs Verify offline with just the public key
No regulatory mapping Maps to EU AI Act Article 12 "automatic event logging" requirement (effective August 2026)

Verifying as an external auditor

If someone else needs to verify your audit log — a security team, a regulator, you on a different machine — they only need:

  • The audit log file (~/.signet/audit/audit.jsonl)
  • The agent's public key (~/.signet/identities/openclaw-agent/openclaw-agent.pub.json)

No private key. No access to the OpenClaw runtime. They run:

signet audit --verify --keys-dir ./received-keys
Enter fullscreen mode Exit fullscreen mode

If the chain is intact and every signature checks out, the audit log is authentic. If anyone tampered with anything, verification fails at the modified record.

This is the property that "we keep good logs" can never give you.

What's next

The plugin is open source (Apache-2.0 OR MIT). If you want:

  • Bilateral co-signing: server-side keys signing alongside the agent for two-party non-repudiation. Already in Signet core; can be wired into OpenClaw via a follow-up plugin.
  • Trust bundles: pin a published bundle of trusted public keys so verifiers don't need to track keys out-of-band.
  • Encrypted params: set encryptParams: true in the plugin config to wrap action.params in an XChaCha20-Poly1305 envelope keyed off the signing key. The signature chain stays verifiable; only key holders see the params.

Repo: https://github.com/Prismer-AI/signet
Plugin: https://github.com/Prismer-AI/signet/tree/main/packages/signet-openclaw-plugin
Issues: https://github.com/Prismer-AI/signet/issues

If you build something with this, I'd love to know. The OpenClaw maintainers in particular — feedback on the hook integration and security audit collector behavior would be valuable.

Top comments (0)