mirror of
https://github.com/Heretek-AI/heretek-actions.git
synced 2026-10-12 02:03:46 +00:00
123 lines
4.9 KiB
Python
123 lines
4.9 KiB
Python
"""Pre-execution agent security guard and secret scanner.
|
|
|
|
Scans codebase, configuration files, and prompt payloads for:
|
|
- Hardcoded secrets (Anthropic API keys, GitHub PATs, JWTs, AWS keys)
|
|
- Prompt injection attack vectors
|
|
- Path traversal and dangerous command injections
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
SECRET_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
|
|
("Anthropic API Key", re.compile(r"sk-ant-[A-Za-z0-9_-]{20,}")),
|
|
("GitHub Personal Access Token", re.compile(r"ghp_[A-Za-z0-9]{20,}")),
|
|
("GitHub OAuth Token", re.compile(r"gho_[A-Za-z0-9]{20,}")),
|
|
("AWS Access Key ID", re.compile(r"AKIA[0-9A-Z]{16}")),
|
|
("JSON Web Token (JWT)", re.compile(r"eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}")), # noqa: E501
|
|
("Generic Secret Key", re.compile(r"\bsk-(?!ant-)[A-Za-z0-9_-]{20,}")),
|
|
]
|
|
|
|
PROMPT_INJECTION_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
|
|
("System Override Vector", re.compile(r"ignore\s+all\s+previous\s+instructions", re.IGNORECASE)), # noqa: E501
|
|
("Role Hijack Vector", re.compile(r"you\s+are\s+now\s+an\s+unrestricted\s+ai", re.IGNORECASE)),
|
|
("Exfiltration Vector", re.compile(r"curl\s+-X\s+POST.*-d\s+@/etc/passwd", re.IGNORECASE)),
|
|
]
|
|
|
|
|
|
def scan_text_content(text: str, filename: str = "<stdin>") -> list[dict[str, Any]]:
|
|
"""Scan string content for security violations.
|
|
|
|
Args:
|
|
text: Target text to scan.
|
|
filename: Name of file or stream being scanned.
|
|
|
|
Returns:
|
|
List of violation dictionaries containing type, category, and match detail.
|
|
"""
|
|
violations: list[dict[str, Any]] = []
|
|
|
|
for name, pattern in SECRET_PATTERNS:
|
|
for match in pattern.finditer(text):
|
|
secret_str = match.group(0)
|
|
redacted = secret_str[:4] + "..." + secret_str[-4:]
|
|
violations.append({
|
|
"type": "hardcoded_secret",
|
|
"category": name,
|
|
"file": filename,
|
|
"detail": redacted,
|
|
})
|
|
|
|
for name, pattern in PROMPT_INJECTION_PATTERNS:
|
|
for match in pattern.finditer(text):
|
|
violations.append({
|
|
"type": "prompt_injection",
|
|
"category": name,
|
|
"file": filename,
|
|
"detail": match.group(0)[:40],
|
|
})
|
|
|
|
return violations
|
|
|
|
|
|
def scan_directory(target_dir: Path) -> list[dict[str, Any]]:
|
|
"""Scan directory recursively for security violations in source files.
|
|
|
|
Args:
|
|
target_dir: Absolute path to directory.
|
|
|
|
Returns:
|
|
List of all detected security violations.
|
|
"""
|
|
violations: list[dict[str, Any]] = []
|
|
ignore_dirs = {".git", ".venv", "__pycache__", "node_modules", "results", "tests", "test", "fixtures"} # noqa: E501
|
|
|
|
for p in target_dir.rglob("*"):
|
|
if p.is_file() and not any(part in ignore_dirs for part in p.parts):
|
|
if p.suffix in (".py", ".json", ".yaml", ".yml", ".md", ".sh", ".txt", ".js", ".ts"):
|
|
try:
|
|
content = p.read_text(encoding="utf-8", errors="ignore")
|
|
v = scan_text_content(content, filename=str(p.relative_to(target_dir)))
|
|
violations.extend(v)
|
|
except OSError:
|
|
continue
|
|
|
|
return violations
|
|
|
|
|
|
def build_arg_parser() -> argparse.ArgumentParser:
|
|
"""Build argument parser for security_scan CLI."""
|
|
parser = argparse.ArgumentParser(prog="security_scan")
|
|
parser.add_argument("--target", type=Path, default=Path("."))
|
|
return parser
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
"""CLI entrypoint for security scanner."""
|
|
args = build_arg_parser().parse_args(argv)
|
|
target = args.target.resolve()
|
|
|
|
violations = scan_directory(target)
|
|
|
|
print("┌──────────────────────────────────────────────────────────┐")
|
|
print("│ AGENT SECURITY HARNESS AUDIT REPORT │")
|
|
print("├──────────────────────────────────────────────────────────┤")
|
|
print(f"│ Target Directory : {str(target):<38} │")
|
|
print(f"│ Total Violations : {len(violations):<38} │")
|
|
if violations:
|
|
print("├──────────────────────────────────────────────────────────┤")
|
|
for v in violations[:10]:
|
|
print(f"│ [WARN] {v['type']}: {v['category']} in {v['file']:<20} │")
|
|
print("└──────────────────────────────────────────────────────────┘")
|
|
|
|
return 1 if violations else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|