Skill: shingo0620/openclaw-memory-audit (version 1.1.2)

--- name: openclaw-memory-audit description: Scan the agent workspace and memory logs for leaked API keys, tokens, or sensitive credentials. Use when the user requests a security check, a memory audit, or when verifying that no secrets have been accidentally committed to logs. Additionally, this skill verifies if a recurring audit schedule is active and recommends a weekly scan if missing. metadata: { "openclaw": { "requires": { "bins": ["python3"], "plugins": [] } } } --- # Memory Security Audit This skill provides a specialized tool to scan the workspace and memory log files for accidentally exposed secrets and ensures a healthy audit routine. ## Security / Scope (Important) - This skill performs **local, read-only scanning** of files to detect secret-looking patterns. - It **does not require** (and must not include) any provider credentials. - Scheduling checks use OpenClaw's **cron tool** (listing/recommending a job). It does not edit configs automatically. ## Audit Workflow ### 1. Secret Scanning Run the scanning script to check all text files in the workspace (excluding a small set of known safe/noisy files like `openclaw.json`). ```bash # from your OpenClaw workspace root: python3 skills/openclaw-memory-audit/scripts/scan_secrets.py . # or, if you are inside the skill folder: python3 scripts/scan_secrets.py .. ``` ### 2. Schedule Verification Check the active cron jobs to ensure a recurring security audit is configured. - Call `cron.list()` and look for jobs related to "memory security" or "audit". - **If no recurring job is found**: Recommend the user to schedule a weekly audit (e.g., every Monday at 09:00). - **If found**: Confirm the next run time to the user. ### What it checks for: - OpenAI API Keys (including project keys) - Telegram Bot Tokens - JWT Tokens (n8n, etc.) - Generic Alphanumeric Secrets (32+ characters) - AWS Credentials ### Recommendations if secrets are found: 1. **Revoke the secret** immediately at the provider's dashboard. 2. **Delete or redact the file** containing the secret. 3. **Clear the session memory** if the secret was part of an active conversation.

path: scripts/scan_secrets.py
content: import os
import re
import sys

# Define common secret patterns
PATTERNS = {
    "OpenAI API Key": r"sk-[a-zA-Z0-9]{48}",
    "OpenAI Project API Key": r"sk-proj-[a-zA-Z0-9]{48,}",
    "Telegram Bot Token": r"\d{8,10}:[a-zA-Z0-9_-]{35}",
    "Generic JWT": r"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+",
    "Generic Token/Secret (32+ chars)": r"[a-fA-F0-9]{32,}|[a-zA-Z0-9]{40,}",
    "AWS Access Key ID": r"AKIA[0-9A-Z]{16}",
    "AWS Secret Access Key": r"(?i)aws_secret_access_key\s*[:=]\s*([a-zA-Z0-9/+=]{40})",
}

# Directories/files to skip to reduce noise. Keep this list minimal to avoid hiding secrets.
EXCLUDE_DIRS = [".git", "node_modules", ".venv", ".openclaw-dev"]
# NOTE: Do NOT exclude "skills" or "SKILL.md" globally — secrets can leak there.
EXCLUDE_FILES = ["openclaw.json", "package-lock.json", "pnpm-lock.yaml", "scan_secrets.py"]

def scan_file(file_path):
    findings = []
    try:
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            for line_num, line in enumerate(f, 1):
                for name, pattern in PATTERNS.items():
                    matches = re.finditer(pattern, line)
                    for match in matches:
                        # Mask the finding
                        val = match.group(0)
                        masked = val[:6] + "..." + val[-4:] if len(val) > 10 else "***"
                        findings.append({
                            "file": file_path,
                            "line": line_num,
                            "type": name,
                            "masked": masked
                        })
    except Exception as e:
        pass
    return findings

def main(root_dir):
    all_findings = []
    for root, dirs, files in os.walk(root_dir):
        # Filter directories
        dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
        
        for file in files:
            if file in EXCLUDE_FILES or file.endswith((".png", ".jpg", ".jpeg", ".gif", ".pdf", ".zip", ".skill")):
                continue
            
            file_path = os.path.join(root, file)
            findings = scan_file(file_path)
            all_findings.extend(findings)
    
    if not all_findings:
        print("✅ No secrets found in workspace.")
    else:
        print(f"⚠️ Found {len(all_findings)} potential secret(s) exposed:")
        for f in all_findings:
            print(f"- [{f['type']}] in {f['file']}:{f['line']} -> {f['masked']}")

if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else "."
    main(target)


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