mirror of
https://github.com/speed47/spectre-meltdown-checker.git
synced 2026-04-20 07:33:20 +02:00
118 lines
4.1 KiB
YAML
118 lines
4.1 KiB
YAML
name: Daily transient-execution vulnerability scan
|
|
|
|
on:
|
|
schedule:
|
|
- cron: '42 8 * * *'
|
|
workflow_dispatch: {} # allow manual trigger
|
|
|
|
permissions:
|
|
contents: read
|
|
actions: read # needed to list/download previous run artifacts
|
|
|
|
concurrency:
|
|
group: vuln-scan
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
scan:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
|
|
steps:
|
|
- name: Checkout repository (for grep-based dedup against existing checks)
|
|
uses: actions/checkout@v5
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
# ---- Load previous state ---------------------------------------------
|
|
# Find the most recent successful run of THIS workflow (other than the
|
|
# current one) and pull its `vuln-scan-state` artifact. On the very
|
|
# first run there will be none — that's fine, we start empty.
|
|
- name: Find previous successful run id
|
|
id: prev
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -e
|
|
run_id=$(gh run list \
|
|
--workflow="${{ github.workflow }}" \
|
|
--status=success \
|
|
--limit 1 \
|
|
--json databaseId \
|
|
--jq '.[0].databaseId // empty')
|
|
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
|
if [ -n "$run_id" ]; then
|
|
echo "Found previous successful run: $run_id"
|
|
else
|
|
echo "No previous successful run — starting from empty state."
|
|
fi
|
|
|
|
- name: Download previous state artifact
|
|
if: steps.prev.outputs.run_id != ''
|
|
uses: actions/download-artifact@v4
|
|
continue-on-error: true # tolerate retention expiry
|
|
with:
|
|
name: vuln-scan-state
|
|
path: state/
|
|
run-id: ${{ steps.prev.outputs.run_id }}
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Ensure state file exists
|
|
run: |
|
|
mkdir -p state
|
|
if [ ! -f state/seen.json ]; then
|
|
echo '{"last_run": null, "seen": {}}' > state/seen.json
|
|
echo "Initialized empty state."
|
|
fi
|
|
echo "State size: $(wc -c < state/seen.json) bytes"
|
|
|
|
# ---- Run the scan ----------------------------------------------------
|
|
# Runs Claude Code (Opus) against daily_vuln_scan_prompt.md.
|
|
# That prompt file fully specifies: sources to poll, how to read
|
|
# state/seen.json, the 25-hour window, the output files to write,
|
|
# and how to rewrite state/seen.json at the end of the run.
|
|
- name: Run vulnerability scan with Claude Opus
|
|
uses: anthropics/claude-code-base-action@v1
|
|
env:
|
|
SCAN_DATE: ${{ github.run_started_at }}
|
|
with:
|
|
model: claude-opus-4-7
|
|
prompt_file: .github/workflows/daily_vuln_scan_prompt.md
|
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
allowed_tools: "Read,Write,Edit,Bash,Grep,Glob,WebFetch"
|
|
timeout_minutes: 15
|
|
|
|
# ---- Persist outputs -------------------------------------------------
|
|
- name: Prune state (keep only entries from the last 30 days)
|
|
run: |
|
|
python3 - <<'PY'
|
|
import json, datetime, pathlib
|
|
p = pathlib.Path("state/seen.json")
|
|
data = json.loads(p.read_text())
|
|
cutoff = (datetime.datetime.utcnow() - datetime.timedelta(days=30)).isoformat()
|
|
before = len(data.get("seen", {}))
|
|
data["seen"] = {
|
|
k: v for k, v in data.get("seen", {}).items()
|
|
if v.get("seen_at", "9999") >= cutoff
|
|
}
|
|
after = len(data["seen"])
|
|
p.write_text(json.dumps(data, indent=2, sort_keys=True))
|
|
print(f"Pruned state: {before} -> {after} entries")
|
|
PY
|
|
|
|
- name: Upload new state artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: vuln-scan-state
|
|
path: state/seen.json
|
|
retention-days: 90
|
|
if-no-files-found: error
|
|
|
|
- name: Upload daily report
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: vuln-scan-report-${{ github.run_id }}
|
|
path: rss_*.md
|
|
retention-days: 90
|
|
if-no-files-found: warn
|