144 lines
5.8 KiB
Python
144 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate a sanitized quarterly security audit report template."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
from datetime import date, datetime
|
|
from pathlib import Path
|
|
|
|
|
|
VALID_QUARTERS = {"Q1", "Q2", "Q3", "Q4"}
|
|
|
|
|
|
def _parse_date(value: str | None) -> date:
|
|
if value is None:
|
|
return date.today()
|
|
try:
|
|
return datetime.strptime(value, "%Y-%m-%d").date()
|
|
except ValueError as exc:
|
|
raise argparse.ArgumentTypeError("date must use YYYY-MM-DD format") from exc
|
|
|
|
|
|
def _quarter_for(month: int) -> str:
|
|
if month <= 3:
|
|
return "Q1"
|
|
if month <= 6:
|
|
return "Q2"
|
|
if month <= 9:
|
|
return "Q3"
|
|
return "Q4"
|
|
|
|
|
|
def build_report(*, audit_date: date, quarter: str | None = None) -> str:
|
|
"""Build a deterministic markdown report template."""
|
|
selected_quarter = (quarter or _quarter_for(audit_date.month)).upper()
|
|
if selected_quarter not in VALID_QUARTERS:
|
|
msg = f"quarter must be one of {', '.join(sorted(VALID_QUARTERS))}"
|
|
raise ValueError(msg)
|
|
|
|
title = f"{audit_date:%Y} {selected_quarter} Security Audit"
|
|
return f"""# {title}
|
|
|
|
Date: {audit_date.isoformat()}
|
|
Scope: ComfyUI-Doctor repository, ComfyUI custom-node runtime boundaries, local validation tooling, and public documentation.
|
|
|
|
## Audit Rules
|
|
|
|
- Do not include secrets, tokens, cookies, private URLs, private hostnames, user data, local state contents, generated reports with sensitive paths, or workstation-specific details.
|
|
- Record only sanitized command summaries, pass/fail status, affected public file paths, and public-safe remediation notes.
|
|
- Keep raw scanner output and target-specific evidence in maintainer-private storage.
|
|
- Run active or passive DAST only against targets you own or are explicitly authorized to test.
|
|
|
|
## Automated Checks
|
|
|
|
| Check | Command or Source | Status | Notes |
|
|
| --- | --- | --- | --- |
|
|
| Supply-chain scanner | `python scripts/check_supply_chain.py --skip-install-trees` | Pending | Required repo-local gate |
|
|
| Outbound safety | `python scripts/check_outbound_safety.py` | Pending | Required repo-local gate |
|
|
| Focused security gate | `python scripts/focused_gate.py --fast` | Pending | Plugin, metadata, dependency-policy, outbound-payload checks |
|
|
| Full local acceptance gate | `scripts/run_full_tests_windows.ps1` or `scripts/run_full_tests_linux.sh` | Pending | Required before accepting remediation changes |
|
|
| Semgrep SAST | `semgrep scan` in scheduled workflow | Pending | Optional when Semgrep install/network is available |
|
|
| Snyk dependency scan | `snyk test` in scheduled workflow | Pending | Optional when `SNYK_TOKEN` is configured |
|
|
| ZAP baseline scan | `zap-baseline.py` in scheduled workflow | Pending | Optional; requires authorized target URL |
|
|
|
|
## Manual Security Scenarios
|
|
|
|
| Scenario | Coverage Target | Status | Sanitized Result |
|
|
| --- | --- | --- | --- |
|
|
| SSRF metadata endpoint attempts | Provider base URL validation and redirect handling | Pending | |
|
|
| SSRF private/internal IP attempts | DNS resolution and private-target blocking | Pending | |
|
|
| XSS chat input attempts | Chat rendering and message sanitization | Pending | |
|
|
| XSS settings field attempts | Settings rendering and saved field handling | Pending | |
|
|
| Path traversal attempts | File/path inputs, report/history access, plugin trust scanning | Pending | |
|
|
| Admin bypass attempts | Write-sensitive endpoints and strict-token mode | Pending | |
|
|
| Credential exposure attempts | Server-side key store, status APIs, logs, and UI state | Pending | |
|
|
| Telemetry privacy review | Local-only opt-in buffer, export, clear, and toggle behavior | Pending | |
|
|
|
|
## Compliance Mapping
|
|
|
|
| Framework | Review Focus | Status | Notes |
|
|
| --- | --- | --- | --- |
|
|
| OWASP Top 10 | Injection, broken access control, SSRF, security logging, vulnerable components | Pending | |
|
|
| CWE Top 25 | Input validation, path traversal, XSS, credential handling, command execution | Pending | |
|
|
| Privacy/GDPR readiness | Data minimization, local telemetry, credential handling, deletion/export expectations | Pending | |
|
|
|
|
## Findings
|
|
|
|
| ID | Severity | Area | Public-Safe Summary | Remediation | Owner | Status |
|
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
| | | | | | | |
|
|
|
|
## Remediation Validation
|
|
|
|
| Finding ID | Targeted Regression | Full Gate Result | Status |
|
|
| --- | --- | --- | --- |
|
|
| | | | |
|
|
|
|
## Sign-Off
|
|
|
|
- Auditor:
|
|
- Reviewer:
|
|
- Follow-up due date:
|
|
- Public release note needed: Yes / No
|
|
"""
|
|
|
|
|
|
def _write_output(path: Path, content: str, *, force: bool) -> None:
|
|
if path.exists() and not force:
|
|
raise FileExistsError(f"refusing to overwrite existing file: {path}")
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(content, encoding="utf-8", newline="\n")
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate a sanitized quarterly security audit report template."
|
|
)
|
|
parser.add_argument("--date", type=_parse_date, default=None, help="Audit date in YYYY-MM-DD.")
|
|
parser.add_argument("--quarter", choices=sorted(VALID_QUARTERS), help="Audit quarter.")
|
|
parser.add_argument("--output", help="Write report to this path instead of stdout.")
|
|
parser.add_argument("--force", action="store_true", help="Overwrite --output if it exists.")
|
|
args = parser.parse_args(argv)
|
|
|
|
audit_date = args.date or date.today()
|
|
try:
|
|
report = build_report(audit_date=audit_date, quarter=args.quarter)
|
|
except ValueError as exc:
|
|
parser.error(str(exc))
|
|
|
|
if args.output:
|
|
try:
|
|
_write_output(Path(args.output), report, force=args.force)
|
|
except OSError as exc:
|
|
print(f"ERROR: {exc}", file=sys.stderr)
|
|
return 1
|
|
else:
|
|
sys.stdout.write(report)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|