camlebrun/StackRadar

GitHub: camlebrun/StackRadar

Stars: 0 | Forks: 0

# StackRadar StackRadar fetches GitHub release notes across your tracked repos, analyses them with a Mistral LLM, flags CVEs, and renders a bento-style digest — refreshed every morning at 06:00 UTC. ![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/ef89b07ba1224642.svg) ## What it does - **Fetches** new releases incrementally (cursor-based, no duplicate processing) - **Analyses** each release with `mistral-small-latest` via Mistral: summary, key changes, severity, CVE IDs, tags - **Enriches** CVE IDs with CVSS scores from NIST NVD - **Stores** one JSON blob per release in Cloudflare R2 - **Serves** a `/digest` API consumed by a static bento frontend on Cloudflare Pages ## Architecture Cloud Scheduler (06:00 UTC daily) │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Cloud Run Job — git-release (Python 3.12 · europe-west9) │ │ │ │ repos.json defines what to track: │ │ ├── GitHub releases ──────────────► GitHub REST API │ │ │ (incremental, cursor-based; │ │ │ 2-major backfill on first run) │ │ ├── dbt-fusion changelog ──────────────► GitHub raw content │ │ ├── GCP Docs ──────────────► cloud.google.com │ │ │ (BigQuery, Lakehouse release notes) │ │ └── security advisories ──────────────► GitHub Security API │ │ │ │ For each new release / advisory: │ │ ├── LLM analysis ─────────────────────► Mistral API │ │ │ mistral-small-latest │ │ │ 6 specialised prompts: │ │ │ standard · bigquery · lakehouse │ │ │ dbt-package · dbt-fusion · dbt-fusion-historical │ │ ├── CVE enrichment ────────────────────► NIST NVD API │ │ │ CVSS scores appended to cve_references[] │ │ └── Cloudflare R2 (boto3, S3-compatible) │ │ releases/{owner}/{repo}/{tag}.json │ │ meta/cursor/{owner}/{repo}.json │ │ meta/advisory-cursor/{owner}/{repo}.json │ │ meta/run_status.json │ │ advisories/{owner}/{repo}/advisories.json │ │ digest.json (pre-built sorted digest, served cold) │ │ │ │ Post-run (new releases only) ─────────► Cloud Function │ │ OIDC HTTP POST email-digest │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────┐ │ Cloud Function │ │ email-digest │ │ Python 3.12 │ │ europe-west9 │ │ │ │ GCP Secret Manager │ │ ├── GMAIL_ADDRESS │ │ ├── GMAIL_APP_PASSWD │ │ └── NOTIFY_EMAIL │ │ │ │ Gmail SMTP (port 465) │ │ → digest or fail HTML │ └────────────────────────┘ Cloudflare Pages (static, built with Vite) ├── / → main digest (bento cards, CVE table, filters) ├── /bigquery → BigQuery-specific release view ├── /lakehouse → Lakehouse-specific release view ├── /dbt-packages → dbt packages release view (sorted by date) ├── /dbt-fusion → dbt-fusion release view └── /security → security advisories view all tabs read digest.json directly from Cloudflare R2 Secrets & config └── GCP Secret Manager → MISTRAL_API_KEY · GITHUB_TOKEN · TRIGGER_SECRET R2_ACCESS_KEY_ID · R2_SECRET_ACCESS_KEY · R2_ACCOUNT_ID EMAIL_FUNCTION_URL (falls back to .env.local for local dev via src/secrets.py) CI / CD ├── GitHub Actions ci.yml → ruff · black · pytest [on every PR] ├── GitHub Actions deploy.yml → deploy Cloud Function [on push to main] └── Cloud Build → Docker build → Artifact Registry → deploy Cloud Run Job ## Tracked repos Configured in [`repos.json`](repos.json): [ "cli/cli", "vitejs/vite", "astral-sh/uv", "dbt-labs/dbt-core" ] To add a repo: append `"owner/repo"` and deploy. The next run auto-backfills the last 2 major versions. ## Local development # Python setup pip install -r requirements-dev.txt # Copy and fill in secrets cp .env.local.example .env.local # Run tests make test # Type check make typecheck # Start local function server make dev # → http://localhost:8080/digest # → http://localhost:8080/health # → curl -X POST -H "X-Trigger-Secret: " http://localhost:8080/trigger ## Environment variables (`.env.local`) | Variable | Required | Description | |---|---|---| | `MISTRAL_API_KEY` | Yes | [Mistral](https://console.mistral.ai) API key | | `GITHUB_TOKEN` | No | GitHub PAT — raises rate limit from 60 to 5 000 req/hr | | `TRIGGER_SECRET` | Yes | Random string — protects the `/trigger` endpoint | | `R2_ACCESS_KEY_ID` | Yes | Cloudflare R2 API token ID | | `R2_SECRET_ACCESS_KEY` | Yes | Cloudflare R2 API token secret | | `R2_ACCOUNT_ID` | Yes | Cloudflare account ID | In production, these are stored in **GCP Secret Manager** and never in env files. ## API endpoints | Endpoint | Auth | Description | |---|---|---| | `GET /digest?limit=N` | none | Latest N releases (default 20, max 100) | | `GET /health` | none | Last run timestamp + per-repo status | | `POST /trigger` | `X-Trigger-Secret` header | Run the pipeline immediately | ## Frontend - **Digest tab** — bento grid of release cards: severity badge, LLM summary, key changes, CVE chips - **CVE tab** — aggregated CVE table across all releases, sorted by severity + CVSS score, linked to NVD - Live search filters both tabs simultaneously - Dark mode, responsive (mobile → desktop) ## Project docs | Document | Description | |---|---| | [`docs/sdd.md`](docs/sdd.md) | Specification-Driven Development methodology used in this project | | [`docs/constitution.md`](docs/constitution.md) | Immutable architectural principles | | [`docs/specification.md`](docs/specification.md) | Functional & non-functional requirements | | [`docs/plan.md`](docs/plan.md) | Technical blueprint and API contracts |