Install
openclaw skills install argus1Argus â The Hundred-Eyed. Scan your Python or JavaScript codebase for bugs, security vulnerabilities, code smells, and common anti-patterns â get a prioritised findings report with line numbers, severity ratings, and fix suggestions. No external tools required.
openclaw skills install argus1Scan any Python or JavaScript codebase for bugs, security holes, and anti-patterns in seconds â with line numbers, severity ratings, and actionable fix suggestions.
Security â SQL injection patterns, hardcoded secrets, eval/exec abuse, innerHTML XSS, open redirects, pickle deserialization
Bugs â mutable default arguments, bare except clauses, missing null checks, == None comparisons
Code quality â broad imports, print statements in production, TODO/FIXME markers, deeply nested code
JavaScript â var usage, loose equality (==), console.log left in, prototype pollution patterns
SOURCE_PATH="./src" LANGUAGE="python" SEVERITY_FILTER="high" openclaw skills run argus1
Reads local files only. Nothing transmitted outside your machine.
pip3 install rich --break-system-packages --quiet
import os, re, json
from pathlib import Path
from datetime import date
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich import box
console = Console()
SRC_PATH = os.environ.get("SOURCE_PATH", ".").strip()
LANGUAGE = os.environ.get("LANGUAGE", "auto").lower().strip()
SEV_FILTER = os.environ.get("SEVERITY_FILTER", "all").lower().strip()
try:
MAX_FINDINGS = int(os.environ.get("MAX_FINDINGS", "50"))
except ValueError:
MAX_FINDINGS = 50
OUTPUT_JSON = os.environ.get("OUTPUT_JSON", "false").lower() == "true"
TODAY = date.today()
src = Path(SRC_PATH)
if not src.exists():
console.print(f"[red]â Path not found: {SRC_PATH}[/red]")
raise SystemExit(1)
def detect_lang(path: Path) -> str:
py = len(list(path.rglob("*.py") if path.is_dir() else ([path] if str(path).endswith(".py") else [])))
js = len(list(path.rglob("*.js") if path.is_dir() else ([path] if str(path).endswith(".js") else [])))
if py >= js:
return "python"
return "javascript"
lang = LANGUAGE if LANGUAGE != "auto" else detect_lang(src)
ext = "*.py" if lang == "python" else "*.js"
# ââ Rule definitions: (id, severity, category, regex, message, fix) ââââââââââ
PYTHON_RULES = [
("PY001", "critical", "security", r"\beval\s*\(", "eval() executes arbitrary code â extremely dangerous with user input.", "Use ast.literal_eval() for safe evaluation of literals."),
("PY002", "critical", "security", r"\bexec\s*\(", "exec() executes arbitrary strings as Python code.", "Refactor to avoid dynamic code execution."),
("PY003", "critical", "security", r"\bpickle\.loads?\s*\(", "pickle.load() can execute arbitrary code when deserialising untrusted data.", "Use json.loads() or a safe serialisation format instead."),
("PY004", "high", "security", r"(?i)(password|secret|api_key|token|auth_key)\s*=\s*['\"].+['\"]", "Hardcoded credential detected.", "Store secrets in environment variables, never in source code."),
("PY005", "high", "security", r"shell\s*=\s*True", "shell=True in subprocess is a command-injection risk.", "Pass a list of arguments instead: subprocess.run(['cmd', 'arg'])"),
("PY006", "high", "security", r"\.execute\s*\(.*(%|\.format\(|f['\"])", "Potential SQL injection via string formatting in execute().", "Use parameterised queries: cursor.execute(sql, (param,))"),
("PY007", "medium", "bug", r"def\s+\w+\s*\([^)]*=\s*\[\s*\]", "Mutable default argument [] â shared across all calls.", "Use None as default and initialise inside the function."),
("PY008", "medium", "bug", r"def\s+\w+\s*\([^)]*=\s*\{\s*\}", "Mutable default argument {} â shared across all calls.", "Use None as default and initialise inside the function."),
("PY009", "medium", "bug", r"except\s*:", "Bare except: catches SystemExit and KeyboardInterrupt â almost never intentional.", "Catch specific exceptions: except ValueError: or except Exception:"),
("PY010", "medium", "bug", r"==\s*None\b|\bNone\s*==", "Use 'is None' instead of '== None'.", "Replace '== None' with 'is None' and '!= None' with 'is not None'."),
("PY011", "medium", "bug", r"!=\s*None\b|\bNone\s*!=", "Use 'is not None' instead of '!= None'.", "Replace '!= None' with 'is not None'."),
("PY012", "medium", "quality", r"\bprint\s*\(", "print() statement found â likely debug output left in production code.", "Remove or interface in logging.debug() / logging.info()."),
("PY013", "low", "quality", r"#\s*(TODO|FIXME|HACK|XXX|BUG)\b", "Unresolved TODO/FIXME marker.", "Resolve or create a tracked issue for this item."),
("PY014", "low", "quality", r"from\s+\w+\s+import\s+\*", "Wildcard import pollutes namespace and makes dependencies unclear.", "Import only what you need: from module import specific_name"),
("PY015", "low", "quality", r"import\s+pdb\b", "Debugger import (pdb) left in code.", "Remove pdb import before committing to production."),
("PY016", "medium", "security", r"hashlib\.(md5|sha1)\s*\(", "MD5/SHA1 are cryptographically weak â not suitable for passwords or signatures.", "Use hashlib.sha256() or better; bcrypt/argon2 for passwords."),
("PY017", "high", "security", r"random\.(random|randint|choice)\s*\(", "random module is not cryptographically secure.", "Use secrets module for security-sensitive random values."),
("PY018", "medium", "bug", r"open\s*\(.+\)(?!\s*as\b)(?!.*with\b)", "File opened without context manager â may leak file handle.", "Use 'with open(...) as f:' to ensure proper cleanup."),
("PY019", "low", "quality", r"lambda\s+\w+:\s*\w+\s*\(", "Lambda used where a named function would be clearer.", "Consider replacing with a def statement for readability."),
("PY020", "medium", "bug", r"\btype\s*\(^w+\)\s*==\s*", "Use isinstance() instead of type() == for type checks.", "Replace type(x) == SomeType with isinstance(x, SomeType)."),
]
JS_RULES = [
("JS001", "high", "quality", r"\bvar\s+", "var is function-scoped and hoisted â causes subtle bugs.", "Use const or let instead."),
("JS002", "critical", "security", r"\.innerHTML\s*=", "innerHTML assignment is an XSS vector if content comes from user input.", "Use textContent for text or DOMPurify to sanitise HTML."),
("JS003", "critical", "security", r"\beval\s*\(", "eval() executes arbitrary JavaScript â a critical security risk.", "Refactor to eliminate eval() usage entirely."),
("JS004", "high", "security", r"(?i)(api_key|apikey|api_secret|access_token|auth_token|password)\s*=\s*['\"].+['\"]", "Hardcoded credential detected.", "Move secrets to environment variables or a secrets manager."),
("JS005", "medium", "bug", r"==\s*null\b(?!\=)|null\s*==(?!\=)", "Loose equality with null also matches undefined â usually unintentional.", "Use === null or check explicitly for both null and undefined."),
("JS006", "medium", "bug", r"(?<!=)==(?!=)", "Loose equality (==) performs type coercion â a common source of bugs.", "Use strict equality (===) instead."),
("JS007", "medium", "quality", r"\bconsole\.(log|warn|error|debug)\s*\(", "console.log left in production code.", "Remove or replace with a proper logging library."),
("JS008", "medium", "security", r"document\.write\s*\(", "document.write() can overwrite the entire page and is an XSS risk.", "Use DOM manipulation methods (createElement, appendChild) instead."),
("JS009", "low", "quality", r"//\s*(TODO|FIXME|HACK|XXX|BUG)\b", "Unresolved TODO/FIXME marker.", "Resolve or create a tracked issue for this item."),
("JS010", "high", "security", r"__proto__\s*=|Object\.assign\s*\(\s*\{\s*\},", "Potential prototype pollution pattern.", "Validate that object keys don't include __proto__ or constructor."),
("JS011", "medium", "bug", r"setTimeout\s*\(\s*['\"]", "setTimeout with a string argument calls eval internally.", "Pass a function reference instead: setTimeout(() => doThing(), ms)"),
("JS012", "low", "quality", r"function\s+\w+\s*\([^)]{60,}\)", "Function has too many parameters â consider an options object.", "Refactor long parameter lists into a single options/config object."),
]
RULES = PYTHON_RULES if lang == "python" else JS_RULES
SEV_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3}
SEV_FILTER_LEVEL = {"all": 3, "low": 3, "medium": 2, "high": 1, "critical": 0}.get(SEV_FILTER, 3)
files = list(src.rglob(ext) if src.is_dir() else [src])
if not files:
console.print(f"[yellow]No {ext} files found in {SRC_PATH}[/yellow]")
raise SystemExit(0)
console.print()
console.print(Panel.fit(
f"[bold red]ð ðï¸ Argus â Code Intelligence Scanner[/bold red]\n"
f"Scanning [yellow]{len(files)}[/yellow] {lang} file(s) in [cyan]{SRC_PATH}[/cyan] "
f"| Severity: [white]{SEV_FILTER}[/white] | Rules: [white]{len(RULES)}[/white]",
border_style="red"
))
findings = []
for filepath in files:
try:
source = filepath.read_text(encoding="utf-8", errors="replace")
lines = source.splitlines()
for lineno, line in enumerate(lines, 1):
for rule_id, sev, category, pattern, message, fix in RULES:
if re.search(pattern, line):
if SEV_ORDER.get(sev, 3) <= SEV_FILTER_LEVEL:
findings.append({
"id": rule_id,
"severity": sev,
"category": category,
"file": str(filepath.relative_to(src) if src.is_dir() else filepath),
"line": lineno,
"code": line.strip()[:80],
"message": message,
"fix": fix,
})
except Exception as e:
console.print(f"[dim]Skipping {filepath}: {e}[/dim]")
# Deduplicate by (rule, file, line)
seen = set()
unique = []
for f in findings:
key = (f["id"], f["file"], f["line"])
if key not in seen:
seen.add(key)
unique.append(f)
# Sort by severity then file/line
unique.sort(key=lambda f: (SEV_ORDER.get(f["severity"], 3), f["file"], f["line"]))
display = unique[:MAX_FINDINGS]
# ââ Display results âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
SEV_COLOUR = {"critical": "red", "high": "orange3", "medium": "yellow", "low": "dim"}
if not unique:
console.print(Panel(
"[green]â
No issues found![/green]\n\n"
f"Scanned {len(files)} file(s) with {len(RULES)} rules â all clear.",
border_style="green"
))
else:
display_count = len(display)
title_suffix = f" â showing {display_count} of {len(unique)}" if len(unique) > MAX_FINDINGS else f" â {len(unique)} found"
findings_table = Table(
title=f"ð Findings{title_suffix}",
box=box.ROUNDED, border_style="red", show_lines=True
)
findings_table.add_column("ID", style="dim", width=7)
findings_table.add_column("Sev", style="white", width=9)
findings_table.add_column("Cat", style="cyan", width=10)
findings_table.add_column("File:Line",style="yellow", width=28)
findings_table.add_column("Issue", style="white", width=45)
for fi in display:
sc = SEV_COLOUR.get(fi["severity"], "white")
loc = f"{fi['file'][-24:]}:{fi['line']}" if len(fi["file"]) > 24 else f"{fi['file']}:{fi['line']}"
findings_table.add_row(
fi["id"],
f"[{sc}]{fi['severity'].upper()}[/{sc}]",
fi["category"],
loc,
fi["message"][:43],
)
console.print(findings_table)
# Detailed panels for critical/high
critical_high = [f for f in display if f["severity"] in ("critical", "high")]
if critical_high:
console.print()
for fi in critical_high[:5]:
sc = SEV_COLOUR.get(fi["severity"], "white")
console.print(Panel(
f"[dim]File:[/dim] {fi['file']}:{fi['line']}\n"
f"[dim]Code:[/dim] [italic]{fi['code']}[/italic]\n\n"
f"[white]{fi['message']}[/white]\n\n"
f"[green]Fix:[/green] {fi['fix']}",
title=f"[{sc}][bold]{fi['severity'].upper()}[/bold][/{sc}] â {fi['id']}",
border_style=sc
))
# Summary by severity
console.print()
sev_counts = {s: sum(1 for f in unique if f["severity"] == s)
for s in ("critical", "high", "medium", "low")}
summary_lines = " ".join(
f"[{SEV_COLOUR[s]}]{s.title()}: {sev_counts[s]}[/{SEV_COLOUR[s]}]"
for s in ("critical", "high", "medium", "low") if sev_counts[s]
)
console.print(Panel(
f"Files scanned: [yellow]{len(files)}[/yellow] "
f"Total issues: [red]{len(unique)}[/red] {summary_lines}",
title="Summary",
border_style="cyan"
))
# ââ Save outputs ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
report_file = f"argus_report_{TODAY}.md"
with open(report_file, "w", encoding="utf-8") as f:
f.write(f"# ð Argus Code Scan Report â {TODAY}\n\n")
f.write(f"**Path:** `{SRC_PATH}` **Language:** {lang} **Files:** {len(files)} **Issues:** {len(unique)}\n\n")
sev_counts = {s: sum(1 for fi in unique if fi["severity"] == s) for s in ("critical","high","medium","low")}
f.write(f"**Critical:** {sev_counts['critical']} **High:** {sev_counts['high']} **Medium:** {sev_counts['medium']} **Low:** {sev_counts['low']}\n\n")
f.write("## Findings\n\n| ID | Severity | Category | File:Line | Issue |\n|---|---|---|---|---|\n")
for fi in unique:
f.write(f"| {fi['id']} | {fi['severity'].upper()} | {fi['category']} | {fi['file'][:30]}:{fi['line']} | {fi['message'][:60]} |\n")
f.write("\n## Fix Suggestions\n\n")
for fi in [x for x in unique if x["severity"] in ("critical","high")]:
f.write(f"### {fi['id']} â {fi['file']}:{fi['line']}\n\n")
f.write(f"**Issue:** {fi['message']}\n\n")
f.write(f"**Fix:** {fi['fix']}\n\n")
f.write(f"```\n{fi['code']}\n```\n\n")
if OUTPUT_JSON:
json_file = f"argus_report_{TODAY}.json"
with open(json_file, "w", encoding="utf-8") as f:
json.dump({"scanned": str(TODAY), "path": SRC_PATH, "language": lang,
"files": len(files), "findings": unique}, f, indent=2)
console.print(f"[dim]JSON saved to {json_file}[/dim]")
console.print(Panel(
f"[green]â
Scan complete![/green] Report saved to [cyan]{report_file}[/cyan]",
border_style="green"
))