gloves 🥊🥊
gloves is a secure secrets control plane for OpenClaw and other multi-agent/human operator runtimes.
- Agent-owned secrets encrypted with
age - Human-owned secrets resolved through
pass - Access requests, approvals, metadata, audit trails, and TTL reaping
Why gloves
- One command surface for both agent and human secret domains
- Secure-by-default storage permissions and atomic writes
- Integrity checks with audit events for sensitive operations
- Explicit request lifecycle for human-gated access
- Rust implementation with comprehensive tests and CI gates
Installation
Prerequisites
- Rust stable toolchain (edition 2021)
pass+ GPG (required for human-owned secret access)- Writable secrets root (default:
.openclaw/secrets)
Quick pass install:
# macOS
# Ubuntu/Debian
Install from crates.io
Install from source
Install Agent skill
Quick Start
# 1) initialize runtime layout
# 2) create an agent secret (1 day TTL)
# 3) inspect state
# 4) read secret (prints raw value)
Common Workflows
Agent secret lifecycle
# create
# read
# revoke
Human request lifecycle
# request access to a human-owned secret
# list pending + metadata
# resolve request (human action)
# or
# check request status by secret name
Verification and cleanup
# verify state and reap expired secrets
Sidecar daemon (systemd-friendly TCP)
# strict startup checks (permissions + loopback bind policy)
# start local daemon on loopback TCP
Set OpenClaw sidecar endpoint to the exact same address and port as --bind.
For OpenClaw process supervisors (for example systemd or qmd), run the same daemon command with restart policy enabled.
systemd (recommended for OpenClaw)
Install unit files from systemd/:
# user-level units
If your gloves binary is not in ~/.cargo/bin/gloves, edit ExecStart and ExecStartPre in the copied unit files.
Commands
| Command | Purpose | Options / Notes |
|---|---|---|
init |
Initialize runtime directories/files | none |
set <name> |
Store agent-owned secret | --generate, --stdin, --value, --ttl <days> |
get <name> |
Retrieve secret value | warns when printing to TTY |
env <name> <var> |
Print redacted env export | outputs export VAR=<REDACTED> |
request <name> --reason <text> |
Create human access request | reason is required |
approve <request_id> |
Approve pending request | request UUID |
deny <request_id> |
Deny pending request | request UUID |
status <name> |
Request status for secret | pending / fulfilled / denied / expired |
list |
List metadata and pending requests | JSON output |
revoke <name> |
Revoke caller-owned secret | removes ciphertext + metadata |
verify |
Reap expired items and verify runtime state | logs expiry events |
daemon |
Run local sidecar daemon | loopback TCP only (--bind, default 127.0.0.1:7788) |
Full CLI implementation: src/cli/mod.rs
Runtime Layout
Default root: .openclaw/secrets
.openclaw/secrets/
store/ # encrypted *.age files
meta/ # per-secret metadata JSON
pending.json # request lifecycle state
audit.jsonl # append-only audit events
default-agent.agekey # generated age identity
default-agent.signing.key # generated Ed25519 signing key
Path model: SecretsPaths
Security Model
- Secret values wrapped in non-
Debugtype:SecretValue - Agent secret encryption and decryption:
src/agent/backend.rs - Human backend via
pass:src/human/backend.rs - Pending request signature verification:
src/human/pending.rs - Restricted file permissions and atomic writes:
src/fs_secure.rs - TTL reaping with audit events:
TtlReaper::reap,AuditLog::log
Agent Memory Exclusions
If another coding agent is installed for this repo, configure memory/indexing excludes:
~/.password-store/**(or$PASSWORD_STORE_DIR/**).openclaw/secrets/**(or any custom--rootdirectory)- Never persist raw
gloves getoutput in memory summaries or notes
Architecture
flowchart LR
CLI["gloves CLI\nsrc/cli/mod.rs"] --> Router["SecretsManager\nsrc/manager.rs"]
Router --> Agent["Agent backend (age)\nsrc/agent/backend.rs"]
Router --> Human["Human backend (pass)\nsrc/human/backend.rs"]
Router --> Meta["Metadata store\nsrc/agent/meta.rs"]
Router --> Pending["Pending request store\nsrc/human/pending.rs"]
Router --> Audit["Audit log\nsrc/audit.rs"]
Router --> Reaper["TTL reaper\nsrc/reaper.rs"]
Request flow
sequenceDiagram
participant AgentCLI as Agent via gloves CLI
participant Manager as SecretsManager
participant Pending as PendingRequestStore
participant Human as Human reviewer
AgentCLI->>Manager: request(secret, reason)
Manager->>Pending: create() with Ed25519 signature
Pending-->>AgentCLI: pending request id
Human->>Manager: approve(request_id) or deny(request_id)
Manager->>Pending: update status
AgentCLI->>Manager: status(secret)
Manager-->>AgentCLI: pending/fulfilled/denied/expired
Development
Release Channels
stablechannel:- Branches:
mainandrelease/* - Tag format:
vX.Y.Z(example:v1.4.0)
- Branches:
betachannel:- Branch:
next - Tag format:
vX.Y.Z-beta.N(example:v1.5.0-beta.1)
- Branch:
alphachannel:- Branch:
canary - Tag format:
vX.Y.Z-alpha.N(example:v1.5.0-alpha.1)
- Branch:
Publishing is tag-driven. The publish workflow validates:
- tag format matches one of the channel patterns
- tag version matches
Cargo.tomlpackage version - tagged commit belongs to an allowed branch for that channel
Release commands and examples: RELEASE.md
CI/CD
.github/workflows/ci.yml: lint + docs.github/workflows/test.yml: full test suite.github/workflows/coverage.yml: coverage thresholds.github/workflows/publish.yml: publish on matching version tags (requiresCARGO_REGISTRY_TOKEN)
License and Changelog
- License:
LICENSE - Changelog:
CHANGELOG.md