chore(soul-guardian): bump version to 0.0.2

This commit is contained in:
David Abutbul
2026-02-06 19:32:20 +02:00
parent c856bb6426
commit 87f80aae94
4 changed files with 291 additions and 80 deletions
+122 -59
View File
@@ -1,7 +1,7 @@
---
name: soul-guardian
version: 0.0.1
description: Drift detection + baseline integrity guard for an agent workspace's auto-loaded prompt/instruction markdown files (SOUL.md, AGENTS.md, etc.), with per-file policies, tamper-evident audit logging, and optional auto-restore.
version: 0.0.2
description: Drift detection + baseline integrity guard for agent workspace files with automatic alerting support
homepage: https://clawsec.prompt.security
metadata: {"openclaw":{"emoji":"👻","category":"security"}}
clawdis:
@@ -10,91 +10,154 @@ clawdis:
bins: [python3]
---
# soul-guardian
# soul-guardian 👻
Use this skill to detect and respond to unexpected edits in the workspace files that the agent auto-loads.
Protects your agent's core files (SOUL.md, AGENTS.md, etc.) from unauthorized changes with automatic detection, restoration, and **user alerting**.
## Installation Options
## Quick Start (3 Steps)
You can get soul-guardian in two ways:
### Step 1: Initialize baselines
```bash
cd ~/.openclaw/workspace
python3 skills/soul-guardian/scripts/soul_guardian.py init --actor setup --note "initial baseline"
```
### Option A: Bundled with ClawSec Suite (Recommended)
### Step 2: Enable monitoring
```bash
python3 skills/soul-guardian/scripts/soul_guardian.py enable-monitoring
```
This shows you how to add monitoring to your workflow.
**If you've installed clawsec-suite, you may already have this!**
### Step 3: Add to HEARTBEAT.md (Recommended)
Soul-guardian is bundled alongside ClawSec Suite to provide file integrity and drift detection capabilities. When you install the suite, if you don't already have soul-guardian installed, it will be deployed from the bundled copy.
Add this to your HEARTBEAT.md for automatic monitoring:
**Advantages:**
- Convenient - no separate download needed
- Standard location - installed to `~/.openclaw/skills/soul-guardian/`
- Preserved - if you already have soul-guardian installed, it won't be overwritten
- Single verification - integrity checked as part of suite package
```markdown
## Soul Guardian Check
- Run `python3 skills/soul-guardian/scripts/soul_guardian.py check --actor heartbeat --output-format alert`
- If any output is produced, relay it to the user immediately as a security alert
```
### Option B: Standalone Installation (This Page)
Install soul-guardian independently without the full suite.
**When to use standalone:**
- You only need file integrity monitoring (not other suite components)
- You want to install before installing the suite
- You prefer explicit control over soul-guardian installation
**Advantages:**
- Lighter weight installation
- Independent from suite
- Direct control over installation process
Continue below for standalone installation instructions.
That's it! Soul Guardian will now:
- ✅ Detect unauthorized changes to protected files
- ✅ Auto-restore SOUL.md and AGENTS.md to approved baseline
- ✅ Alert you when drift is detected and handled
---
## What it protects (default policy)
- **Auto-restore + alert:** `SOUL.md`, `AGENTS.md`
- **Alert-only:** `USER.md`, `TOOLS.md`, `IDENTITY.md`, `HEARTBEAT.md`, `MEMORY.md`
- **Ignored by default:** `memory/*.md` (daily notes)
Policy is stored in the guardian state directory as `policy.json`.
## Quick start (first run)
Recommended: onboard an **external** state dir, then initialize baselines there.
```bash
python3 skills/soul-guardian/scripts/onboard_state_dir.py --agent-id <agentId>
python3 skills/soul-guardian/scripts/soul_guardian.py --state-dir ~/.clawdbot/soul-guardian/<agentId> init --actor sam --note "first baseline"
```
(Full step-by-step + scheduling options are in `README.md`.)
| File | Mode | Action on drift |
|------|------|-----------------|
| SOUL.md | restore | Auto-restore + alert |
| AGENTS.md | restore | Auto-restore + alert |
| USER.md | alert | Alert only |
| TOOLS.md | alert | Alert only |
| IDENTITY.md | alert | Alert only |
| HEARTBEAT.md | alert | Alert only |
| MEMORY.md | alert | Alert only |
| memory/*.md | ignore | Ignored |
## Commands
Run from the agent workspace root:
### Check for drift (with alert output)
```bash
python3 skills/soul-guardian/scripts/soul_guardian.py check --output-format alert
```
- Silent if no drift
- Outputs human-readable alert if drift detected
- Perfect for heartbeat integration
### Watch mode (continuous monitoring)
```bash
python3 skills/soul-guardian/scripts/soul_guardian.py watch --interval 30
```
Runs continuously, checking every 30 seconds.
### Approve intentional changes
```bash
python3 skills/soul-guardian/scripts/soul_guardian.py approve --file SOUL.md --actor user --note "intentional update"
```
### View status
```bash
python3 skills/soul-guardian/scripts/soul_guardian.py status
python3 skills/soul-guardian/scripts/soul_guardian.py check
python3 skills/soul-guardian/scripts/soul_guardian.py check --no-restore
python3 skills/soul-guardian/scripts/soul_guardian.py approve --file SOUL.md
python3 skills/soul-guardian/scripts/soul_guardian.py restore --file SOUL.md
```
### Verify audit log integrity
```bash
python3 skills/soul-guardian/scripts/soul_guardian.py verify-audit
```
### State directory
---
- Default (backward compatible): `memory/soul-guardian/`
- Recommended external override:
## Alert Format
```bash
python3 skills/soul-guardian/scripts/soul_guardian.py --state-dir ~/.clawdbot/soul-guardian/<agentId> check
When drift is detected, the `--output-format alert` produces output like:
```
==================================================
🚨 SOUL GUARDIAN SECURITY ALERT
==================================================
📄 FILE: SOUL.md
Mode: restore
Status: ✅ RESTORED to approved baseline
Expected hash: abc123def456...
Found hash: 789xyz000111...
Diff saved: /path/to/patches/drift.patch
==================================================
Review changes and investigate the source of drift.
If intentional, run: soul_guardian.py approve --file <path>
==================================================
```
## Cron pattern
This output is designed to be relayed directly to the user in TUI/chat.
Keep the existing gateway cron pattern: run `check` every N minutes and notify only when drift is detected.
---
For onboarding/migration to an external state directory, see `README.md` and:
## Security Model
**What it does:**
- Detects filesystem drift vs approved baseline (sha256)
- Produces unified diffs for review
- Maintains tamper-evident audit log with hash chaining
- Refuses to operate on symlinks
- Uses atomic writes for restores
**What it doesn't do:**
- Cannot prove WHO made a change (actor is best-effort metadata)
- Cannot protect if attacker controls both workspace AND state directory
- Is not a substitute for backups
**Recommendation:** Store state directory outside workspace for better resilience.
---
## Demo
Run the full demo flow to see soul-guardian in action:
```bash
python3 skills/soul-guardian/scripts/onboard_state_dir.py
bash skills/soul-guardian/scripts/demo.sh
```
This will:
1. Verify clean state (silent check)
2. Inject malicious content into SOUL.md
3. Run heartbeat check (produces alert)
4. Show SOUL.md was restored
---
## Troubleshooting
**"Not initialized" error:**
Run `init` first to set up baselines.
**Drift keeps happening:**
Check what's modifying your files. Review the audit log and patches.
**Want to approve a change:**
Run `approve --file <path>` after reviewing the change.
View File
+168 -20
View File
@@ -546,7 +546,52 @@ def restore_one(state: GuardianState, relp: str, info: dict[str, Any]) -> dict[s
return {"quarantinePath": str(quarantine_path), **info}
def check_cmd(state: GuardianState, actor: str, note: str, *, no_restore: bool = False) -> int:
def format_alert_human(drifted: list[dict[str, Any]]) -> str:
"""Format drift results as human-readable alert for TUI notification."""
lines = []
lines.append("")
lines.append("=" * 50)
lines.append("🚨 SOUL GUARDIAN SECURITY ALERT")
lines.append("=" * 50)
lines.append("")
for d in drifted:
path = d.get("path", "unknown")
mode = d.get("mode", "unknown")
restored = d.get("restored", False)
error = d.get("error")
if error:
lines.append(f"⚠️ ERROR: {path}")
lines.append(f" {error}")
else:
lines.append(f"📄 FILE: {path}")
lines.append(f" Mode: {mode}")
if restored:
lines.append(f" Status: ✅ RESTORED to approved baseline")
if d.get("quarantinePath"):
lines.append(f" Quarantined: {d.get('quarantinePath')}")
else:
lines.append(f" Status: ⚠️ DRIFT DETECTED (not auto-restored)")
if d.get("approvedSha"):
lines.append(f" Expected hash: {d.get('approvedSha')[:16]}...")
if d.get("currentSha"):
lines.append(f" Found hash: {d.get('currentSha')[:16]}...")
if d.get("patchPath"):
lines.append(f" Diff saved: {d.get('patchPath')}")
lines.append("")
lines.append("=" * 50)
lines.append("Review changes and investigate the source of drift.")
lines.append("If intentional, run: soul_guardian.py approve --file <path>")
lines.append("=" * 50)
lines.append("")
return "\n".join(lines)
def check_cmd(state: GuardianState, actor: str, note: str, *, no_restore: bool = False, output_format: str = "json") -> int:
state.ensure_dirs()
policy = load_policy(state)
baselines = load_baselines(state)
@@ -611,30 +656,112 @@ def check_cmd(state: GuardianState, actor: str, note: str, *, no_restore: bool =
drifted.append(rec)
if not drifted:
# Silent on OK for alert format
if output_format != "alert":
pass # Could print "OK" here if desired
return 0
# Single-line summary suitable for cron parsing.
# Keep it small; details are in audit + patch paths.
summary = {
"event": "SOUL_GUARDIAN_DRIFT",
"count": len(drifted),
"files": [
{
"path": d["path"],
"mode": d.get("mode"),
"restored": d.get("restored"),
"patch": d.get("patchPath"),
"error": d.get("error"),
}
for d in drifted
],
}
print("SOUL_GUARDIAN_DRIFT " + json.dumps(summary, ensure_ascii=False))
# Output based on format
if output_format == "alert":
# Human-readable alert suitable for direct relay to user
print(format_alert_human(drifted))
else:
# JSON format (default) - single-line summary suitable for cron parsing
summary = {
"event": "SOUL_GUARDIAN_DRIFT",
"count": len(drifted),
"files": [
{
"path": d["path"],
"mode": d.get("mode"),
"restored": d.get("restored"),
"patch": d.get("patchPath"),
"error": d.get("error"),
}
for d in drifted
],
}
print("SOUL_GUARDIAN_DRIFT " + json.dumps(summary, ensure_ascii=False))
# Drift occurred (even if restored).
return 2
def watch_cmd(state: GuardianState, actor: str, note: str, *, interval: int = 30) -> int:
"""Continuous watch mode - checks periodically and alerts on drift."""
import time
print(f"👻 Soul Guardian watching... (interval: {interval}s)")
print("Press Ctrl+C to stop.\n")
try:
while True:
result = check_cmd(state, actor, note, output_format="alert")
if result == 0:
# No drift - stay silent
pass
# Sleep before next check
time.sleep(interval)
except KeyboardInterrupt:
print("\n👻 Soul Guardian stopped.")
return 0
def enable_monitoring_cmd(state: GuardianState) -> None:
"""Output instructions and HEARTBEAT.md snippet for easy monitoring setup."""
heartbeat_snippet = """
## Soul Guardian Check
- Run `cd {workspace} && python3 skills/soul-guardian/scripts/soul_guardian.py check --actor heartbeat --output-format alert`
- If any output is produced, relay it to the user immediately as a security alert
- This checks for unauthorized changes to SOUL.md, AGENTS.md, and other protected files
""".format(workspace=WORKSPACE_ROOT)
print("""
╔══════════════════════════════════════════════════════════════╗
║ SOUL GUARDIAN - ENABLE MONITORING ║
╚══════════════════════════════════════════════════════════════╝
To enable automatic drift detection and alerting, you have two options:
────────────────────────────────────────────────────────────────
OPTION 1: Heartbeat Integration (Recommended)
────────────────────────────────────────────────────────────────
Add the following to your HEARTBEAT.md file:
""")
print(heartbeat_snippet)
print("""
────────────────────────────────────────────────────────────────
OPTION 2: Watch Mode (Foreground)
────────────────────────────────────────────────────────────────
Run this in a terminal to continuously monitor:
python3 skills/soul-guardian/scripts/soul_guardian.py watch --interval 30
────────────────────────────────────────────────────────────────
OPTION 3: Manual Check
────────────────────────────────────────────────────────────────
Run a one-time check with human-readable output:
python3 skills/soul-guardian/scripts/soul_guardian.py check --output-format alert
────────────────────────────────────────────────────────────────
The guardian will:
✓ Detect unauthorized changes to protected files
✓ Auto-restore SOUL.md and AGENTS.md to approved baselines
✓ Alert you immediately when drift is detected
✓ Save diffs and quarantine modified files for review
""")
print(f"State directory: {state.state_dir}")
print(f"Workspace: {WORKSPACE_ROOT}")
print()
def approve_cmd(state: GuardianState, actor: str, note: str, *, files: list[str] | None, all_files: bool = False) -> None:
state.ensure_dirs()
policy = load_policy(state)
@@ -796,7 +923,10 @@ def verify_audit_cmd(state: GuardianState) -> None:
def parse_args(argv: list[str]) -> argparse.Namespace:
p = argparse.ArgumentParser()
p = argparse.ArgumentParser(
description="Soul Guardian - Workspace file integrity guard with alerting support.",
epilog="For easy setup, run: soul_guardian.py enable-monitoring"
)
p.add_argument(
"--state-dir",
default=str(DEFAULT_STATE_DIR),
@@ -818,6 +948,8 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
sp_check = sub.add_parser("check", help="Check for drift; restore restore-mode by default.")
add_common(sp_check)
sp_check.add_argument("--no-restore", action="store_true", help="Never restore during check (alert-only run).")
sp_check.add_argument("--output-format", choices=["json", "alert"], default="json",
help="Output format: json (machine-readable) or alert (human-readable for TUI).")
sp_approve = sub.add_parser("approve", help="Approve current contents as baselines.")
add_common(sp_approve)
@@ -830,6 +962,13 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
sp_restore.add_argument("--all", action="store_true", help="Restore all restore-mode targets.")
sub.add_parser("verify-audit", help="Verify audit log hash chain.")
# New commands for easier monitoring setup
sp_watch = sub.add_parser("watch", help="Continuous watch mode - monitors and alerts on drift.")
add_common(sp_watch)
sp_watch.add_argument("--interval", type=int, default=30, help="Check interval in seconds (default: 30).")
sub.add_parser("enable-monitoring", help="Show instructions for enabling automatic monitoring and alerts.")
return p.parse_args(argv)
@@ -846,7 +985,11 @@ def main(argv: list[str]) -> int:
status_cmd(state)
return 0
if args.cmd == "check":
return check_cmd(state, args.actor, args.note, no_restore=bool(getattr(args, "no_restore", False)))
return check_cmd(
state, args.actor, args.note,
no_restore=bool(getattr(args, "no_restore", False)),
output_format=getattr(args, "output_format", "json")
)
if args.cmd == "approve":
approve_cmd(state, args.actor, args.note, files=getattr(args, "files", None), all_files=bool(getattr(args, "all", False)))
return 0
@@ -856,6 +999,11 @@ def main(argv: list[str]) -> int:
if args.cmd == "verify-audit":
verify_audit_cmd(state)
return 0
if args.cmd == "watch":
return watch_cmd(state, args.actor, args.note, interval=getattr(args, "interval", 30))
if args.cmd == "enable-monitoring":
enable_monitoring_cmd(state)
return 0
raise RuntimeError(f"Unknown cmd: {args.cmd}")
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "soul-guardian",
"version": "0.0.1",
"version": "0.0.2",
"description": "Drift detection and baseline integrity guard for agent workspace prompt files. Auto-restore critical files with tamper-evident audit logging.",
"author": "prompt-security",
"license": "MIT",