Credential Manager STATUS: MANDATORY SECURITY FOUNDATION Consolidate scattered API keys and credentials into a secure, centralized file. ⚠️ This Is Not Optional Centralized credential management is a core requirement for OpenClaw security. If your credentials are scattered across multiple files, stop and consolidate them now . THE RULE: All credentials MUST be in ONLY. No workspace, no skills, no scripts directories. See: - CORE-PRINCIPLE.md - Why this is non-negotiable - CONSOLIDATION-RULE.md - The single source principle The Foundation Every OpenClaw deployment MUST have: This is your singl…

) or match.startswith('process.env'):\n continue\n findings.append({\n \"file\": str(fpath),\n \"line\": line_num,\n \"pattern\": desc,\n \"preview\": match[:20] + '...' if len(match) > 20 else match,\n })\n except (PermissionError, UnicodeDecodeError):\n continue\n\n return findings\n\n\ndef scan_locations(custom_paths: List[str] = None) -> List[Dict]:\n \"\"\"Scan all common credential locations.\"\"\"\n results = []\n home = Path.home()\n\n patterns = CREDENTIAL_PATTERNS.copy()\n if custom_paths:\n patterns.extend(custom_paths)\n\n # Expand patterns and check files\n checked = set()\n for pattern in patterns:\n expanded = home.glob(pattern.replace('~/', ''))\n for path in expanded:\n # Follow symlinks for existence check but report them\n if not (path.is_file() or path.is_symlink()) or str(path) in checked:\n continue\n checked.add(str(path))\n\n if path.suffix == '.json':\n result = scan_json_file(path)\n elif '.env' in path.name:\n result = scan_env_file(path)\n else:\n continue\n\n if result.get('likely_credentials'):\n results.append(result)\n\n # Check for existing .env\n env_path = home / '.openclaw' / '.env'\n if env_path.exists() and str(env_path) not in checked:\n results.append(scan_env_file(env_path))\n\n return results\n\n\ndef main():\n parser = argparse.ArgumentParser(description='Scan for credential files')\n parser.add_argument('--paths', nargs='+', help='Additional paths to scan')\n parser.add_argument('--deep', action='store_true',\n help='Deep scan source files for hardcoded secrets')\n parser.add_argument('--format', choices=['text', 'json'], default='text',\n help='Output format')\n args = parser.parse_args()\n\n results = scan_locations(args.paths)\n\n if args.format == 'json':\n output = {\"files\": results}\n if args.deep:\n output[\"deep_scan\"] = deep_scan()\n print(json.dumps(output, indent=2))\n else:\n print(f\"\\n🔍 Found {len(results)} credential file(s):\\n\")\n for r in results:\n status = \"✅\" if r.get('mode') == '600' else \"⚠️\"\n print(f\"{status} {r['path']}\")\n print(f\" Type: {r['type']}\")\n if r.get('symlink_target'):\n ok = \"✅\" if r.get('symlink_ok', True) else \"⚠️\"\n print(f\" {ok} Symlink → {r['symlink_target']}\")\n if 'keys' in r:\n print(f\" Keys: {', '.join(r['keys'][:5])}\")\n if len(r['keys']) > 5:\n print(f\" (+{len(r['keys']) - 5} more)\")\n print(f\" Mode: {r.get('mode', 'unknown')}\")\n if r.get('mode') != '600':\n print(f\" ⚠️ Should be 600 for security\")\n print()\n\n if args.deep:\n findings = deep_scan()\n if findings:\n print(f\"\\n🔎 Deep scan: {len(findings)} potential hardcoded secret(s):\\n\")\n for f in findings[:20]:\n print(f\" ⚠️ {f['file']}:{f['line']}\")\n print(f\" Pattern: {f['pattern']}\")\n print(f\" Preview: {f['preview']}\")\n print()\n if len(findings) > 20:\n print(f\" ... +{len(findings) - 20} more findings\")\n else:\n print(f\"\\n🔎 Deep scan: No hardcoded secrets found ✅\")\n\n print(f\"\\n📊 Summary:\")\n print(f\" Total files: {len(results)}\")\n print(f\" Insecure permissions: {sum(1 for r in results if r.get('mode') != '600')}\")\n symlinks = [r for r in results if r.get('symlink_target')]\n if symlinks:\n print(f\" Symlinks: {len(symlinks)}\")\n print(f\"\\n💡 Next: Run ./scripts/consolidate.py to merge into .env\\n\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":9579,"content_sha256":"0207e5efad8bc9df319662824d71bbf5b475a04adefd0ddbced7726e7d99a7a4"},{"filename":"scripts/setup-gpg.sh","content":"#!/usr/bin/env bash\nset -euo pipefail\n\n# GPG setup for OpenClaw credential encryption\n# Usage: ./scripts/setup-gpg.sh [--cache-hours N]\n\nCACHE_HOURS=8\n\n# Parse args\nwhile [[ $# -gt 0 ]]; do\n case $1 in\n --cache-hours)\n CACHE_HOURS=\"$2\"\n shift 2\n ;;\n --help|-h)\n echo \"Usage: $0 [--cache-hours N]\"\n echo \"\"\n echo \"Sets up GPG for OpenClaw credential encryption.\"\n echo \"\"\n echo \"Options:\"\n echo \" --cache-hours N GPG agent cache timeout (default: 8)\"\n echo \" --help, -h Show this help\"\n exit 0\n ;;\n *)\n echo \"Unknown option: $1\"\n exit 1\n ;;\n esac\ndone\n\nCACHE_SECONDS=$((CACHE_HOURS * 3600))\n\necho \"\"\necho \"🔐 OpenClaw GPG Setup\"\necho \"=====================\"\necho \"\"\n\n# Step 1: Check if GPG is installed\necho \"📋 Step 1: Checking GPG installation...\"\nif ! command -v gpg &>/dev/null; then\n echo \" ❌ GPG is not installed\"\n echo \"\"\n echo \" Install with:\"\n echo \" Ubuntu/Debian: sudo apt install gnupg\"\n echo \" macOS: brew install gnupg\"\n echo \" Fedora: sudo dnf install gnupg2\"\n exit 1\nfi\n\nGPG_VERSION=$(gpg --version | head -1)\necho \" ✅ $GPG_VERSION\"\n\n# Step 2: Configure gpg-agent\necho \"\"\necho \"📋 Step 2: Configuring GPG agent (cache: ${CACHE_HOURS}h)...\"\n\nGPG_DIR=\"$HOME/.gnupg\"\nmkdir -p \"$GPG_DIR\"\nchmod 700 \"$GPG_DIR\"\n\nAGENT_CONF=\"$GPG_DIR/gpg-agent.conf\"\n\n# Backup existing config\nif [[ -f \"$AGENT_CONF\" ]]; then\n cp \"$AGENT_CONF\" \"${AGENT_CONF}.bak\"\n echo \" 📦 Backed up existing config\"\nfi\n\n# Write agent config\ncat > \"$AGENT_CONF\" \u003c\u003c EOF\n# OpenClaw GPG Agent Configuration\n# Cache passphrase for ${CACHE_HOURS} hours\ndefault-cache-ttl ${CACHE_SECONDS}\nmax-cache-ttl ${CACHE_SECONDS}\n\n# Allow loopback pinentry (for headless/script usage)\nallow-loopback-pinentry\nEOF\n\nchmod 600 \"$AGENT_CONF\"\n\n# Configure GPG to use loopback pinentry\nGPG_CONF=\"$GPG_DIR/gpg.conf\"\nif ! grep -q \"pinentry-mode loopback\" \"$GPG_CONF\" 2>/dev/null; then\n echo \"pinentry-mode loopback\" >> \"$GPG_CONF\"\nfi\n\necho \" ✅ Agent configured (cache: ${CACHE_HOURS}h)\"\n\n# Reload agent\ngpgconf --kill gpg-agent 2>/dev/null || true\necho \" ✅ Agent reloaded\"\n\n# Step 3: Test encrypt/decrypt cycle\necho \"\"\necho \"📋 Step 3: Testing encrypt/decrypt cycle...\"\n\nTEST_FILE=$(mktemp)\nTEST_ENC=\"${TEST_FILE}.gpg\"\nTEST_DEC=\"${TEST_FILE}.dec\"\nTEST_DATA=\"openclaw-gpg-test-$(date +%s)\"\n\necho \"$TEST_DATA\" > \"$TEST_FILE\"\n\n# Try encryption\nif gpg -c --batch --yes --cipher-algo AES256 --passphrase \"\" -o \"$TEST_ENC\" \"$TEST_FILE\" 2>/dev/null; then\n # Try decryption\n if gpg -d --batch --quiet --passphrase \"\" \"$TEST_ENC\" > \"$TEST_DEC\" 2>/dev/null; then\n DECRYPTED=$(cat \"$TEST_DEC\")\n if [[ \"$DECRYPTED\" == \"$TEST_DATA\" ]]; then\n echo \" ✅ Encrypt/decrypt test passed\"\n else\n echo \" ⚠️ Decrypt returned different data\"\n fi\n else\n echo \" ⚠️ Decryption test failed (passphrase may be needed)\"\n echo \" 💡 This is normal — you'll set a passphrase when encrypting secrets\"\n fi\nelse\n echo \" ⚠️ Encryption test failed\"\n echo \" 💡 GPG may need additional configuration\"\nfi\n\n# Cleanup test files\nrm -f \"$TEST_FILE\" \"$TEST_ENC\" \"$TEST_DEC\"\n\n# Step 4: Summary\necho \"\"\necho \"✅ GPG setup complete!\"\necho \"\"\necho \" 📁 GPG home: $GPG_DIR\"\necho \" ⏱️ Cache TTL: ${CACHE_HOURS} hours\"\necho \" 🔐 Cipher: AES256\"\necho \"\"\necho \"💡 Next steps:\"\necho \" # Encrypt high-value keys:\"\necho \" ./scripts/encrypt.py --keys MAIN_WALLET_PRIVATE_KEY,CUSTODY_PRIVATE_KEY\"\necho \"\"\necho \" # List encrypted keys:\"\necho \" ./scripts/encrypt.py --list\"\necho \"\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3804,"content_sha256":"4e59b3974afac661fbebeb4d46bf37e15a522fdb40ce39b2f55bf489d2e58966"},{"filename":"scripts/validate.py","content":"#!/usr/bin/env python3\n\"\"\"\nValidate .env file security, format, and credential strength.\nChecks permissions, entropy, private key exposure, backup security.\n\"\"\"\n\nimport argparse\nimport math\nimport os\nimport re\nfrom pathlib import Path\nfrom typing import Dict, List\n\n\ndef calc_entropy(s: str) -> float:\n \"\"\"Calculate Shannon entropy of a string in bits per character.\"\"\"\n if not s:\n return 0.0\n freq = {}\n for c in s:\n freq[c] = freq.get(c, 0) + 1\n length = len(s)\n return -sum((count / length) * math.log2(count / length) for count in freq.values())\n\n\ndef check_permissions(env_file: Path) -> Dict:\n \"\"\"Check file permissions.\"\"\"\n if not env_file.exists():\n return {'status': 'missing', 'message': 'File does not exist'}\n\n mode = oct(env_file.stat().st_mode)[-3:]\n\n if mode == '600':\n return {'status': 'ok', 'mode': mode}\n else:\n return {\n 'status': 'insecure',\n 'mode': mode,\n 'message': f'Permissions {mode} are too permissive (should be 600)'\n }\n\n\ndef check_gitignore(openclaw_dir: Path) -> Dict:\n \"\"\"Check if .env is in .gitignore.\"\"\"\n gitignore = openclaw_dir / '.gitignore'\n\n if not gitignore.exists():\n return {'status': 'missing', 'message': '.gitignore does not exist'}\n\n content = gitignore.read_text()\n if '.env' in content or '*.env' in content:\n return {'status': 'ok'}\n else:\n return {'status': 'unprotected', 'message': '.env not in .gitignore'}\n\n\ndef check_format(env_file: Path) -> Dict:\n \"\"\"Check .env file format.\"\"\"\n issues = []\n keys = set()\n duplicates = []\n\n with open(env_file) as f:\n for line_num, line in enumerate(f, 1):\n line = line.strip()\n\n # Skip comments and empty lines\n if not line or line.startswith('#'):\n continue\n\n # Check format\n if '=' not in line:\n issues.append(f\"Line {line_num}: Missing '=' separator\")\n continue\n\n key, value = line.split('=', 1)\n key = key.strip()\n\n # Check key format\n if not re.match(r'^[A-Z0-9_]+

Credential Manager STATUS: MANDATORY SECURITY FOUNDATION Consolidate scattered API keys and credentials into a secure, centralized file. ⚠️ This Is Not Optional Centralized credential management is a core requirement for OpenClaw security. If your credentials are scattered across multiple files, stop and consolidate them now . THE RULE: All credentials MUST be in ONLY. No workspace, no skills, no scripts directories. See: - CORE-PRINCIPLE.md - Why this is non-negotiable - CONSOLIDATION-RULE.md - The single source principle The Foundation Every OpenClaw deployment MUST have: This is your singl…

, key):\n issues.append(f\"Line {line_num}: Invalid key format '{key}'\")\n\n # Check for duplicates\n if key in keys:\n duplicates.append(key)\n keys.add(key)\n\n # Values with spaces that aren't quoted — warn\n if ' ' in value and not (value.startswith('\"') or value.startswith(\"'\")):\n # Allow JSON-like values (starts with { or [)\n if not (value.startswith('{') or value.startswith('[')):\n issues.append(f\"Line {line_num}: Value with spaces should be quoted (key: {key})\")\n\n if issues or duplicates:\n return {\n 'status': 'issues',\n 'issues': issues,\n 'duplicates': duplicates,\n 'keys_count': len(keys)\n }\n else:\n return {\n 'status': 'ok',\n 'keys_count': len(keys)\n }\n\n\ndef check_security(env_file: Path) -> Dict:\n \"\"\"Check for security issues including entropy, private keys, mnemonics.\"\"\"\n warnings = []\n\n with open(env_file) as f:\n for line_num, line in enumerate(f, 1):\n line = line.strip()\n if not line or line.startswith('#') or '=' not in line:\n continue\n\n key, value = line.split('=', 1)\n key = key.strip()\n value = value.strip().strip('\"').strip(\"'\")\n\n # Skip GPG-encrypted placeholders\n if value.startswith('GPG:'):\n continue\n\n # Check for private key patterns in values (not in ADDRESS/HASH keys)\n key_upper = key.upper()\n if re.match(r'^0x[a-fA-F0-9]{64}

Credential Manager STATUS: MANDATORY SECURITY FOUNDATION Consolidate scattered API keys and credentials into a secure, centralized file. ⚠️ This Is Not Optional Centralized credential management is a core requirement for OpenClaw security. If your credentials are scattered across multiple files, stop and consolidate them now . THE RULE: All credentials MUST be in ONLY. No workspace, no skills, no scripts directories. See: - CORE-PRINCIPLE.md - Why this is non-negotiable - CONSOLIDATION-RULE.md - The single source principle The Foundation Every OpenClaw deployment MUST have: This is your singl…

, value):\n if not any(x in key_upper for x in ['ADDRESS', 'HASH', 'TX', 'CONTRACT']):\n warnings.append(\n f\"Line {line_num}: {key} looks like a private key (0x + 64 hex). \"\n f\"Consider GPG encryption: ./scripts/encrypt.py --keys {key}\"\n )\n\n # Check for mnemonic patterns (12 or 24 words)\n words = value.split()\n if len(words) in (12, 24) and all(w.isalpha() and w.islower() for w in words):\n warnings.append(\n f\"Line {line_num}: {key} looks like a mnemonic seed phrase ({len(words)} words). \"\n f\"Consider GPG encryption: ./scripts/encrypt.py --keys {key}\"\n )\n\n # Entropy check for keys that should be secret\n secret_indicators = ['SECRET', 'PRIVATE_KEY', 'MNEMONIC', 'PASSWORD', 'PASSPHRASE']\n if any(ind in key_upper for ind in secret_indicators):\n if value and len(value) > 4:\n entropy = calc_entropy(value)\n if entropy \u003c 3.0:\n warnings.append(\n f\"Line {line_num}: {key} has low entropy ({entropy:.1f} bits/char). \"\n f\"May be a weak or placeholder value.\"\n )\n\n # Check for common weak values\n weak_values = ['password', 'password123', 'test', 'changeme', 'admin', 'default',\n 'your_value_here', 'xxx', 'placeholder']\n if value.lower() in weak_values:\n warnings.append(f\"Line {line_num}: {key} has a weak/placeholder value\")\n\n return {\n 'status': 'ok' if not warnings else 'warnings',\n 'warnings': warnings\n }\n\n\ndef check_backups(openclaw_dir: Path) -> Dict:\n \"\"\"Check backup file and directory permissions.\"\"\"\n issues = []\n backup_dir = openclaw_dir / 'backups'\n\n if not backup_dir.exists():\n return {'status': 'ok', 'message': 'No backups directory'}\n\n # Check backup root dir\n mode = oct(backup_dir.stat().st_mode)[-3:]\n if mode != '700':\n issues.append(f\"{backup_dir}: directory mode {mode} (should be 700)\")\n\n # Check each backup subdirectory and files\n for sub in backup_dir.iterdir():\n if sub.is_dir():\n sub_mode = oct(sub.stat().st_mode)[-3:]\n if sub_mode != '700':\n issues.append(f\"{sub}: directory mode {sub_mode} (should be 700)\")\n\n for f in sub.iterdir():\n if f.is_file():\n f_mode = oct(f.stat().st_mode)[-3:]\n if f_mode != '600':\n issues.append(f\"{f.name}: file mode {f_mode} (should be 600)\")\n\n return {\n 'status': 'ok' if not issues else 'issues',\n 'issues': issues\n }\n\n\ndef fix_permissions(env_file: Path):\n \"\"\"Fix .env file permissions.\"\"\"\n os.chmod(env_file, 0o600)\n print(f\" 🔧 Fixed permissions: 600\")\n\n\ndef fix_gitignore(openclaw_dir: Path):\n \"\"\"Add .env to .gitignore.\"\"\"\n gitignore = openclaw_dir / '.gitignore'\n entries = ['.env', '.env.secrets.gpg', '.env.meta']\n\n if not gitignore.exists():\n with open(gitignore, 'w') as f:\n f.write(\"# Credentials\\n\")\n for e in entries:\n f.write(f\"{e}\\n\")\n else:\n content = gitignore.read_text()\n missing = [e for e in entries if e not in content]\n if missing:\n with open(gitignore, 'a') as f:\n f.write(\"\\n# Credentials\\n\")\n for e in missing:\n f.write(f\"{e}\\n\")\n\n print(f\" 🔧 Updated .gitignore\")\n\n\ndef fix_backups(openclaw_dir: Path):\n \"\"\"Fix backup file and directory permissions.\"\"\"\n backup_dir = openclaw_dir / 'backups'\n if not backup_dir.exists():\n return\n\n fixed = 0\n os.chmod(backup_dir, 0o700)\n\n for sub in backup_dir.iterdir():\n if sub.is_dir():\n os.chmod(sub, 0o700)\n fixed += 1\n for f in sub.iterdir():\n if f.is_file():\n os.chmod(f, 0o600)\n fixed += 1\n\n print(f\" 🔧 Fixed {fixed} backup file/directory permissions\")\n\n\ndef validate(check_type: str = 'all', auto_fix: bool = False) -> bool:\n \"\"\"Validate .env file.\"\"\"\n home = Path.home()\n openclaw_dir = home / '.openclaw'\n env_file = openclaw_dir / '.env'\n\n print(\"\\n🔍 Validating credentials...\\n\")\n\n all_ok = True\n\n # Check permissions\n if check_type in ['all', 'permissions']:\n print(\"📋 Checking permissions...\")\n result = check_permissions(env_file)\n if result['status'] == 'ok':\n print(f\" ✅ Permissions: {result['mode']}\")\n elif result['status'] == 'missing':\n print(f\" ❌ {result['message']}\")\n return False\n else:\n print(f\" ⚠️ {result['message']}\")\n all_ok = False\n if auto_fix:\n fix_permissions(env_file)\n all_ok = True\n\n # Check gitignore\n if check_type in ['all', 'gitignore']:\n print(\"\\n📋 Checking .gitignore...\")\n result = check_gitignore(openclaw_dir)\n if result['status'] == 'ok':\n print(f\" ✅ .env is git-ignored\")\n else:\n print(f\" ⚠️ {result.get('message', 'Not protected')}\")\n all_ok = False\n if auto_fix:\n fix_gitignore(openclaw_dir)\n all_ok = True\n\n # Check format\n if check_type in ['all', 'format']:\n print(\"\\n📋 Checking format...\")\n result = check_format(env_file)\n if result['status'] == 'ok':\n print(f\" ✅ Format valid ({result['keys_count']} keys)\")\n else:\n if result['issues']:\n print(f\" ⚠️ Found {len(result['issues'])} issue(s):\")\n for issue in result['issues'][:5]:\n print(f\" • {issue}\")\n if len(result['issues']) > 5:\n print(f\" ... +{len(result['issues']) - 5} more\")\n if result['duplicates']:\n print(f\" ⚠️ Duplicate keys: {', '.join(result['duplicates'])}\")\n all_ok = False\n\n # Check security\n if check_type in ['all', 'security']:\n print(\"\\n📋 Checking security...\")\n result = check_security(env_file)\n if result['status'] == 'ok':\n print(f\" ✅ No security warnings\")\n else:\n print(f\" ⚠️ Found {len(result['warnings'])} warning(s):\")\n for warning in result['warnings'][:10]:\n print(f\" • {warning}\")\n if len(result['warnings']) > 10:\n print(f\" ... +{len(result['warnings']) - 10} more\")\n\n # Check backups\n if check_type in ['all', 'backups']:\n print(\"\\n📋 Checking backup security...\")\n result = check_backups(openclaw_dir)\n if result['status'] == 'ok':\n print(f\" ✅ Backup permissions OK\")\n else:\n print(f\" ⚠️ Found {len(result['issues'])} issue(s):\")\n for issue in result['issues'][:10]:\n print(f\" • {issue}\")\n if len(result['issues']) > 10:\n print(f\" ... +{len(result['issues']) - 10} more\")\n all_ok = False\n if auto_fix:\n fix_backups(openclaw_dir)\n all_ok = True\n\n # Summary\n print(f\"\\n{'✅' if all_ok else '⚠️'} Validation {'passed' if all_ok else 'found issues'}\")\n\n if not all_ok and not auto_fix:\n print(f\"\\n💡 Run with --fix to automatically fix issues\")\n\n return all_ok\n\n\ndef main():\n parser = argparse.ArgumentParser(description='Validate credentials')\n parser.add_argument('--check', choices=['all', 'permissions', 'gitignore', 'format', 'security', 'backups'],\n default='all', help='What to check')\n parser.add_argument('--fix', action='store_true',\n help='Automatically fix issues')\n args = parser.parse_args()\n\n result = validate(args.check, args.fix)\n return 0 if result else 1\n\n\nif __name__ == '__main__':\n exit(main())\n","content_type":"text/x-python; charset=utf-8","language":"python","size":12086,"content_sha256":"12e19285de0b5a0ba43777c27065edbc16232b8982b18cfdd621db5b9735c5fd"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Credential Manager","type":"text"}]},{"type":"paragraph","content":[{"text":"STATUS: MANDATORY SECURITY FOUNDATION","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Consolidate scattered API keys and credentials into a secure, centralized ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" file.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"⚠️ This Is Not Optional","type":"text"}]},{"type":"paragraph","content":[{"text":"Centralized ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" credential management is a ","type":"text"},{"text":"core requirement","type":"text","marks":[{"type":"strong"}]},{"text":" for OpenClaw security. If your credentials are scattered across multiple files, ","type":"text"},{"text":"stop and consolidate them now","type":"text","marks":[{"type":"strong"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"THE RULE:","type":"text","marks":[{"type":"strong"}]},{"text":" All credentials MUST be in ","type":"text"},{"text":"~/.openclaw/.env","type":"text","marks":[{"type":"code_inline"}]},{"text":" ONLY. No workspace, no skills, no scripts directories.","type":"text"}]},{"type":"paragraph","content":[{"text":"See:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CORE-PRINCIPLE.md","type":"text","marks":[{"type":"link","attrs":{"href":"CORE-PRINCIPLE.md","title":null}}]},{"text":" - Why this is non-negotiable","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CONSOLIDATION-RULE.md","type":"text","marks":[{"type":"link","attrs":{"href":"CONSOLIDATION-RULE.md","title":null}}]},{"text":" - The single source principle","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"The Foundation","type":"text"}]},{"type":"paragraph","content":[{"text":"Every OpenClaw deployment MUST have:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"~/.openclaw/.env (mode 600)","type":"text"}]},{"type":"paragraph","content":[{"text":"This is your single source of truth for all credentials. No exceptions.","type":"text"}]},{"type":"paragraph","content":[{"text":"Why?","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Single location = easier to secure","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"File mode 600 = only you can read","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Git-ignored = won't accidentally commit","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validated format = catches errors","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Audit trail = know what changed","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Scattered credentials = scattered attack surface. This skill fixes that.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"What This Skill Does","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scans","type":"text","marks":[{"type":"strong"}]},{"text":" for credentials in common locations (including deep scan for hardcoded secrets)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Backs up","type":"text","marks":[{"type":"strong"}]},{"text":" existing credential files (timestamped, mode 600)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Consolidates","type":"text","marks":[{"type":"strong"}]},{"text":" into ","type":"text"},{"text":"~/.openclaw/.env","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Secures","type":"text","marks":[{"type":"strong"}]},{"text":" with proper permissions (600 files, 700 directories)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validates","type":"text","marks":[{"type":"strong"}]},{"text":" security, format, and entropy","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Encrypts","type":"text","marks":[{"type":"strong"}]},{"text":" high-value secrets with GPG (wallet keys, private keys, mnemonics)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tracks","type":"text","marks":[{"type":"strong"}]},{"text":" credential rotation schedules","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Enforces","type":"text","marks":[{"type":"strong"}]},{"text":" best practices via fail-fast checks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cleans up","type":"text","marks":[{"type":"strong"}]},{"text":" old files after migration","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Detection Parameters","type":"text"}]},{"type":"paragraph","content":[{"text":"The skill automatically detects credentials by scanning for:","type":"text"}]},{"type":"paragraph","content":[{"text":"File Patterns:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"~/.config/*/credentials.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Service config directories","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"~/.config/*/*.credentials.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Nested credential files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"~/.openclaw/*.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Credential files in OpenClaw root","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"~/.openclaw/*-credentials*","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Named credential files (e.g., farcaster-credentials.json)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"~/.openclaw/workspace/memory/*-creds.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Memory credential files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"~/.openclaw/workspace/memory/*credentials*.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Memory credential files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"~/.openclaw/workspace/.env","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Workspace env files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"~/.openclaw/workspace/*/.env","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Subdirectory env files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"~/.openclaw/workspace/skills/*/.env","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Skill env files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"~/.local/share/*/credentials.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Local share directories","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Sensitive Key Patterns:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"API keys, access tokens, bearer tokens","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Secrets, passwords, passphrases","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"OAuth consumer keys","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Private keys, signing keys, wallet keys","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mnemonics and seed phrases","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Deep Scan (--deep flag):","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Greps ","type":"text"},{"text":".sh","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".js","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".py","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".mjs","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".ts","type":"text","marks":[{"type":"code_inline"}]},{"text":" files for hardcoded secrets","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Detects high-entropy strings matching common key prefixes (","type":"text"},{"text":"sk_","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"pk_","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Bearer","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"0x","type":"text","marks":[{"type":"code_inline"}]},{"text":" + 64 hex)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Excludes ","type":"text"},{"text":"node_modules/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".git/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reports file, line number, and key pattern matched","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Security Checks:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"File permissions (must be ","type":"text"},{"text":"600","type":"text","marks":[{"type":"code_inline"}]},{"text":" for files, ","type":"text"},{"text":"700","type":"text","marks":[{"type":"code_inline"}]},{"text":" for directories)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Backup permissions (must be ","type":"text"},{"text":"600","type":"text","marks":[{"type":"code_inline"}]},{"text":" for backup files, ","type":"text"},{"text":"700","type":"text","marks":[{"type":"code_inline"}]},{"text":" for backup dirs)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Git-ignore protection","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Format validation (allows quoted values with spaces)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Entropy analysis (flags suspiciously low-entropy secrets)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Private key detection (flags ","type":"text"},{"text":"0x","type":"text","marks":[{"type":"code_inline"}]},{"text":" + 64 hex char values)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mnemonic detection (flags 12/24 word values)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Symlink detection (validates symlinked .env targets)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Start","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Full Migration (Recommended)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Scan for credentials\n./scripts/scan.py\n\n# Deep scan (includes hardcoded secrets in scripts)\n./scripts/scan.py --deep\n\n# Review and consolidate\n./scripts/consolidate.py\n\n# Validate security\n./scripts/validate.py\n\n# Encrypt high-value secrets\n./scripts/encrypt.py --keys MAIN_WALLET_PRIVATE_KEY,CUSTODY_PRIVATE_KEY\n\n# Check rotation status\n./scripts/rotation-check.py","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Individual Operations","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Scan only\n./scripts/scan.py\n\n# Consolidate specific service\n./scripts/consolidate.py --service x\n\n# Backup without removing\n./scripts/consolidate.py --backup-only\n\n# Clean up old files\n./scripts/cleanup.py --confirm","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Credential Locations","type":"text"}]},{"type":"paragraph","content":[{"text":"The skill scans these locations:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"~/.config/*/credentials.json\n~/.openclaw/*.json\n~/.openclaw/*-credentials*\n~/.openclaw/workspace/memory/*-creds.json\n~/.openclaw/workspace/memory/*credentials*.json\n~/.openclaw/workspace/*/.env\n~/.openclaw/workspace/skills/*/.env\n~/.env (if exists, merges)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Security Features","type":"text"}]},{"type":"paragraph","content":[{"text":"✅ ","type":"text"},{"text":"File permissions:","type":"text","marks":[{"type":"strong"}]},{"text":" Sets ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" to mode 600 (owner only) ✅ ","type":"text"},{"text":"Directory permissions:","type":"text","marks":[{"type":"strong"}]},{"text":" Sets backup dirs to mode 700 (owner only) ✅ ","type":"text"},{"text":"Backup permissions:","type":"text","marks":[{"type":"strong"}]},{"text":" Sets backup files to mode 600 (owner only) ✅ ","type":"text"},{"text":"Git protection:","type":"text","marks":[{"type":"strong"}]},{"text":" Creates/updates ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":" ✅ ","type":"text"},{"text":"Backups:","type":"text","marks":[{"type":"strong"}]},{"text":" Timestamped backups before changes (secured) ✅ ","type":"text"},{"text":"Validation:","type":"text","marks":[{"type":"strong"}]},{"text":" Checks format, permissions, entropy, and duplicates ✅ ","type":"text"},{"text":"Template:","type":"text","marks":[{"type":"strong"}]},{"text":" Creates ","type":"text"},{"text":".env.example","type":"text","marks":[{"type":"code_inline"}]},{"text":" (safe to share) ✅ ","type":"text"},{"text":"GPG encryption:","type":"text","marks":[{"type":"strong"}]},{"text":" Encrypts high-value secrets at rest ✅ ","type":"text"},{"text":"Rotation tracking:","type":"text","marks":[{"type":"strong"}]},{"text":" Warns when credentials need rotation ✅ ","type":"text"},{"text":"Deep scan:","type":"text","marks":[{"type":"strong"}]},{"text":" Detects hardcoded secrets in source files ✅ ","type":"text"},{"text":"Symlink-aware:","type":"text","marks":[{"type":"strong"}]},{"text":" Validates symlinked .env targets","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Output Structure","type":"text"}]},{"type":"paragraph","content":[{"text":"After migration:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"~/.openclaw/\n├── .env # All credentials (secure, mode 600)\n├── .env.secrets.gpg # GPG-encrypted high-value keys (mode 600)\n├── .env.meta # Rotation metadata (mode 600)\n├── .env.example # Template (safe to share)\n├── .gitignore # Protects .env and .env.secrets.gpg\n└── backups/ # (mode 700)\n └── credentials-old-YYYYMMDD/ # (mode 700)\n └── *.bak # Backup files (mode 600)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"GPG Encryption for High-Value Secrets","type":"text"}]},{"type":"paragraph","content":[{"text":"Private keys, wallet keys, and mnemonics should ","type":"text"},{"text":"never","type":"text","marks":[{"type":"strong"}]},{"text":" exist as plaintext on disk. Use GPG encryption for these.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Setup GPG","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# First-time setup (generates OpenClaw GPG key, configures agent cache)\n./scripts/setup-gpg.sh","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Encrypt High-Value Keys","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Encrypt specific keys (moves them from .env to .env.secrets.gpg)\n./scripts/encrypt.py --keys MAIN_WALLET_PRIVATE_KEY,CUSTODY_PRIVATE_KEY,SIGNER_PRIVATE_KEY\n\n# The .env will contain placeholders:\n# MAIN_WALLET_PRIVATE_KEY=GPG:MAIN_WALLET_PRIVATE_KEY","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"How Scripts Access Encrypted Keys","type":"text"}]},{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"enforce.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" module handles this transparently:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from enforce import get_credential\n\n# Works for both plaintext and GPG-encrypted keys\nkey = get_credential('MAIN_WALLET_PRIVATE_KEY')\n# If value starts with \"GPG:\", decrypts from .env.secrets.gpg automatically","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"GPG Agent Caching","type":"text"}]},{"type":"paragraph","content":[{"text":"On headless servers (VPS), the GPG agent caches the passphrase:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Default cache TTL: 8 hours","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Configurable via ","type":"text"},{"text":"setup-gpg.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Passphrase required once after reboot, then cached","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"What to Encrypt","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Key Type","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Encrypt?","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Why","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wallet private keys","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅ Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Controls funds","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Custody/signer private keys","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅ Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Controls identity","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mnemonics / seed phrases","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅ Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Master recovery","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"API keys (services)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"❌ No","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Revocable, low damage","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Agent IDs, names, URLs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"❌ No","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Not secrets","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Credential Rotation Tracking","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Setup Rotation Metadata","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Initialize rotation tracking for all keys\n./scripts/rotation-check.py --init","type":"text"}]},{"type":"paragraph","content":[{"text":"Creates ","type":"text"},{"text":"~/.openclaw/.env.meta","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"MAIN_WALLET_PRIVATE_KEY\": {\n \"created\": \"2026-01-15\",\n \"lastRotated\": null,\n \"rotationDays\": 90,\n \"risk\": \"critical\"\n },\n \"MOLTBOOK_API_KEY\": {\n \"created\": \"2026-02-04\",\n \"lastRotated\": null,\n \"rotationDays\": 180,\n \"risk\": \"low\"\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Check Rotation Status","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Check which keys need rotation\n./scripts/rotation-check.py\n\n# Output:\n# 🔴 MAIN_WALLET_PRIVATE_KEY: 26 days old (critical, rotate every 90 days)\n# ✅ MOLTBOOK_API_KEY: 7 days old (low, rotate every 180 days)","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Rotation Schedules","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Risk Level","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rotation Period","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Examples","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Critical","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"90 days","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wallet keys, private keys","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Standard","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"180 days","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"API keys for paid services","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Low","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"365 days","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Free-tier API keys, agent IDs","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Add to Heartbeat (Optional)","type":"text"}]},{"type":"paragraph","content":[{"text":"Add rotation checks to ","type":"text"},{"text":"HEARTBEAT.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for periodic monitoring:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"## Credential Rotation (weekly)\nIf 7+ days since last rotation check:\n1. Run: ./scripts/rotation-check.py\n2. If any keys overdue: notify human\n3. Update lastRotationCheck timestamp","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Supported Services","type":"text"}]},{"type":"paragraph","content":[{"text":"Common services auto-detected:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"X (Twitter):","type":"text","marks":[{"type":"strong"}]},{"text":" OAuth 1.0a credentials","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Farcaster:","type":"text","marks":[{"type":"strong"}]},{"text":" Custody keys, signer keys, FID credentials","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Molten:","type":"text","marks":[{"type":"strong"}]},{"text":" Agent intent matching","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Moltbook:","type":"text","marks":[{"type":"strong"}]},{"text":" Agent social network","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Botchan/4claw:","type":"text","marks":[{"type":"strong"}]},{"text":" Net Protocol","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"OpenAI, Anthropic, Google:","type":"text","marks":[{"type":"strong"}]},{"text":" AI providers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"GitHub, GitLab:","type":"text","marks":[{"type":"strong"}]},{"text":" Code hosting","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Coinbase/CDP:","type":"text","marks":[{"type":"strong"}]},{"text":" Crypto wallet credentials","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generic:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"API_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*_TOKEN","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*_SECRET","type":"text","marks":[{"type":"code_inline"}]},{"text":" patterns","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/supported-services.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/supported-services.md","title":null}}]},{"text":" for full list.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Scripts","type":"text"}]},{"type":"paragraph","content":[{"text":"All scripts support ","type":"text"},{"text":"--help","type":"text","marks":[{"type":"code_inline"}]},{"text":" for detailed usage.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"scan.py","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Scan and report\n./scripts/scan.py\n\n# Deep scan (includes hardcoded secrets in scripts)\n./scripts/scan.py --deep\n\n# Include custom paths\n./scripts/scan.py --paths ~/.myapp/config ~/.local/share/creds\n\n# JSON output\n./scripts/scan.py --format json","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"consolidate.py","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Interactive mode (prompts before changes)\n./scripts/consolidate.py\n\n# Auto-confirm (no prompts)\n./scripts/consolidate.py --yes\n\n# Backup only\n./scripts/consolidate.py --backup-only\n\n# Specific service\n./scripts/consolidate.py --service molten","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"validate.py","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Full validation (permissions, format, entropy, security)\n./scripts/validate.py\n\n# Check permissions only\n./scripts/validate.py --check permissions\n\n# Fix issues automatically\n./scripts/validate.py --fix","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"encrypt.py","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Encrypt specific high-value keys\n./scripts/encrypt.py --keys MAIN_WALLET_PRIVATE_KEY,CUSTODY_PRIVATE_KEY\n\n# List currently encrypted keys\n./scripts/encrypt.py --list\n\n# Decrypt (move back to plaintext .env)\n./scripts/encrypt.py --decrypt --keys MAIN_WALLET_PRIVATE_KEY","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"rotation-check.py","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Check rotation status\n./scripts/rotation-check.py\n\n# Initialize tracking for all keys\n./scripts/rotation-check.py --init\n\n# Record a rotation\n./scripts/rotation-check.py --rotated MOLTBOOK_API_KEY","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"setup-gpg.sh","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# First-time GPG setup for OpenClaw\n./scripts/setup-gpg.sh\n\n# Configure cache timeout (hours)\n./scripts/setup-gpg.sh --cache-hours 12","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"cleanup.py","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Dry run (shows what would be deleted)\n./scripts/cleanup.py\n\n# Actually delete old files\n./scripts/cleanup.py --confirm\n\n# Keep backups\n./scripts/cleanup.py --confirm --keep-backups","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Migration Workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"This is the exact step-by-step flow, tested and verified on a live OpenClaw deployment.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1: Scan for Scattered Credentials","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd /path/to/openclaw/skills/credential-manager\n\n# Basic scan — finds credential files by path patterns\n./scripts/scan.py\n\n# Deep scan — also greps source files for hardcoded secrets\n./scripts/scan.py --deep","type":"text"}]},{"type":"paragraph","content":[{"text":"What to look for in output:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"⚠️ files with mode != 600 (insecure permissions)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Symlinked ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" files (should point to main ","type":"text"},{"text":"~/.openclaw/.env","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"JSON credential files outside ","type":"text"},{"text":"~/.openclaw/.env","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Deep scan hits on hardcoded keys in scripts","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Example output:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"⚠️ /home/user/.openclaw/farcaster-credentials.json\n Type: json\n Keys: custodyPrivateKey, signerPrivateKey, ...\n Mode: 644\n ⚠️ Should be 600 for security\n\n✅ /home/user/.openclaw/.env\n Type: env\n Keys: API_KEY, X_CONSUMER_KEY, ...\n Mode: 600","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2: Consolidate into .env","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"./scripts/consolidate.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Interactive flow:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Script scans and lists all credential files found","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Backs up existing ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" to ","type":"text"},{"text":"~/.openclaw/backups/credentials-old-YYYYMMDD/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Loads existing ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" keys","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Processes each credential file:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Auto-detects service (x, farcaster, moltbook, molten, etc.)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Normalizes key names (e.g., ","type":"text"},{"text":"custodyPrivateKey","type":"text","marks":[{"type":"code_inline"}]},{"text":" → ","type":"text"},{"text":"FARCASTER_CUSTODY_PRIVATE_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Shows mapping: ","type":"text"},{"text":"key → ENV_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Asks for confirmation: ","type":"text"},{"text":"Proceed? [y/N]","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Writes merged ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" (mode 600)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Creates ","type":"text"},{"text":".env.example","type":"text","marks":[{"type":"code_inline"}]},{"text":" template (safe to share)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Updates ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"For credentials not auto-detected","type":"text","marks":[{"type":"strong"}]},{"text":" (e.g., nested JSON like ","type":"text"},{"text":"farcaster-credentials.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" with multiple accounts), manually add to ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cat >> ~/.openclaw/.env \u003c\u003c 'EOF'\n\n# FARCASTER (Active: mr-teeclaw, FID 2700953)\nFARCASTER_FID=2700953\nFARCASTER_FNAME=mr-teeclaw\nFARCASTER_CUSTODY_ADDRESS=0x...\nFARCASTER_CUSTODY_PRIVATE_KEY=0x...\nFARCASTER_SIGNER_PUBLIC_KEY=...\nFARCASTER_SIGNER_PRIVATE_KEY=...\n\n# FARCASTER LEGACY (teeclaw, FID 2684290)\nFARCASTER_LEGACY_FID=2684290\nFARCASTER_LEGACY_CUSTODY_ADDRESS=0x...\nFARCASTER_LEGACY_CUSTODY_PRIVATE_KEY=0x...\nFARCASTER_LEGACY_SIGNER_PUBLIC_KEY=...\nFARCASTER_LEGACY_SIGNER_PRIVATE_KEY=...\nEOF\n\nchmod 600 ~/.openclaw/.env","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3: Validate","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"./scripts/validate.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Checks performed:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" permissions (must be 600)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":" coverage","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ Format validation (key format, quoting, duplicates)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ Security analysis:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Detects plaintext private keys (","type":"text"},{"text":"0x","type":"text","marks":[{"type":"code_inline"}]},{"text":" + 64 hex chars) → recommends GPG","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Detects mnemonic/seed phrases (12/24 word values) → recommends GPG","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Entropy analysis on SECRET/PRIVATE_KEY/PASSWORD fields","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Flags weak/placeholder values","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ Backup permissions (files must be 600, directories 700)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Fix issues automatically:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"./scripts/validate.py --fix","type":"text"}]},{"type":"paragraph","content":[{"text":"This fixes: file permissions, directory permissions, backup permissions, gitignore. It does NOT auto-fix format issues or encrypt keys — those require manual action.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 4: Setup GPG and Encrypt Private Keys","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# First-time GPG setup (configures agent cache, tests encrypt/decrypt)\n./scripts/setup-gpg.sh\n# Optional: --cache-hours 12 (default: 8)","type":"text"}]},{"type":"paragraph","content":[{"text":"Encrypt high-value keys:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Encrypt wallet + Farcaster private keys\n./scripts/encrypt.py --keys MAIN_WALLET_PRIVATE_KEY,FARCASTER_CUSTODY_PRIVATE_KEY,FARCASTER_SIGNER_PRIVATE_KEY,FARCASTER_LEGACY_CUSTODY_PRIVATE_KEY,FARCASTER_LEGACY_SIGNER_PRIVATE_KEY","type":"text"}]},{"type":"paragraph","content":[{"text":"What happens:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prompts for a GPG passphrase (or reads ","type":"text"},{"text":"OPENCLAW_GPG_PASSPHRASE","type":"text","marks":[{"type":"code_inline"}]},{"text":" env var)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extracts specified key values from ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stores them encrypted in ","type":"text"},{"text":"~/.openclaw/.env.secrets.gpg","type":"text","marks":[{"type":"code_inline"}]},{"text":" (AES256, mode 600)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Replaces ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" values with ","type":"text"},{"text":"GPG:KEY_NAME","type":"text","marks":[{"type":"code_inline"}]},{"text":" placeholders","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scripts using ","type":"text"},{"text":"get_credential()","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"_load_cred()","type":"text","marks":[{"type":"code_inline"}]},{"text":" decrypt transparently","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Save passphrase to .env for automated decryption:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"echo 'OPENCLAW_GPG_PASSPHRASE=your-passphrase-here' >> ~/.openclaw/.env\nchmod 600 ~/.openclaw/.env","type":"text"}]},{"type":"paragraph","content":[{"text":"Verify encryption:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Check .env has GPG placeholders\ngrep \"GPG:\" ~/.openclaw/.env\n\n# List all encrypted keys\n./scripts/encrypt.py --list","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 5: Initialize Rotation Tracking","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"./scripts/rotation-check.py --init","type":"text"}]},{"type":"paragraph","content":[{"text":"Auto-classifies all keys by risk:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Critical","type":"text","marks":[{"type":"strong"}]},{"text":" (90-day rotation): ","type":"text"},{"text":"*PRIVATE_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*MNEMONIC","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*SEED","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*WALLET_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*CUSTODY*","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*SIGNER*","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Standard","type":"text","marks":[{"type":"strong"}]},{"text":" (180-day rotation): ","type":"text"},{"text":"*API_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*SECRET","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*TOKEN","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*BEARER","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*CONSUMER*","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"*ACCESS*","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Low","type":"text","marks":[{"type":"strong"}]},{"text":" (365-day rotation): Everything else","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Creates ","type":"text"},{"text":"~/.openclaw/.env.meta","type":"text","marks":[{"type":"code_inline"}]},{"text":" (mode 600) with creation dates and rotation schedules.","type":"text"}]},{"type":"paragraph","content":[{"text":"Check rotation status anytime:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"./scripts/rotation-check.py","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 6: Cleanup Old Credential Files","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Dry run first — see what would be deleted\n./scripts/cleanup.py\n\n# Actually delete (prompts for 'DELETE' confirmation)\n./scripts/cleanup.py --confirm","type":"text"}]},{"type":"paragraph","content":[{"text":"Also manually remove migrated files not caught by the scanner:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Example: farcaster-credentials.json was manually migrated\ncp ~/.openclaw/farcaster-credentials.json ~/.openclaw/backups/credentials-old-YYYYMMDD/farcaster-credentials.json.bak\nchmod 600 ~/.openclaw/backups/credentials-old-YYYYMMDD/farcaster-credentials.json.bak\nrm ~/.openclaw/farcaster-credentials.json","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 7: Update Scripts That Referenced Old Files","type":"text"}]},{"type":"paragraph","content":[{"text":"Any scripts that loaded from JSON credential files or hardcoded paths need updating.","type":"text"}]},{"type":"paragraph","content":[{"text":"Pattern — Bash scripts:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# OLD (insecure):\nFARCASTER_CREDS=\"/home/user/.openclaw/farcaster-credentials.json\"\nfid=$(jq -r '.fid' \"$FARCASTER_CREDS\")\nprivate_key=$(jq -r '.custodyPrivateKey' \"$FARCASTER_CREDS\")\n\n# NEW (secure, GPG-aware):\nENV_FILE=\"$HOME/.openclaw/.env\"\n\n_load_cred() {\n local key=\"$1\"\n local value\n value=$(grep \"^${key}=\" \"$ENV_FILE\" | head -1 | cut -d= -f2-)\n if [[ \"$value\" == GPG:* ]]; then\n local gpg_key=\"${value#GPG:}\"\n local passphrase=\"${OPENCLAW_GPG_PASSPHRASE:-}\"\n if [ -n \"$passphrase\" ]; then\n value=$(echo \"$passphrase\" | gpg -d --batch --quiet --passphrase-fd 0 \"$HOME/.openclaw/.env.secrets.gpg\" | python3 -c \"import json,sys; print(json.load(sys.stdin).get('$gpg_key',''))\")\n else\n value=$(gpg -d --batch --quiet \"$HOME/.openclaw/.env.secrets.gpg\" | python3 -c \"import json,sys; print(json.load(sys.stdin).get('$gpg_key',''))\")\n fi\n fi\n echo \"$value\"\n}\n\nfid=$(_load_cred \"FARCASTER_FID\")\nprivate_key=$(_load_cred \"FARCASTER_CUSTODY_PRIVATE_KEY\")","type":"text"}]},{"type":"paragraph","content":[{"text":"Pattern — Node.js scripts:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// OLD (insecure):\nconst creds = JSON.parse(fs.readFileSync('~/.openclaw/farcaster-credentials.json'));\nconst privateKey = creds.custodyPrivateKey;\n\n// NEW (secure, GPG-aware):\nconst ENV_PATH = path.join(os.homedir(), '.openclaw/.env');\nconst SECRETS_PATH = path.join(os.homedir(), '.openclaw/.env.secrets.gpg');\n\nfunction loadCred(key) {\n const content = fs.readFileSync(ENV_PATH, 'utf8');\n for (const line of content.split('\\n')) {\n if (line.startsWith(key + '=')) {\n let value = line.slice(key.length + 1).trim();\n if (value.startsWith('GPG:')) {\n const { execSync } = require('child_process');\n const passphrase = process.env.OPENCLAW_GPG_PASSPHRASE || '';\n const cmd = passphrase\n ? `echo \"${passphrase}\" | gpg -d --batch --quiet --passphrase-fd 0 \"${SECRETS_PATH}\"`\n : `gpg -d --batch --quiet \"${SECRETS_PATH}\"`;\n const secrets = JSON.parse(execSync(cmd, { encoding: 'utf8' }));\n return secrets[value.slice(4)] || '';\n }\n return value;\n }\n }\n return '';\n}\n\nconst privateKey = loadCred('FARCASTER_CUSTODY_PRIVATE_KEY');","type":"text"}]},{"type":"paragraph","content":[{"text":"Pattern — Python scripts:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"# Use the enforce module (recommended):\nimport sys\nfrom pathlib import Path\nsys.path.insert(0, str(Path.home() / 'openclaw/skills/credential-manager/scripts'))\nfrom enforce import get_credential\n\nprivate_key = get_credential('FARCASTER_CUSTODY_PRIVATE_KEY') # Auto-decrypts GPG","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 8: Final Validation","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Run full validation — should show all green\n./scripts/validate.py\n\n# Verify encrypted keys\n./scripts/encrypt.py --list\n\n# Check rotation status\n./scripts/rotation-check.py\n\n# Test a script that uses credentials\nbash /path/to/your/script.sh","type":"text"}]},{"type":"paragraph","content":[{"text":"Expected final state:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"~/.openclaw/\n├── .env # All credentials (mode 600, private keys = GPG:*)\n├── .env.secrets.gpg # GPG-encrypted private keys (mode 600)\n├── .env.meta # Rotation tracking metadata (mode 600)\n├── .env.example # Template (safe to share)\n├── .gitignore # Protects .env, .env.secrets.gpg, .env.meta\n└── backups/ # (mode 700)\n └── credentials-old-YYYYMMDD/ # (mode 700)\n └── *.bak # Backup files (mode 600)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"For Skill Developers: Enforce This Standard","type":"text"}]},{"type":"paragraph","content":[{"text":"Other OpenClaw skills MUST validate credentials are secure before using them:","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Python Skills","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"#!/usr/bin/env python3\nimport sys\nfrom pathlib import Path\n\n# Add credential-manager scripts to path\nsys.path.insert(0, str(Path.home() / '.openclaw/skills/credential-manager/scripts'))\n\n# Enforce secure .env (exits if not compliant)\nfrom enforce import require_secure_env, get_credential\n\nrequire_secure_env()\n\n# Now safe to load credentials (handles GPG-encrypted keys transparently)\napi_key = get_credential('SERVICE_API_KEY')\nwallet_key = get_credential('MAIN_WALLET_PRIVATE_KEY') # Auto-decrypts from GPG","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Bash Skills","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"#!/usr/bin/env bash\nset -euo pipefail\n\n# Validate .env exists and is secure\nif ! python3 ~/.openclaw/skills/credential-manager/scripts/enforce.py; then\n exit 1\nfi\n\n# Now safe to load\nsource ~/.openclaw/.env","type":"text"}]},{"type":"paragraph","content":[{"text":"This creates a fail-fast system:","type":"text","marks":[{"type":"strong"}]},{"text":" If credentials aren't properly secured, skills refuse to run. Users are forced to fix it.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Loading Credentials","type":"text"}]},{"type":"paragraph","content":[{"text":"After migration, load from ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Python","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"import os\nfrom pathlib import Path\n\n# Load .env\nenv_file = Path.home() / '.openclaw' / '.env'\nwith open(env_file) as f:\n for line in f:\n if '=' in line and not line.strip().startswith('#'):\n key, val = line.strip().split('=', 1)\n os.environ[key] = val\n\n# Use credentials\napi_key = os.getenv('SERVICE_API_KEY')","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Bash","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Load .env\nset -a\nsource ~/.openclaw/.env\nset +a\n\n# Use credentials\necho \"$SERVICE_API_KEY\"","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Using Existing Loaders","type":"text"}]},{"type":"paragraph","content":[{"text":"If you migrated using OpenClaw scripts:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from load_credentials import get_credentials\ncreds = get_credentials('x')","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Adding New Credentials","type":"text"}]},{"type":"paragraph","content":[{"text":"Edit ","type":"text"},{"text":"~/.openclaw/.env","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Add new service\nNEW_SERVICE_API_KEY=your_key_here\nNEW_SERVICE_SECRET=your_secret_here","type":"text"}]},{"type":"paragraph","content":[{"text":"Update template too:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Edit .env.example\nNEW_SERVICE_API_KEY=your_key_here\nNEW_SERVICE_SECRET=your_secret_here","type":"text"}]},{"type":"paragraph","content":[{"text":"If the new credential is high-value (private key, wallet key):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Add to .env first, then encrypt\n./scripts/encrypt.py --keys NEW_SERVICE_PRIVATE_KEY","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Security Best Practices","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/security.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/security.md","title":null}}]},{"text":" for detailed security guidelines.","type":"text"}]},{"type":"paragraph","content":[{"text":"Quick checklist:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" has 600 permissions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" is git-ignored","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ Backup files have 600 permissions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ Backup directories have 700 permissions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ No credentials in code or logs (use ","type":"text"},{"text":"--deep","type":"text","marks":[{"type":"code_inline"}]},{"text":" scan to verify)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ Private keys encrypted with GPG","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ Rotation schedule established and tracked","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ Symlinked .env files point to the main .env only","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ No credentials in shell history (use ","type":"text"},{"text":"source","type":"text","marks":[{"type":"code_inline"}]},{"text":", not ","type":"text"},{"text":"export KEY=val","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Rollback","type":"text"}]},{"type":"paragraph","content":[{"text":"If something goes wrong:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Find your backup\nls -la ~/.openclaw/backups/\n\n# Restore specific file\ncp ~/.openclaw/backups/credentials-old-YYYYMMDD/x-credentials.json.bak \\\n ~/.config/x/credentials.json\n\n# Decrypt GPG secrets back to plaintext\n./scripts/encrypt.py --decrypt --keys MAIN_WALLET_PRIVATE_KEY","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Notes","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Non-destructive by default:","type":"text","marks":[{"type":"strong"}]},{"text":" Original files backed up before removal","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Idempotent:","type":"text","marks":[{"type":"strong"}]},{"text":" Safe to run multiple times","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extensible:","type":"text","marks":[{"type":"strong"}]},{"text":" Add custom credential patterns in scripts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Secure:","type":"text","marks":[{"type":"strong"}]},{"text":" Never logs full credentials, only metadata","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"GPG-aware:","type":"text","marks":[{"type":"strong"}]},{"text":" Transparently handles encrypted and plaintext credentials","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Backup-hardened:","type":"text","marks":[{"type":"strong"}]},{"text":" All backups secured with proper permissions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Symlink-aware:","type":"text","marks":[{"type":"strong"}]},{"text":" Detects and validates symlinked credential files","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"credential-manager","author":"@skillopedia","source":{"stars":2012,"repo_name":"openclaw-master-skills","origin_url":"https://github.com/leoyeai/openclaw-master-skills/blob/HEAD/skills/openclaw-credential-manager/SKILL.md","repo_owner":"leoyeai","body_sha256":"5f94540ca40cb82cf1ee0ebef0d8084ba6e5aaa1a19a3144960874bf6f111174","cluster_key":"d1288e33a3bf0092269ab0965f68b2fd1cdfb1c8939617765a3d2fd2606dd13d","clean_bundle":{"format":"clean-skill-bundle-v1","source":"leoyeai/openclaw-master-skills/skills/openclaw-credential-manager/SKILL.md","attachments":[{"id":"e58419d1-6122-55ac-a769-838eba9f6437","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e58419d1-6122-55ac-a769-838eba9f6437/attachment.md","path":"CHANGELOG.md","size":8203,"sha256":"3c30b872ef0942fd65117a2b5a481735a23b797208d9a23a0383a8b9f5df1c14","contentType":"text/markdown; charset=utf-8"},{"id":"98723814-73aa-5b72-8eee-6b65ed979e42","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/98723814-73aa-5b72-8eee-6b65ed979e42/attachment.md","path":"CONSOLIDATION-RULE.md","size":2865,"sha256":"8deacc452e0ea3e7b8e5fdbffb748a8620fa4d7259b7fe42bd83bad0dd4f5b76","contentType":"text/markdown; charset=utf-8"},{"id":"02c4b33d-778c-56ca-822c-c2a3cd823774","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/02c4b33d-778c-56ca-822c-c2a3cd823774/attachment.md","path":"CORE-PRINCIPLE.md","size":3914,"sha256":"8c34e6ebfb7b8fbfa4e57aa5183af556292e9c14c92c9c4801f05ada2f61c6ad","contentType":"text/markdown; charset=utf-8"},{"id":"73f1dde6-2f56-5997-ab72-ba6e7a588457","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/73f1dde6-2f56-5997-ab72-ba6e7a588457/attachment.md","path":"README.md","size":7230,"sha256":"a48e37c13a473ab8fc99154b331cbaf6466fab4e18ca3f1aa98104ccba860ea9","contentType":"text/markdown; charset=utf-8"},{"id":"f64fb7f9-d31b-5dbd-b0a9-a22a15a1c370","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f64fb7f9-d31b-5dbd-b0a9-a22a15a1c370/attachment.json","path":"_meta.json","size":307,"sha256":"53fbac6da220063ccab19de8a407ff579103c5053d33d1d8aec342e817a741df","contentType":"application/json; charset=utf-8"},{"id":"91e30c39-714d-5f68-881d-2fdb2713c4a3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/91e30c39-714d-5f68-881d-2fdb2713c4a3/attachment.md","path":"references/security.md","size":4518,"sha256":"3e20fc646eb01f55026c4a53f9d5f11fc3ac05fe995d5d98fdbc973992c5c77e","contentType":"text/markdown; charset=utf-8"},{"id":"8af48f86-6170-5b00-9a84-671cbd7d8089","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8af48f86-6170-5b00-9a84-671cbd7d8089/attachment.md","path":"references/supported-services.md","size":4839,"sha256":"0f04331332fc7ba17054c33ea89b918558f74014c137addd4ed66e24f50d5da5","contentType":"text/markdown; charset=utf-8"},{"id":"6ca5a68c-392e-5e29-aaeb-a71fbafe28f6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6ca5a68c-392e-5e29-aaeb-a71fbafe28f6/attachment.py","path":"scripts/cleanup.py","size":3840,"sha256":"b185b21700b8eaff224632993bfa3e1b1fd4a23519983cda6a049b4f43511bb1","contentType":"text/x-python; charset=utf-8"},{"id":"0f06e42b-3954-5524-bf91-997298e323c7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0f06e42b-3954-5524-bf91-997298e323c7/attachment.py","path":"scripts/consolidate.py","size":10039,"sha256":"60d1938616edec57d20b476a0fdf3448b67dfa874ad0903322e9fa90efef3bbd","contentType":"text/x-python; charset=utf-8"},{"id":"893fa774-4009-5aac-90c7-e57f964fd6c8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/893fa774-4009-5aac-90c7-e57f964fd6c8/attachment.py","path":"scripts/encrypt.py","size":9577,"sha256":"a703cf72dd5722a9ff75670038d9d20cb7ee0ba2318e5142b1dbfa35f0be2134","contentType":"text/x-python; charset=utf-8"},{"id":"f1f480d4-f833-54a5-9444-db43826696d3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f1f480d4-f833-54a5-9444-db43826696d3/attachment.py","path":"scripts/enforce.py","size":6870,"sha256":"f39a9662bdaeb84703971f363dcaf5ad7a9bdb9c1e3663a205ab8ba785eae49e","contentType":"text/x-python; charset=utf-8"},{"id":"6ee0a5d0-e743-5339-8309-d6a515a15911","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6ee0a5d0-e743-5339-8309-d6a515a15911/attachment.py","path":"scripts/rotation-check.py","size":7104,"sha256":"7963298d2a1045647f2d34aa0eb5e2ea4d078c3eba55d4063418c4ae844f661c","contentType":"text/x-python; charset=utf-8"},{"id":"90645a26-19e5-5eee-a791-5fbc92e6a315","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/90645a26-19e5-5eee-a791-5fbc92e6a315/attachment.py","path":"scripts/scan.py","size":9579,"sha256":"0207e5efad8bc9df319662824d71bbf5b475a04adefd0ddbced7726e7d99a7a4","contentType":"text/x-python; charset=utf-8"},{"id":"62b73ec6-1eb3-50ba-aef0-744fd6f5eaa1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/62b73ec6-1eb3-50ba-aef0-744fd6f5eaa1/attachment.sh","path":"scripts/setup-gpg.sh","size":3804,"sha256":"4e59b3974afac661fbebeb4d46bf37e15a522fdb40ce39b2f55bf489d2e58966","contentType":"application/x-sh; charset=utf-8"},{"id":"48b18e0a-42f8-5e39-b432-b3361e6530b5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/48b18e0a-42f8-5e39-b432-b3361e6530b5/attachment.py","path":"scripts/validate.py","size":12086,"sha256":"12e19285de0b5a0ba43777c27065edbc16232b8982b18cfdd621db5b9735c5fd","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"3790994c35fb0ca33bab64d32f91fcac1d16b9cc1db4def1f3470820c317920b","attachment_count":15,"text_attachments":15,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/openclaw-credential-manager/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"security","import_tag":"clean-skills-v1","description":"MANDATORY security foundation for OpenClaw. Consolidate scattered API keys and credentials into a secure .env file with proper permissions. Includes GPG encryption for high-value secrets, credential rotation tracking, deep scanning, and backup hardening. Use when setting up OpenClaw, migrating credentials, auditing security, or enforcing the .env standard. This is not optional — centralized credential management is a core requirement for secure OpenClaw deployments."}},"renderedAt":1782980044245}

Credential Manager STATUS: MANDATORY SECURITY FOUNDATION Consolidate scattered API keys and credentials into a secure, centralized file. ⚠️ This Is Not Optional Centralized credential management is a core requirement for OpenClaw security. If your credentials are scattered across multiple files, stop and consolidate them now . THE RULE: All credentials MUST be in ONLY. No workspace, no skills, no scripts directories. See: - CORE-PRINCIPLE.md - Why this is non-negotiable - CONSOLIDATION-RULE.md - The single source principle The Foundation Every OpenClaw deployment MUST have: This is your singl…