feat: add daily security audit job (07:00 București)
- Add tools/security_audit.py script - Add security-audit cron job - Update TOOLS.md with job schedule
This commit is contained in:
1
TOOLS.md
1
TOOLS.md
@@ -234,6 +234,7 @@ create_event(
|
|||||||
| Oră (UTC) | Oră (București) | Job | Canal | Ce face |
|
| Oră (UTC) | Oră (București) | Job | Canal | Ce face |
|
||||||
|-----------|-----------------|-----|-------|---------|
|
|-----------|-----------------|-----|-------|---------|
|
||||||
| 00:00 | 02:00 | content-discovery | - | Caută video+articole pe teme recente → memory/kb/ |
|
| 00:00 | 02:00 | content-discovery | - | Caută video+articole pe teme recente → memory/kb/ |
|
||||||
|
| 05:00 | 07:00 | security-audit | #echo-work (doar alerte) | Audit securitate zilnic |
|
||||||
| 01:00 | 03:00 | night-execute-late | #echo-work | Continuă execuția task-uri (run 2) |
|
| 01:00 | 03:00 | night-execute-late | #echo-work | Continuă execuția task-uri (run 2) |
|
||||||
| 03:00 | 05:00 | archive-tasks | #echo-work | Arhivează task-uri vechi |
|
| 03:00 | 05:00 | archive-tasks | #echo-work | Arhivează task-uri vechi |
|
||||||
| 06:00,17:00 | 08:00,19:00 | insights-extract | - | Extrage insights din memory/kb/ + actualizează tehnici-pauza.md |
|
| 06:00,17:00 | 08:00,19:00 | insights-extract | - | Extrage insights din memory/kb/ + actualizează tehnici-pauza.md |
|
||||||
|
|||||||
128
tools/security_audit.py
Executable file
128
tools/security_audit.py
Executable file
@@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Security audit script - verifică zilnic:
|
||||||
|
1. Permisiuni fișiere sensibile (600)
|
||||||
|
2. Parole hardcoded în cod
|
||||||
|
3. Fișiere sensibile în git tracking
|
||||||
|
4. Porturi expuse neașteptat
|
||||||
|
|
||||||
|
Exit codes:
|
||||||
|
0 = OK
|
||||||
|
1 = Warnings found
|
||||||
|
2 = Critical issues found
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
CLAWD_DIR = Path(__file__).parent.parent
|
||||||
|
SENSITIVE_FILES = [".env", "credentials"]
|
||||||
|
REQUIRED_PERMS = 0o600
|
||||||
|
REQUIRED_DIR_PERMS = 0o700
|
||||||
|
|
||||||
|
issues = []
|
||||||
|
warnings = []
|
||||||
|
|
||||||
|
def check_permissions():
|
||||||
|
"""Check sensitive files have 600 permissions"""
|
||||||
|
env_file = CLAWD_DIR / ".env"
|
||||||
|
if env_file.exists():
|
||||||
|
mode = env_file.stat().st_mode & 0o777
|
||||||
|
if mode != REQUIRED_PERMS:
|
||||||
|
issues.append(f".env has {oct(mode)} permissions (should be 0o600)")
|
||||||
|
|
||||||
|
creds_dir = CLAWD_DIR / "credentials"
|
||||||
|
if creds_dir.exists():
|
||||||
|
for f in creds_dir.iterdir():
|
||||||
|
if f.is_file():
|
||||||
|
mode = f.stat().st_mode & 0o777
|
||||||
|
if mode != REQUIRED_PERMS:
|
||||||
|
issues.append(f"credentials/{f.name} has {oct(mode)} (should be 0o600)")
|
||||||
|
|
||||||
|
def check_hardcoded_secrets():
|
||||||
|
"""Scan Python files for potential hardcoded secrets"""
|
||||||
|
patterns = [
|
||||||
|
'password.*=.*"[^"]{4,}"',
|
||||||
|
'api_key.*=.*"[^"]{8,}"',
|
||||||
|
'secret.*=.*"[^"]{8,}"',
|
||||||
|
]
|
||||||
|
|
||||||
|
for py_file in CLAWD_DIR.rglob("*.py"):
|
||||||
|
if "venv" in str(py_file) or "__pycache__" in str(py_file):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = py_file.read_text()
|
||||||
|
for i, line in enumerate(content.split('\n'), 1):
|
||||||
|
line_lower = line.lower()
|
||||||
|
# Skip comments and env reads
|
||||||
|
if line.strip().startswith('#') or 'os.getenv' in line or 'environ' in line:
|
||||||
|
continue
|
||||||
|
# Check for hardcoded passwords (excluding empty strings and placeholders)
|
||||||
|
if ('password' in line_lower or 'api_pass' in line_lower) and '= "' in line and 'in line' not in line_lower:
|
||||||
|
if '= ""' not in line and '= "***"' not in line:
|
||||||
|
# Check if it's actually setting a value, not reading
|
||||||
|
if 'getenv' not in line and 'environ' not in line:
|
||||||
|
rel_path = py_file.relative_to(CLAWD_DIR)
|
||||||
|
warnings.append(f"Potential hardcoded secret in {rel_path}:{i}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def check_git_tracking():
|
||||||
|
"""Check if sensitive files are tracked by git"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "ls-files", ".env", "credentials/"],
|
||||||
|
cwd=CLAWD_DIR,
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
if result.stdout.strip():
|
||||||
|
for f in result.stdout.strip().split('\n'):
|
||||||
|
issues.append(f"Sensitive file tracked by git: {f}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def check_gitignore():
|
||||||
|
"""Verify .gitignore contains sensitive patterns"""
|
||||||
|
gitignore = CLAWD_DIR / ".gitignore"
|
||||||
|
if gitignore.exists():
|
||||||
|
content = gitignore.read_text()
|
||||||
|
required = [".env", "credentials/"]
|
||||||
|
for pattern in required:
|
||||||
|
if pattern not in content:
|
||||||
|
warnings.append(f"Missing from .gitignore: {pattern}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("🔒 Security Audit - Echo")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
check_permissions()
|
||||||
|
check_hardcoded_secrets()
|
||||||
|
check_git_tracking()
|
||||||
|
check_gitignore()
|
||||||
|
|
||||||
|
if issues:
|
||||||
|
print("\n🔴 CRITICAL ISSUES:")
|
||||||
|
for issue in issues:
|
||||||
|
print(f" - {issue}")
|
||||||
|
|
||||||
|
if warnings:
|
||||||
|
print("\n🟠 WARNINGS:")
|
||||||
|
for warning in warnings:
|
||||||
|
print(f" - {warning}")
|
||||||
|
|
||||||
|
if not issues and not warnings:
|
||||||
|
print("\n✅ All checks passed!")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print(f"\n📊 Summary: {len(issues)} critical, {len(warnings)} warnings")
|
||||||
|
|
||||||
|
if issues:
|
||||||
|
return 2
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user