audit-code install-tools.sh:
- Buffer the opengrep release JSON before grep -m1; curl died with (23)
under pipefail when grep quit early.
- Use ${m}: in the PowerShell block; $m: parsed as a scope-qualified var.
- On Arch, skip paru/yay when pacman -Q shows every package installed,
since --needed still invokes sudo.
- Add --check-only (fast, installs nothing, non-zero naming missing tools)
and --user-only (no system package managers, no sudo).
log-run.py (both skills): put the skill dir on sys.path so running it as
a script from any cwd no longer raises ModuleNotFoundError.
audit-terraform: move deps from requirements.txt into pyproject
dependency groups and add scripts/install-tools.sh (uv sync --group tools,
then check trivy, tflint, tofu, terragrunt, gh).
Both SKILL.md files gain a 0.5 Preflight step and call scripts through
uv run --project ${SKILL_DIR}. tools_unavailable is now a map of tool to
exact install command; audit-terraform skips trivy when absent and stops
with an install hint instead of crashing when tofu/terragrunt is missing.
190 lines
8.4 KiB
Python
190 lines
8.4 KiB
Python
from scripts.slicing import slice_for_agent
|
|
|
|
|
|
def _manifest_dict():
|
|
return {
|
|
"mode": "local",
|
|
"base_ref": "origin/main",
|
|
"head_ref": "HEAD",
|
|
"default_branch": "main",
|
|
"language_breakdown": {"python": 1, "javascript": 0, "typescript": 0, "csharp": 0, "skipped_files": []},
|
|
"changed_files": [{"path": "src/api.py", "language": "python", "added_lines": [[10, 15]]}],
|
|
"findings": [
|
|
{"tool": "bandit", "rule_id": "B608", "severity": "high", "file": "src/api.py", "line": 12, "end_line": 12, "message": "sqlinj"},
|
|
{"tool": "mypy", "rule_id": "arg-type", "severity": "medium", "file": "src/api.py", "line": 14, "end_line": 14, "message": "type"},
|
|
{"tool": "gitleaks", "rule_id": "aws-token", "severity": "critical", "file": "src/api.py", "line": 11, "end_line": 11, "message": "leak"},
|
|
{"tool": "pip-audit", "rule_id": "GHSA-x", "severity": "high", "file": "requirements.txt", "line": 1, "end_line": 1, "message": "vuln"},
|
|
{"tool": "opengrep", "rule_id": "py.dangerous","severity": "high", "file": "src/api.py", "line": 13, "end_line": 13, "message": "dangerous"},
|
|
{"tool": "ruff", "rule_id": "S102", "severity": "high", "file": "src/api.py", "line": 10, "end_line": 10, "message": "issue"},
|
|
{"tool": "ruff-idiom", "rule_id": "SIM102", "severity": "low", "file": "src/api.py", "line": 20, "end_line": 20, "message": "collapsible-if"},
|
|
],
|
|
"package_diffs": {"python": {"added": [], "removed": [], "upgraded": []}},
|
|
"tool_stats": {},
|
|
"tools_unavailable": {},
|
|
"errors": [],
|
|
}
|
|
|
|
|
|
def test_security_slice_only_security_tools():
|
|
sliced = slice_for_agent(_manifest_dict(), "security-triage")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert tools == {"bandit", "opengrep", "ruff"}
|
|
|
|
|
|
def test_type_safety_slice_only_type_tools():
|
|
sliced = slice_for_agent(_manifest_dict(), "type-safety")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert tools == {"mypy"}
|
|
|
|
|
|
def test_dependency_slice_includes_dep_tools_and_diffs():
|
|
sliced = slice_for_agent(_manifest_dict(), "dependency")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert tools == {"pip-audit"}
|
|
assert "package_diffs" in sliced
|
|
|
|
|
|
def test_secrets_slice_only_gitleaks():
|
|
sliced = slice_for_agent(_manifest_dict(), "secrets")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert tools == {"gitleaks"}
|
|
|
|
|
|
def test_consistency_slice_excludes_non_idiom_tools():
|
|
sliced = slice_for_agent(_manifest_dict(), "consistency")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
non_idiom = {"bandit", "mypy", "gitleaks", "pip-audit", "opengrep", "ruff"}
|
|
assert tools & non_idiom == set()
|
|
assert "changed_files" in sliced
|
|
|
|
|
|
def test_consistency_slice_includes_ruff_idiom():
|
|
sliced = slice_for_agent(_manifest_dict(), "consistency")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert tools == {"ruff-idiom"}
|
|
rule_ids = {f["rule_id"] for f in sliced["findings"]}
|
|
assert "SIM102" in rule_ids
|
|
|
|
|
|
def test_security_slice_excludes_ruff_idiom():
|
|
sliced = slice_for_agent(_manifest_dict(), "security-triage")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert "ruff-idiom" not in tools
|
|
assert tools == {"bandit", "opengrep", "ruff"}
|
|
|
|
|
|
def _manifest_with_lua_gha():
|
|
"""Manifest with selene, luac, actionlint, and zizmor findings."""
|
|
return {
|
|
"mode": "local",
|
|
"base_ref": "origin/main",
|
|
"head_ref": "HEAD",
|
|
"default_branch": "main",
|
|
"language_breakdown": {
|
|
"python": 0, "javascript": 0, "typescript": 0, "csharp": 0,
|
|
"lua": 1, "github_actions": 1, "skipped_files": [],
|
|
},
|
|
"changed_files": [
|
|
{"path": "src/game.lua", "language": "lua", "added_lines": [[5, 10]]},
|
|
{"path": ".github/workflows/ci.yml", "language": "github-actions", "added_lines": [[12, 12]]},
|
|
],
|
|
"findings": [
|
|
{"tool": "selene", "rule_id": "undefined_variable", "severity": "high", "file": "src/game.lua", "line": 5, "end_line": 5, "message": "undefined var"},
|
|
{"tool": "luac", "rule_id": "syntax-error", "severity": "critical","file": "src/game.lua", "line": 8, "end_line": 8, "message": "syntax error"},
|
|
{"tool": "actionlint", "rule_id": "expression", "severity": "high", "file": ".github/workflows/ci.yml", "line": 12, "end_line": 12, "message": "bad expr"},
|
|
{"tool": "zizmor", "rule_id": "unpinned-uses", "severity": "medium", "file": ".github/workflows/ci.yml", "line": 15, "end_line": 15, "message": "unpin"},
|
|
],
|
|
"package_diffs": {},
|
|
"tool_stats": {},
|
|
"tools_unavailable": {},
|
|
"errors": [],
|
|
}
|
|
|
|
|
|
def test_gha_reviewer_slice_only_gha_tools():
|
|
sliced = slice_for_agent(_manifest_with_lua_gha(), "gha-reviewer")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert tools == {"actionlint", "zizmor"}
|
|
|
|
|
|
def test_gha_reviewer_slice_excludes_lua_tools():
|
|
sliced = slice_for_agent(_manifest_with_lua_gha(), "gha-reviewer")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert "selene" not in tools
|
|
assert "luac" not in tools
|
|
|
|
|
|
def test_selene_routes_to_maintainability():
|
|
sliced = slice_for_agent(_manifest_with_lua_gha(), "maintainability")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert "selene" in tools
|
|
|
|
|
|
def test_luac_routes_to_security_triage():
|
|
sliced = slice_for_agent(_manifest_with_lua_gha(), "security-triage")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert "luac" in tools
|
|
|
|
|
|
def test_luac_routes_to_maintainability():
|
|
sliced = slice_for_agent(_manifest_with_lua_gha(), "maintainability")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert "luac" in tools
|
|
|
|
|
|
def test_security_triage_excludes_gha_tools():
|
|
sliced = slice_for_agent(_manifest_with_lua_gha(), "security-triage")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert "actionlint" not in tools
|
|
assert "zizmor" not in tools
|
|
|
|
|
|
def _manifest_with_powershell():
|
|
"""Manifest with psscriptanalyzer (security + style rules) and injectionhunter."""
|
|
return {
|
|
"mode": "local",
|
|
"base_ref": "origin/main",
|
|
"head_ref": "HEAD",
|
|
"default_branch": "main",
|
|
"language_breakdown": {
|
|
"python": 0, "javascript": 0, "typescript": 0, "csharp": 0,
|
|
"lua": 0, "powershell": 1, "github_actions": 0, "skipped_files": [],
|
|
},
|
|
"changed_files": [
|
|
{"path": "scripts/Deploy.ps1", "language": "powershell", "added_lines": [[10, 40]]},
|
|
],
|
|
"findings": [
|
|
{"tool": "psscriptanalyzer", "rule_id": "PSAvoidUsingInvokeExpression", "severity": "medium", "file": "scripts/Deploy.ps1", "line": 12, "end_line": 12, "message": "iex"},
|
|
{"tool": "psscriptanalyzer", "rule_id": "PSAvoidUsingWriteHost", "severity": "medium", "file": "scripts/Deploy.ps1", "line": 5, "end_line": 5, "message": "write-host"},
|
|
{"tool": "injectionhunter", "rule_id": "InjectionRisk.InvokeExpression","severity": "high", "file": "scripts/Deploy.ps1", "line": 12, "end_line": 12, "message": "injection"},
|
|
],
|
|
"package_diffs": {},
|
|
"tool_stats": {},
|
|
"tools_unavailable": {},
|
|
"errors": [],
|
|
}
|
|
|
|
|
|
def test_injectionhunter_routes_to_security_triage():
|
|
sliced = slice_for_agent(_manifest_with_powershell(), "security-triage")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert "injectionhunter" in tools
|
|
|
|
|
|
def test_psscriptanalyzer_security_rules_route_to_security_triage():
|
|
sliced = slice_for_agent(_manifest_with_powershell(), "security-triage")
|
|
rules = {f["rule_id"] for f in sliced["findings"] if f["tool"] == "psscriptanalyzer"}
|
|
assert rules == {"PSAvoidUsingInvokeExpression"}
|
|
|
|
|
|
def test_psscriptanalyzer_style_rules_route_to_maintainability():
|
|
sliced = slice_for_agent(_manifest_with_powershell(), "maintainability")
|
|
rules = {f["rule_id"] for f in sliced["findings"] if f["tool"] == "psscriptanalyzer"}
|
|
assert rules == {"PSAvoidUsingWriteHost"}
|
|
|
|
|
|
def test_maintainability_excludes_injectionhunter():
|
|
sliced = slice_for_agent(_manifest_with_powershell(), "maintainability")
|
|
tools = {f["tool"] for f in sliced["findings"]}
|
|
assert "injectionhunter" not in tools
|