Integration
Clawperm
OpenClaw plugin for in-chat policy approvals — chat-native approve/deny prompts.
Install
openclaw plugins install @clawnify/clawperm
Configuration Example
{
"plugins": {
"allow": ["clawperm"],
"load": {
"paths": ["./workspace/clawperm"]
},
"entries": {
"clawperm": {
"config": {
"secret": "<openssl rand -hex 32>",
"agentId": "main",
"defaultTimeoutMs": 540000
}
}
}
},
"approvals": {
"plugin": {
"enabled": true,
"mode": "session",
"agentFilter": ["main"]
}
}
}
README
<p align="center">
<!-- Drop a 1280×640 hero at .github/hero.png — keyword in the headline. -->
<img src="./.github/hero.png" alt="Clawperm — in-chat policy approvals for OpenClaw" width="800" />
</p>
# Clawperm — in-chat policy approvals for OpenClaw
Chat-native approve/deny prompts for OpenClaw agents. POST a prompt over loopback HTTP, the user replies with `/approve plugin:<id> ...` in their existing webchat / Slack / Telegram / WhatsApp thread, the CLI gets the decision back.
<p align="center">
<a href="https://github.com/clawnify/clawperm/blob/main/LICENSE"><img alt="MIT License" src="https://img.shields.io/github/license/clawnify/clawperm?color=blue" /></a>
<a href="https://github.com/clawnify/clawperm/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/clawnify/clawperm?style=social" /></a>
<a href="https://github.com/openclaw/openclaw"><img alt="OpenClaw" src="https://img.shields.io/badge/openclaw-plugin-purple" /></a>
<a href="https://www.npmjs.com/package/@clawnify/clawperm"><img alt="npm" src="https://img.shields.io/npm/v/@clawnify/clawperm.svg" /></a>
</p>
## What it does
Lets an external CLI (or any local process) raise an approval prompt **in the agent's active chat session**. The user sees the prompt where they're already talking to the agent and replies inline. The caller blocks on a single HTTP request and gets back `allow-once` / `allow-always` / `deny` / `null` (timeout).
Built for [Clawnify](https://clawnify.com), open-sourced because every OpenClaw operator who ships a "policy gate" CLI hits the same wall.
## The wall it solves
OpenClaw exposes `plugin.approval.request` + `waitDecision` so any plugin can ask the user for approval. Delivery to chat works via `approvals.plugin.mode = "session"` — but only when the call carries a `sessionKey` so the gateway knows which conversation to forward into.
A CLI invoked as a child of the agent's `exec` tool **does not** get session context. Only `OPENCLAW_SHELL=exec` is set; there is no `OPENCLAW_SESSION_KEY`. Calling `openclaw gateway call plugin.approval.request` from that child opens an independent operator-token RPC — the gateway sees an admin request with no session link, the approval lands with `sessionKey: null`, and `mode: "session"` forwarding has nowhere to deliver it.
Clawperm sidesteps this by running **inside** the gateway process. It exposes a loopback HTTP route on the gateway's HTTP server. The CLI POSTs to that route; the plugin emits the approval through the in-process plugin API, which is session-aware.
## Install
```bash
openclaw plugins install @clawnify/clawperm
# or, from a local checkout:
openclaw plugins install ./extensions/clawperm
```
The plugin ships TypeScript directly — `openclaw.extensions: ["./index.ts"]`. The host loads it; no build step.
## Quickstart
Generate a shared secret and add the plugin to `openclaw.json`:
```bash
openssl rand -hex 32 # → use this for `secret` below and CLAWPERM_SECRET in the caller's env
```
```json
{
"plugins": {
"allow": ["clawperm"],
"load": {
"paths": ["./workspace/clawperm"]
},
"entries": {
"clawperm": {
"config": {
"secret": "<openssl rand -hex 32>",
"agentId": "main",
"defaultTimeoutMs": 540000
}
}
}
},
"approvals": {
"plugin": {
"enabled": true,
"mode": "session",
"agentFilter": ["main"]
}
}
}
```
Restart the gateway. The plugin registers `POST /clawperm/approval` on the gateway's HTTP port (`gateway.port`, default `18789`).
## CLI contract
```http
POST http://127.0.0.1:18789/clawperm/approval
Authorization: Bearer <secret>
Content-Type: application/json
{
"subject": "GOOGLESHEETS_SPREADSHEETS_VALUES_APPEND",
"title": "Run clawnify action GOOGLESHEETS_SPREADSHEETS_VALUES_APPEND?",
"description": "Toolkit: googlesheets\nArgs: { ... }",
"timeoutMs": 540000
}
```
Response:
```http
200 OK
{ "decision": "allow-once" | "allow-always" | "deny" | null,
"id": "plugin:cpa:..." }
```
`decision: null` means timeout — callers typically treat it as deny.
The endpoint blocks until the user resolves the approval (or `timeoutMs` elapses), so callers should set their HTTP timeout above `timeoutMs`. The gateway's hard cap is currently 10 minutes (600000 ms).
### Example: bash
```bash
curl -fsS http://127.0.0.1:18789/clawperm/approval \
-H "Authorization: Bearer $CLAWPERM_SECRET" \
-H "Content-Type: application/json" \
-d '{"subject":"GOOGLESHEETS_APPEND","title":"Append to sheet?","timeoutMs":540000}' \
| jq -r .decision
```
## Config
| Field | Type | Default | Notes |
| ----------------- | -------- | ------------ | --------------------------------------------------------------------- |
| `secret` | `string` | env fallback | Bearer token. Falls back to `CLAWPERM_SECRET` env var. |
| `agentId` | `string` | `"main"` | Agent the approval is bound to. |
| `defaultTimeoutMs`| `number` | `540000` | Used when caller omits `timeoutMs`. Capped at 600000 by the gateway. |
Treat the secret like any other shared secret — give the same value to the CLI via systemd env (`Environment=CLAWPERM_SECRET=...`) and to the plugin via `plugins.entries.clawperm.config.secret`.
## Security model
- **Loopback only.** The gateway HTTP server should already be bound to `127.0.0.1` (or to the host's private interface). Don't expose the gateway port publicly. `gateway.bind = "loopback"` is the recommended posture.
- **Bearer token.** `auth: "plugin"` means OpenClaw doesn't gate the route — clawperm enforces auth itself with a constant-time compare against the configured secret.
- **No body bigger than 64 KB.** Defensive cap; approvals are tiny.
- **No persistence.** Decisions are returned to the caller and forgotten. Persisting "allow-always" rules is the caller's responsibility (e.g. via your own policy DB).
## Development
There is no build step. The OpenClaw host loads `index.ts` directly. To iterate locally, point an `extensions/clawperm` symlink at this checkout and restart the gateway.
## Known gaps
- `openclaw/plugin-sdk/approval-runtime` is the **current best-guess** import path for `requestApproval` / `waitDecision`. If the SDK has moved them, the plugin will fail at first POST with a clear error pointing at this README and `src/plugin/index.ts`. Patch the import there.
- No retry logic — the loopback path is reliable, retries would just paper over real failures.
## Contributing
Issues and PRs welcome. Keep the surface area small — clawperm is intentionally one HTTP route and one approval shim. Anything bigger probably belongs in your own plugin.
## Related
Part of the Clawnify suite — managed OpenClaw agents for non-technical teams:
- [openclaw/openclaw](https://github.com/openclaw/openclaw) — the core agent runtime
- [clawnify/clawflow](https://github.com/clawnify/clawflow) — declarative workflows agents can read, write, and run
- [clawnify/clawvoice](https://github.com/clawnify/clawvoice) — voice agent plugin (give your agent a phone number)
## License
MIT — see [LICENSE](./LICENSE).
---
Built by the [Clawnify](https://clawnify.com) team.
integration
Comments
Sign in to leave a comment