@@ -113,13 +113,26 @@ jobs:
113113 env :
114114 IMAGE_NAME : ${{ matrix.image }}
115115
116- - name : Scan with Trivy
116+ - name : Checkout (for argus SDK)
117+ uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
118+
119+ - name : Set up Python
120+ uses : actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
121+ with :
122+ python-version : ' 3.13'
123+
124+ - name : Install Argus SDK
125+ run : |
126+ pip install --quiet pyyaml
127+ echo "PYTHONPATH=$GITHUB_WORKSPACE" >> "$GITHUB_ENV"
128+
129+ # Scan with ALL severities — perception is protection
130+ - name : Scan with Trivy (SARIF)
117131 uses : aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
118132 with :
119133 image-ref : " ghcr.io/huntridge-labs/argus/${{ matrix.image }}:${{ github.sha }}"
120134 format : ' sarif'
121135 output : ' trivy-results.sarif'
122- severity : ' CRITICAL,HIGH'
123136
124137 - name : Upload Trivy SARIF
125138 if : always()
@@ -136,7 +149,6 @@ jobs:
136149 fail-build : false
137150 severity-cutoff : critical
138151
139- # Run Trivy again with JSON for severity breakdown
140152 - name : Scan with Trivy (JSON)
141153 if : always()
142154 uses : aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
@@ -145,59 +157,51 @@ jobs:
145157 format : ' json'
146158 output : ' trivy-results.json'
147159
148- - name : Generate scan report
160+ # Use argus container scanner parser + markdown reporter
161+ - name : Generate report with Argus
149162 if : always()
150163 run : |
151- mkdir -p scan-reports
164+ mkdir -p scanner-summaries
152165 python3 << 'PYEOF'
153- import json, os
166+ import sys, os
167+ sys.path.insert(0, os.environ.get("PYTHONPATH", "."))
168+
169+ from pathlib import Path
170+ from argus.scanners.container import ContainerScanner
171+ from argus.core.models import ScanResult, ScanSummary, Severity
172+ from argus.reporters.markdown import MarkdownReporter
154173
155174 image_name = os.environ["IMAGE_NAME"]
156- report = {"image": image_name, "critical": 0, "high": 0, "medium": 0, "low": 0, "total": 0, "top_findings": []}
157-
158- # Parse Trivy JSON for severity counts and details
159- try:
160- data = json.load(open("trivy-results.json"))
161- seen = set()
162- for result in data.get("Results", []):
163- for vuln in result.get("Vulnerabilities", []):
164- vid = vuln.get("VulnerabilityID", "")
165- if vid in seen:
166- continue
167- seen.add(vid)
168- sev = vuln.get("Severity", "UNKNOWN").upper()
169- if sev == "CRITICAL":
170- report["critical"] += 1
171- elif sev == "HIGH":
172- report["high"] += 1
173- elif sev == "MEDIUM":
174- report["medium"] += 1
175- elif sev == "LOW":
176- report["low"] += 1
177- report["total"] += 1
178- if len(report["top_findings"]) < 15:
179- report["top_findings"].append({
180- "id": vid,
181- "severity": sev,
182- "pkg": vuln.get("PkgName", ""),
183- "installed": vuln.get("InstalledVersion", ""),
184- "fixed": vuln.get("FixedVersion", ""),
185- "title": vuln.get("Title", "")[:80],
186- })
187- except Exception as e:
188- report["error"] = str(e)
189-
190- json.dump(report, open(f"scan-reports/{image_name}.json", "w"), indent=2)
175+ trivy_json = Path("trivy-results.json")
176+
177+ scanner = ContainerScanner()
178+ findings = scanner.parse_trivy_results(trivy_json) if trivy_json.exists() else []
179+
180+ result = ScanResult(
181+ scanner=f"container/{image_name}",
182+ findings=findings,
183+ metadata={"image": image_name},
184+ )
185+ summary = ScanSummary(results=[result])
186+
187+ reporter = MarkdownReporter()
188+ reporter.report(summary, "scanner-summaries")
189+
190+ # Rename to per-image filename for aggregation
191+ src = Path("scanner-summaries/argus-summary.md")
192+ dst = Path(f"scanner-summaries/{image_name}.md")
193+ if src.exists():
194+ dst.write_text(src.read_text())
191195 PYEOF
192196 env :
193197 IMAGE_NAME : ${{ matrix.image }}
194198
195- - name : Upload scan report
199+ - name : Upload scan artifacts
196200 if : always()
197201 uses : actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
198202 with :
199- name : scan-report -${{ matrix.image }}
200- path : scan-reports /
203+ name : scanner-summary-container -${{ matrix.image }}
204+ path : scanner-summaries /
201205 retention-days : 7
202206
203207 # ── Step 3: Test argus CLI using built images ───────────────────────
@@ -310,20 +314,24 @@ jobs:
310314 path : cli-test-results/
311315 retention-days : 7
312316
313- # ── Step 4: Post PR comment with results ─────────────────────────── ─
317+ # ── Step 4: Post PR comment using argus markdown + comment-pr action ─
314318 comment-pr :
315319 name : Container Scan Summary
316320 if : always() && github.event_name == 'pull_request'
317321 needs : [scan, test-cli]
318322 runs-on : ubuntu-latest
319323
320324 steps :
321- - name : Download scan reports
325+ - name : Checkout (for comment-pr action)
326+ uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
327+
328+ # Download the argus-generated markdown from each scan matrix job
329+ - name : Download scanner summaries
322330 uses : actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4
323331 with :
324- pattern : scan-report -*
332+ pattern : scanner-summary-container -*
325333 merge-multiple : true
326- path : scan-reports
334+ path : scanner-summaries
327335
328336 - name : Download CLI test results
329337 uses : actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4
@@ -332,120 +340,26 @@ jobs:
332340 path : cli-results
333341 continue-on-error : true
334342
335- - name : Generate PR comment
343+ # Combine per-image argus markdown reports into one file
344+ - name : Combine scanner summaries
336345 run : |
337- python3 << 'PYEOF'
338- import json, glob, os
339-
340- lines = []
341- lines.append("## 🔒 Argus Container Security Scan")
342- lines.append("")
343-
344- # ── Overall summary table ──
345- reports = sorted(glob.glob("scan-reports/*.json"))
346- totals = {"critical": 0, "high": 0, "medium": 0, "low": 0}
347- image_data = []
348- for path in reports:
349- data = json.load(open(path))
350- image_data.append(data)
351- for sev in totals:
352- totals[sev] += data.get(sev, 0)
353-
354- lines.append("### 📊 Findings Summary")
355- lines.append("")
356- lines.append("| 🚨 Critical | ⚠️ High | 🟡 Medium | 🔵 Low | 📦 Total |")
357- lines.append("|:-----------:|:-------:|:---------:|:------:|:--------:|")
358- total = sum(totals.values())
359- lines.append(f"| **{totals['critical']}** | **{totals['high']}** | **{totals['medium']}** | **{totals['low']}** | **{total}** |")
360- lines.append("")
361-
362- # ── Per-image breakdown ──
363- for data in image_data:
364- name = data["image"]
365- c, h, m, l = data.get("critical", 0), data.get("high", 0), data.get("medium", 0), data.get("low", 0)
366- img_total = c + h + m + l
367-
368- if img_total == 0:
369- lines.append(f"<details>")
370- lines.append(f"<summary>📦 {name} — ✅ Clean</summary>")
371- lines.append("")
372- lines.append("No vulnerabilities found at any severity level.")
373- lines.append("")
374- lines.append("</details>")
375- else:
376- lines.append(f"<details>")
377- lines.append(f"<summary>📦 {name} — {img_total} finding(s) (🚨{c} ⚠️{h} 🟡{m} 🔵{l})</summary>")
378- lines.append("")
379- lines.append("| 🚨 Critical | ⚠️ High | 🟡 Medium | 🔵 Low | Total |")
380- lines.append("|:-----------:|:-------:|:---------:|:------:|:-----:|")
381- lines.append(f"| **{c}** | **{h}** | **{m}** | **{l}** | **{img_total}** |")
382- lines.append("")
383-
384- findings = data.get("top_findings", [])
385- if findings:
386- lines.append("| CVE | Severity | Package | Installed | Fixed | Title |")
387- lines.append("|-----|----------|---------|-----------|-------|-------|")
388- for f in findings:
389- sev = f.get("severity", "")
390- sev_icon = {"CRITICAL": "🚨", "HIGH": "⚠️", "MEDIUM": "🟡", "LOW": "🔵"}.get(sev, "")
391- lines.append(
392- f"| {f.get('id', '')} | {sev_icon} {sev} | {f.get('pkg', '')} | {f.get('installed', '')} | {f.get('fixed', 'N/A')} | {f.get('title', '')} |"
393- )
394- if img_total > len(findings):
395- lines.append(f"| | | | | | *...and {img_total - len(findings)} more* |")
396-
397- lines.append("")
398- lines.append("</details>")
399- lines.append("")
400-
401- # ── Argus CLI scan summary ──
402- cli_json = "cli-results/argus-results.json"
403- if os.path.exists(cli_json):
404- data = json.load(open(cli_json))
405- results = data.get("results", [])
406- scanners = [r["scanner"] for r in results]
407- total_findings = sum(len(r.get("findings", [])) for r in results)
408- lines.append("<details>")
409- lines.append(f"<summary>🔍 Argus CLI Scan — {len(scanners)} scanner(s), {total_findings} finding(s)</summary>")
410- lines.append("")
411- for r in results:
412- scanner = r["scanner"]
413- count = len(r.get("findings", []))
414- icon = "✅" if count == 0 else "⚠️"
415- lines.append(f"- {icon} **{scanner}**: {count} finding(s)")
416- lines.append("")
417- lines.append("</details>")
418- lines.append("")
419-
420- lines.append("---")
421- lines.append("*Scanned with Trivy + Grype (containers) and Argus CLI (code). SARIF uploaded to Security tab.*")
422-
423- body = "\n".join(lines)
424- with open("comment-body.md", "w") as f:
425- f.write(body)
426- print(body)
427- PYEOF
346+ mkdir -p scanner-summaries
347+ {
348+ for md in scanner-summaries/*.md; do
349+ [ -f "$md" ] && cat "$md" && echo ""
350+ done
351+ if [ -f "cli-results/argus-summary.md" ]; then
352+ echo "---"
353+ echo ""
354+ cat cli-results/argus-summary.md
355+ fi
356+ } > scanner-summaries/combined-container-scan.md
428357
429- - name : Post PR comment
430- env :
431- GH_TOKEN : ${{ github.token }}
432- PR_NUMBER : ${{ github.event.pull_request.number }}
433- REPO : ${{ github.repository }}
434- run : |
435- BODY=$(cat comment-body.md)
436- MARKER="<!-- argus-container-scan -->"
437-
438- # Find existing comment by marker
439- COMMENT_ID=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
440- --jq ".[] | select(.body | startswith(\"${MARKER}\")) | .id" 2>/dev/null | head -1)
441-
442- FULL_BODY="${MARKER}
443- ${BODY}"
444-
445- if [ -n "$COMMENT_ID" ]; then
446- gh api "repos/${REPO}/issues/comments/${COMMENT_ID}" \
447- -X PATCH -f body="$FULL_BODY"
448- else
449- gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
450- -f body="$FULL_BODY"
451- fi
358+ # Post using the existing comment-pr composite action
359+ - name : Comment PR with scan results
360+ uses : ./.github/actions/comment-pr
361+ with :
362+ summary_file : scanner-summaries/combined-container-scan.md
363+ comment_marker : argus-container-scan
364+ title : ' 🔒 Argus Container Security Scan'
365+ fallback_message : ' No container scan results available'
0 commit comments