Security & Approval
Strong hands, firm limits.
MateClaw gives agents real capability — shell access, file writes, browser automation, delegation to other agents, remote tools over MCP. That's the "strong hands" half. This page is about the other half: the limits that keep strong hands from doing stupid things.
- JWT auth — who you are
- Tool Guard (rule-based) — what each agent is allowed to do
- Approval workflow — when a human needs to decide before execution
- File Guard — what the filesystem looks like to an agent
- Workspace isolation — what each team can see
- Audit log — what everybody did, in order, forever
If you're running MateClaw in production, read this page top to bottom.
Agentic, but not autonomous
Every IT department and CISO in 2025–2026 has the same question before buying AI:
"What if the agent goes off the rails and deletes the wrong thing?"
Anyone who tells you "AI won't go off the rails" is lying. MateClaw's answer is different — the agent asks you first when it matters.
When the agent wants to delete a file, send an email, run a write-side SQL, or hit a paid API — any tool call matched by a Tool Guard rule pauses mid-turn. An approval notification is pushed to your IM (Feishu / DingTalk / Slack / email). You tap approve, the agent resumes from where it stopped. Every action lands in mate_tool_guard_audit_log — append-only, retained as long as you want, CSV-exportable.
Agentic — it acts. Not autonomous — it doesn't act on its own initiative for the things that matter.
That's the line between "let AI do work for you" and "let AI make decisions for you." MateClaw stays on the left side of that line — which is also the side your CISO doesn't immediately say no to.
JWT authentication
How it works
- The user posts credentials to
/api/v1/auth/login - The server validates and returns a JWT
- Every subsequent request includes the token in the
Authorizationheader - The server validates the token on each request
Logging in
curl -X POST http://localhost:18088/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin123"}'Response:
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9...",
"tokenType": "Bearer",
"expiresIn": 86400
}
}Password change
Users can change their own password from the profile settings dialog. Admins can reset any member's password from member management.
Sliding window renewal
MateClaw does sliding-window token renewal. When a token's remaining lifetime falls below the configurable renewal-threshold (default 2 hours / 7200000ms), the server issues a new token in the X-New-Token response header. The frontend picks it up and replaces the stored token transparently. Active users never get kicked out; idle sessions still expire on time.
Configuration
mateclaw:
auth:
jwt:
secret: your-secret-key-must-be-at-least-32-characters-long
expiration: 86400000 # 24h in milliseconds
sliding-window: trueWARNING
Change the default JWT secret in production. At least 32 characters. Set via env var (JWT_SECRET=...), never commit.
Error codes
| Code | Meaning | Response |
|---|---|---|
| 401 | Token missing, expired, or invalid | {"code":401,"msg":"Token expired or invalid","data":null} |
| 403 | Valid token but insufficient permissions | {"code":403,"msg":"Forbidden","data":null} |
Frontend handles both uniformly — redirect to login, clear stored tokens.
Default credentials
MateClaw ships with admin / admin123. Change this immediately in any deployment other than your laptop.
Spring Security config
- Stateless sessions — no server-side session; all state in the JWT
- Public API endpoints —
GET /api/v1/settings/language,/api/v1/auth/login,/api/v1/chat/stream,/api/v1/chat/*/stop,/api/v1/agents/*/chat/stream,/api/v1/setup/**,/api/v1/channels/webhook/**,/api/v1/channels/webchat/**,/api/v1/talk/ws,/api/v1/files/generated/** - Protected endpoints — everything else under
/api/** - CSRF disabled — not needed for stateless JWT
Tool Guard — rule-based permission engine
Tool Guard is how MateClaw decides what a tool call is allowed to do. It's not a flat dangerous-tools list. It's a rule engine. Each rule specifies: for this tool, optionally matching these arguments, in this workspace, do X — where X is allow, deny, or require_approval.
The three tables
| Table | Purpose |
|---|---|
mate_tool_guard_config | Global config — enabled, default policy, approval timeout, notification channels |
mate_tool_guard_rule | Individual rules — tool pattern, optional arg regex, workspace scope, action, priority |
mate_tool_guard_audit_log | Every guarded call gets an entry — tool, args, rule matched, decision, user, timestamp |
How a rule is evaluated
Tool call arrives
│
▼
Load rules for this workspace + global rules, sorted by priority
│
▼
For each rule in priority order:
┌─ Does the tool name match the pattern?
│ └─ No → next rule
├─ Does the arg pattern match (if any)?
│ └─ No → next rule
└─ Yes on both → apply this rule's action and stop
│
▼
No rules matched → apply default policy
│
▼
Action: allow / deny / require_approval
│
▼
Write audit log entry
│
▼
Execute / reject / suspend for approvalRules with higher priority run first. First matching rule wins. A rule can be scoped to a specific workspace or global.
Example rules
Rule 1 (priority 100): ShellExecuteTool, arg matches "^(ls|cat|grep|find)\\s" → allow
Rule 2 (priority 50): ShellExecuteTool → require_approval
Rule 3 (priority 50): WriteFileTool, arg.path starts with "/tmp" → allow
Rule 4 (priority 40): WriteFileTool → require_approval
Rule 5 (priority 30): * → allow (default)Read-only shell commands execute immediately. Anything else needs approval. File writes under /tmp are free; elsewhere they need approval. Everything else runs.
Managing rules
Settings → Security & Approval → Tool Guard Rules: list, create, edit, reorder, disable. Or via config:
mateclaw:
tool:
guard:
enabled: true
default-policy: require_approval
rules:
- tool: ShellExecuteTool
arg-pattern: "^(ls|cat|grep|find)\\s"
action: allow
priority: 100
- tool: ShellExecuteTool
action: require_approval
priority: 50
- tool: WriteFileTool
arg-pattern: "^/tmp/"
action: allow
priority: 50
- tool: WriteFileTool
action: require_approval
priority: 40Or via API:
curl -X POST http://localhost:18088/api/v1/security/guard/rules \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"tool": "ShellExecuteTool",
"argPattern": "^(ls|cat|grep|find)\\s",
"action": "allow",
"priority": 100
}'Credential-rule toggles (1.4.0)
Credential rules now support per-rule control — each rule can be enabled/disabled individually, each rule carries its own decision (allow / deny / require_approval), and the entire guard rule set can be exported and imported as JSON for migrating between deployments or version-controlling your policy.
Dangerous pattern detection
In addition to user-defined rules, MateClaw's shell tool has built-in detection for patterns that are dangerous no matter what. find -delete, rm -rf /, piped downloads through bash, and similar patterns trigger elevated approval even if a rule would otherwise allow them.
Approval workflow — human in the loop
When a rule evaluates to require_approval, MateClaw doesn't fail the call. It suspends the agent mid-turn, creates a pending approval, surfaces it to the user, and resumes exactly where it left off once the user decides.
From 1.3.0: workflows ride the same approval rail
The v1.3.0 workflow await_approval step suspends the entire workflow run on the same mate_tool_approval table — persisted across restarts. Approval requests fan out to the approver's channel (Feishu / DingTalk / Slack / WeCom); once resolved, the workflow runtime auto-resumes the next step. One audit log, one notification pipeline, one "pause / resume" semantic — covering both agent tool calls and workflow steps.
How it flows
Agent calls tool
│
▼
Tool Guard: require_approval
│
▼
Create mate_tool_approval row (status=pending)
│
▼
Set AWAITING_APPROVAL=true in graph state
│
▼
Emit approval_required SSE event
│
▼
Graph terminates cleanly
│
▼
Frontend shows approval card
│
▼
User clicks Approve or Reject
│
▼
POST /api/v1/chat/stream with /approve or /deny
│
├─ Approved → reload agent, replay tool call, continue reasoning
└─ Rejected → send rejection as observation, continue reasoningThe "replay" mechanism is important. When the agent resumes, it doesn't re-reason from scratch — it skips straight to the approved tool call, executes it, and continues from the observation. No duplicate LLM calls, no wasted tokens.
The current web path has no write-style POST /api/v1/approvals/{id}/resolve endpoint. Approval and denial use the same SSE channel as normal chat so replay, persistence, and cancellation all stay on one lifecycle.
The mate_tool_approval table
| Column | Purpose |
|---|---|
id | Primary key |
agent_id | Which agent is waiting |
conversation_id | Which conversation is suspended |
tool_name | The tool being called |
tool_args | JSON of the actual arguments |
rule_id | Which rule triggered the approval |
status | pending / approved / rejected / expired |
requested_at | When the approval was created |
resolved_at | When the user decided |
resolved_by | Who decided |
notes | Optional user notes on the decision |
Placeholder substitution
Sometimes the agent's tool arguments contain placeholders — a computed file path, a templated command. The approval workflow resolves placeholders before showing the dialog, so users see the actual values they're approving. Approval returns the resolved values too, so what the agent executes is exactly what the user saw.
Timeouts
Pending approvals expire after a configurable timeout (default: 10 minutes). Expired approvals become rejected, and the agent treats expiry the same as user rejection.
Notifications
MateClaw can notify through channel/notification/ adapters — email, in-app alert, DingTalk/Feishu push. Configure in Settings → Security & Approval → Notifications.
Current API surface
# Hydrate pending approvals after a page refresh
curl http://localhost:18088/api/v1/chat/{conversationId}/pending-approvals \
-H "Authorization: Bearer <token>"
# Approve in the waiting conversation
curl -N -X POST http://localhost:18088/api/v1/chat/stream \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"agentId":"1","conversationId":"conv-abc123","message":"/approve"}'
# Reject in the waiting conversation
curl -N -X POST http://localhost:18088/api/v1/chat/stream \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"agentId":"1","conversationId":"conv-abc123","message":"/deny"}'
# Manage auto-approval grants
curl http://localhost:18088/api/v1/approval/grants \
-H "Authorization: Bearer <token>"File Guard
File Guard is filesystem-level access control. It sits underneath any tool or skill that reads or writes files, and decides what paths are in-bounds.
Evaluation pipeline
File access request
│
▼
Path normalization (resolve .., symlinks, relative paths)
│
▼
Allowlist check: is the path inside an allowed directory?
│
▼
Denylist check: is the path inside a denied directory?
│
▼
Symlink check: does following the path escape the sandbox?
│
▼
Allow / DenyRules built in
| Rule | Description |
|---|---|
| Workspace isolation | Default access restricted to the workspace directory |
| System path denial | /etc, /usr, /bin, /boot, etc. blocked |
| Sensitive file protection | .ssh, .config, .env blocked |
| Path traversal prevention | ../ attacks detected and blocked |
| Symlink check | Symlink targets resolved and re-validated |
Configuration
mateclaw:
security:
file-guard:
enabled: true
allowed-paths:
- "${user.dir}/workspace"
- "${java.io.tmpdir}/mateclaw"
denied-paths:
- "/etc"
- "/usr"
- "${user.home}/.ssh"
- "${user.home}/.config"
- "${user.home}/.env"Visual editor on Settings → Security & Approval → File Guard.
Workspace isolation
Workspaces are how MateClaw keeps multiple teams' data separate. Every agent, skill, wiki, conversation, and memory file belongs to exactly one workspace.
Security primitives that follow workspace boundaries
- File Guard — path allowlists default to
workspace/{workspaceId}/... - Tool Guard rules — can be scoped to a specific workspace
- Wiki knowledge bases — owned by a workspace, readable only by members
- Memory files — every agent's memory is under its workspace's directory
- Channels — each channel belongs to a workspace
Roles (four-tier RBAC)
Capabilities are additive — a higher role inherits everything below it.
| Role | Capabilities (added on top of the tier below) |
|---|---|
| Viewer | chat, view:wiki. Read-only. So that chat works, a Viewer can also read the active model and read an employee's workspace files. |
| Member | Viewer + view:memory, view:dashboard, manage:wiki, manage:agents |
| Admin | Member + manage:skills, manage:channels, manage:models, manage:security, manage:settings |
| Owner | Same as Admin, plus owner-only: delete the workspace, transfer ownership |
The backend is the single source of truth for capabilities — it holds a RoleCapabilities mapping, and the frontend never derives them locally. After a workspace switch, or on a capability-related 403, the frontend calls GET /api/v1/workspaces/{id}/access, which returns memberRole, isGlobalAdmin, effectiveRole, and capabilities.
Global admin vs workspace role: mate_user.role='admin' is the system-wide global admin — it manages users, creates workspaces, and spans all workspaces with owner-equivalent power even where it isn't a member; mate_workspace_member.role is per-workspace. System-level endpoints (models / providers / OAuth / datasources, user management, workspace creation) require a global admin (@RequireGlobalAdmin); workspace-scoped endpoints (skills / tools / plugins) require a workspace role — reads need Member, writes need Admin.
Full details in Workspaces.
What isolation does NOT cover
- Shared global config — JWT secret, model provider keys, MCP server definitions are global
- Audit logs — all workspaces' security events are in the same audit log; only admins with audit access read across workspaces
Audit log
Every security-relevant action is recorded in mate_audit_event. Append-only — you can't modify an entry, and rows are retained for the configured window (default 90 days).
What gets logged
| Event type | Captured data |
|---|---|
| Tool calls | Tool name, args, result summary, duration, agent, workspace |
| Tool Guard decisions | Rule matched, action taken, rule ID |
| Approvals | Who approved/rejected, when, notes |
| File Guard decisions | Path, allow/deny, reason |
| Skill executions | Skill name, parameters, agent |
| Login events | User, IP, success/failure |
| Configuration changes | Old and new values for security-relevant settings |
Entry schema
timestamp When it happened
user_id Who did it (system for automated events)
action What they did
resource What it was done to
details JSON blob with the specifics
result success / failure / denied
ip_address Source IP when applicable
workspace_id Which workspace this belongs toQuerying
Settings → Security & Approval → Audit Log: filterable view by time range, event type, user, workspace, result. Export to CSV.
Via API:
curl "http://localhost:18088/api/v1/audit/events?from=2026-04-01&to=2026-04-11&action=tool_call" \
-H "Authorization: Bearer <token>"Skill security scanning
Custom skills are scanned for dangerous patterns before they become active:
| Check | What it looks for |
|---|---|
| Prompt injection | Attempts to override system prompts, hidden instructions |
| Dangerous tool references | Tools not in the allowlist, or tools requiring approval without declaration |
| External URL references | Links to untrusted external resources |
| Script injection | Embedded scripts or code execution attempts |
Severity levels
| Level | Action |
|---|---|
CRITICAL | Install blocked; must be fixed |
HIGH | Warning + admin must confirm |
MEDIUM | Warning displayed; install allowed |
LOW | Logged only |
INFO | Logged only |
Scan reports live in Settings → Security & Approval → Skill Scans.
API key protection
- API keys encrypted at rest in the database
- Keys masked (
sk-****abcd) in every API response — never returned in full after creation - MCP server
env_jsonandheaders_jsonvalues sanitized the same way - Environment variable references (
${VAR}) in MCP config resolve at runtime from the process environment
Network security
Production recommendations
| Recommendation | Details |
|---|---|
| HTTPS | Reverse proxy with TLS (Nginx or Caddy) |
| Disable H2 console | spring.h2.console.enabled=false in production |
| Firewall | Only expose the public port |
| Rate limiting | Configure at the reverse proxy level |
| MySQL, not H2 | Use a dedicated MySQL 8 instance for production |
Nginx reverse proxy example
server {
listen 443 ssl;
server_name mateclaw.example.com;
ssl_certificate /etc/ssl/certs/mateclaw.pem;
ssl_certificate_key /etc/ssl/private/mateclaw.key;
location / {
proxy_pass http://localhost:18080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE support
proxy_buffering off;
proxy_read_timeout 86400s;
}
}Security best practices
- Change the default password. Right now. On every deployment.
- Set a real JWT secret. At least 32 characters, via environment variable, never committed.
- Least privilege. Only enable the tools agents actually need.
- Default to
require_approval. Flip the Tool Guard default policy, then addallowrules for safe cases. Newly added tools default to safe. - Configure File Guard. Lock down allowed/denied paths before any agent touches the filesystem in anger.
- Review audit logs regularly. Set a recurring reminder. Look for anomalies.
- Watch your skill scans. CRITICAL findings shouldn't be bypassed lightly.
- Isolate networks. Ollama, H2 console, internal MCP servers — none should be public.
- Don't skip approvals in production. Auto-approve rules should be narrow and specific.
allow *is a crisis waiting to happen.
Security configuration reference
mateclaw:
auth:
jwt:
secret: ${JWT_SECRET:your-secret-key-at-least-32-chars}
expiration: 86400
sliding-window-ratio: 0.5
tool:
guard:
enabled: true
default-policy: require_approval
approval-timeout-seconds: 600
notifications:
email-enabled: false
dingtalk-enabled: false
security:
file-guard:
enabled: true
allowed-paths:
- "${user.dir}/workspace"
denied-paths:
- "/etc"
- "${user.home}/.ssh"
audit-log:
enabled: true
retention-days: 90
skill:
security-scan:
enabled: true
block-critical: trueNext
- Tools — tool details and Tool Guard rule patterns
- Skills — skill security scanning details
- Workspaces — workspace isolation primitives
- Agents — how approval pauses and resumes an agent turn
- Configuration — full configuration reference
