-
Notifications
You must be signed in to change notification settings - Fork 316
Description
Problem Statement
gh aw audit <run-id> already downloads firewall artifacts (audit.jsonl, policy-manifest.json, access.log) but doesn't leverage the policy manifest for rule attribution — the ability to show which policy rule caused each allow/deny decision. The AWF CLI already has this capability (awf logs audit with audit-enricher.ts), but gh aw audit only shows basic domain-level allow/block counts.
Additionally, there's no way to:
- Compare firewall behavior across two runs (detect regressions, new denied domains, policy drift)
- Generate comprehensive reports across multiple runs for security review
Proposed Changes (MVP-scoped)
Phase 1: Enhance gh aw audit <run-id> with automatic policy enrichment
When firewall artifacts are present in the downloaded run directory (detected automatically — no new flags), enrich the audit output with:
- Per-domain breakdown with allow/deny counts (already exists in
firewall_log.go) - Policy rule attribution — for each domain, show which rule caused the decision
- Requires parsing
policy-manifest.jsonand replaying ACL evaluation order (port the logic fromaudit-enricher.tsto Go) - Rules are evaluated top-to-bottom by
orderfield; first matching rule wins - Domain matching:
.github.commatches bothgithub.comand*.github.com
- Requires parsing
- Denied request details with the specific rule ID, description, and reason
- Summary statistics — total requests, allowed, denied, unique domains, rule hit counts
Data sources (in priority order):
audit.jsonl— structured JSON lines, preferredaccess.log— space-separated text format, fallback
Policy manifest location: policy-manifest.json in the audit artifacts directory
Output: Integrated into the existing audit markdown report (no separate flag). When policy-manifest.json is absent, fall back to current behavior (domain counts only).
Example enriched output section
### Firewall Policy Analysis
**Policy**: 12 rules, SSL Bump disabled, DLP disabled
| Rule | Action | Description | Hits |
|------|--------|-------------|------|
| allow-both-plain | ✅ allow | Allow HTTP/HTTPS to whitelisted domains | 47 |
| deny-blocked-plain | ❌ deny | Deny all other HTTP/HTTPS traffic | 3 |
| deny-unsafe-ports | ❌ deny | Deny requests to unsafe ports | 0 |
**Denied Requests (3)**
| Time | Domain | Rule | Reason |
|------|--------|------|--------|
| 14:23:01 | evil.com:443 | deny-blocked-plain | Domain not in allowlist |
| 14:23:05 | tracker.io:443 | deny-blocked-plain | Domain not in allowlist |
| 14:24:12 | evil.com:80 | deny-blocked-plain | Domain not in allowlist |Implementation notes
- Reimplement in Go — do NOT invoke
awf logs auditas a subprocess (requires Docker/sudo) - Port
enrichWithPolicyRules()anddomainMatchesRule()logic fromsrc/logs/audit-enricher.ts - Add
PolicyManifestandPolicyRulestructs mirroring the TypeScript types insrc/types.ts - Parse
audit.jsonlas JSON lines (each line:{"ts", "client", "host", "dest", "method", "status", "decision", "url"}) - Extend existing
pkg/cli/firewall_log.gowith JSONL parsing + policy manifest loading - Follow existing patterns:
console.Format*()for output,--jsonsupport for structured output
Tasks
- Add
PolicyManifest,PolicyRuleGo structs - Add
audit.jsonlJSONL parser (complement existingaccess.logparser) - Port
enrichWithPolicyRules()/domainMatchesRule()to Go - Detect firewall artifacts automatically in
AuditWorkflowRun() - Integrate enriched policy analysis into audit markdown output
- Add
--jsonsupport for enriched firewall data - Unit tests for rule matching, JSONL parsing, policy manifest loading
Phase 2: gh aw audit diff <run-id-1> <run-id-2>
Compare firewall behavior across two workflow runs.
Output includes:
- New domains — domains contacted in run-2 but not run-1
- Removed domains — domains in run-1 but not run-2
- Status changes — domains that changed from allowed→denied or denied→allowed
- Request volume changes — significant increase/decrease in request counts per domain
- Anomaly flags — new denied domains, previously-denied domains now allowed
Output formats: pretty (default), markdown, json
Usage:
# Compare two runs
gh aw audit diff 12345 12346
# Markdown output for PR comments
gh aw audit diff 12345 12346 --format markdown
# JSON for CI integration
gh aw audit diff 12345 12346 --jsonExample diff output
### Firewall Diff: Run #12345 → Run #12346
**New domains (2)**
- ✅ `registry.npmjs.org` (15 requests, allowed)
- ❌ `telemetry.example.com` (2 requests, denied)
**Removed domains (1)**
- `old-api.internal.com` (was allowed, 8 requests in previous run)
**Status changes (1)**
- `staging.api.com`: ✅ allowed → ❌ denied (policy change?)
**Volume changes**
- `api.github.com`: 23 → 89 requests (+287%)Implementation notes
- Reuse Phase 1's artifact downloading and parsing
- Diff logic operates on aggregated
DomainRequestStatsfrom both runs - Cache downloaded artifacts to avoid re-downloading when diffing against a previously audited run
Tasks
- Add
audit diffsubcommand with two positional run-id arguments - Implement domain-level diff logic (new, removed, changed status, volume delta)
- Add anomaly detection (new denied, status flips)
- Pretty, markdown, and JSON formatters
- Unit tests for diff logic
Phase 3: gh aw audit report [--workflow <name>] [--last <N>]
Generate a comprehensive audit report across multiple runs for security review.
Output includes:
- Executive summary — total runs analyzed, overall denial rate, unique domains across all runs
- Domain inventory — all domains contacted across runs, with per-run allow/deny status
- Anomaly detection — runs with unusual patterns (spike in denials, new domains)
- Recommendations — frequently denied domains that might need allowlisting, unused allowed domains
- Per-run breakdown — summary row per run with key metrics
Usage:
# Report on last 10 runs of a workflow
gh aw audit report --workflow "agent-task" --last 10
# Report on all recent runs (default: last 20)
gh aw audit report
# JSON for dashboards
gh aw audit report --workflow "agent-task" --last 5 --jsonOutput: Markdown by default (suitable for security reviews, piping to files, or $GITHUB_STEP_SUMMARY).
Implementation notes
- Fetch run list via GitHub API filtered by workflow name
- Download and parse artifacts for each run (with caching)
- Aggregate stats across runs into a cross-run summary
- Follow existing
addRepoFlag(),addJSONFlag()patterns
Tasks
- Add
audit reportsubcommand with--workflowand--lastflags - Implement cross-run aggregation logic
- Anomaly detection (denial rate spikes, new domain appearances)
- Recommendation engine (frequently denied → suggest allowlist, unused allowed → suggest removal)
- Markdown and JSON formatters
- Unit tests for aggregation and anomaly detection
Data Format Reference
audit.jsonl (one JSON object per line):
{"ts":1761074374.646,"client":"172.30.0.20","host":"api.github.com:443","dest":"140.82.114.22:443","method":"CONNECT","status":200,"decision":"TCP_TUNNEL","url":"api.github.com:443"}policy-manifest.json:
{
"version": 1,
"generatedAt": "2024-12-20T15:30:45.123Z",
"rules": [
{
"id": "deny-unsafe-ports",
"order": 1,
"action": "deny",
"aclName": "!Safe_ports",
"protocol": "both",
"domains": [],
"description": "Deny requests to ports not in Safe_ports ACL"
}
],
"dangerousPorts": [22, 25, 109, 110, 143, 389, 465, 587],
"dnsServers": ["8.8.8.8", "8.8.4.4"],
"sslBumpEnabled": false,
"dlpEnabled": false,
"hostAccessEnabled": false,
"allowHostPorts": null
}Decision codes: TCP_TUNNEL/TCP_HIT/TCP_MISS = allowed, TCP_DENIED/NONE_NONE = denied
Artifact directory structure
run-{id}/
├── sandbox/
│ └── firewall/
│ ├── logs/
│ │ ├── audit.jsonl # Structured log entries (preferred)
│ │ └── access.log # Text format (fallback)
│ └── policy/
│ └── policy-manifest.json # Policy rules for enrichment