2860fb5d18
Security / Dependency review (push) Has been skipped
Scorecard / Scorecard analysis (push) Failing after 0s
Validate / eval (push) Failing after 0s
Security / Dependency audit (push) Failing after 1s
Security / Secret scan (push) Failing after 1s
Validate / tests (push) Failing after 0s
Validate / mcp-tests (push) Failing after 1s
GitHub Actions Security Analysis with zizmor 🌈 / zizmor (push) Failing after 1s
Security / SAST scan (push) Failing after 13m52s
66 lines
2.3 KiB
Python
66 lines
2.3 KiB
Python
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
WORKFLOW = ROOT / ".github" / "workflows" / "security.yml"
|
|
# AGENTS.md is the canonical agent-guidance file; CLAUDE.md is a one-line
|
|
# pointer (`@AGENTS.md`) so anything Claude Code-shaped reads the same source.
|
|
AGENTS = ROOT / "AGENTS.md"
|
|
|
|
|
|
def _workflow_text() -> str:
|
|
return WORKFLOW.read_text(encoding="utf-8")
|
|
|
|
|
|
def test_security_workflow_exists() -> None:
|
|
assert WORKFLOW.is_file()
|
|
|
|
|
|
def test_security_workflow_runs_dependency_audit_as_blocking_check() -> None:
|
|
text = _workflow_text()
|
|
dependency_audit_job = text.split("dependency-audit:", 1)[1].split("secret-scan:", 1)[0]
|
|
|
|
assert "dependency-audit:" in text
|
|
assert "uv audit --locked" in dependency_audit_job
|
|
assert "continue-on-error: true" not in dependency_audit_job
|
|
|
|
|
|
def test_security_workflow_runs_secret_scan_for_pull_requests_and_main_pushes() -> None:
|
|
text = _workflow_text()
|
|
secret_scan_job = text.split("secret-scan:", 1)[1].split("sast-scan:", 1)[0]
|
|
|
|
assert "secret-scan:" in text
|
|
assert "pull_request:" in text
|
|
assert "push:" in text
|
|
assert "workflow_dispatch:" in text
|
|
assert "branches:\n - main" in text
|
|
assert "trufflesecurity/trufflehog" in secret_scan_job
|
|
assert "version: 3.95.5" in secret_scan_job
|
|
assert "extra_args: --results=verified" in secret_scan_job
|
|
assert "continue-on-error: true" not in secret_scan_job
|
|
assert "if: github.event_name" not in secret_scan_job
|
|
assert "path: ./" not in secret_scan_job
|
|
|
|
|
|
def test_security_workflow_runs_sast_without_unused_code_scanning_permission() -> None:
|
|
text = _workflow_text()
|
|
sast_job = text.split("sast-scan:", 1)[1]
|
|
|
|
assert "semgrep/semgrep@sha256:" in sast_job
|
|
assert "semgrep scan --config=auto" in sast_job
|
|
assert "SEMGREP_SEND_METRICS: off" in sast_job
|
|
assert "continue-on-error: true" in sast_job
|
|
assert "contents: read" in sast_job
|
|
assert "security-events: write" not in sast_job
|
|
assert "--sarif" not in sast_job
|
|
assert "upload-sarif" not in sast_job
|
|
|
|
|
|
def test_agent_guidance_mentions_secret_hygiene() -> None:
|
|
text = AGENTS.read_text(encoding="utf-8")
|
|
|
|
assert "Security hygiene" in text
|
|
assert "Never commit real API keys" in text
|
|
assert "skills/last30days/scripts/lib/env.py" in text
|
|
assert "fixtures" in text
|