Skip to main content

vuln-tracker

One lifecycle poll over everything vuln-scanner produces - PR and advisory status, PVR triage transitions, and pending-disclosure aging, with a stars-secured impact headline and one action queue.

Zur Installation springen

Quellinformationen

Repository
aeonfun/aeon
Letzte Quellaktivität
17. August 2026 um 22:06
Erkannte Sprache von SKILL.md
Englisch
Sterne
738
Forks
265

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
vuln-tracker
description
One lifecycle poll over everything vuln-scanner produces - PR and advisory status, PVR triage transitions, and pending-disclosure aging, with a stars-secured impact headline and one action queue.
metadata
{"title":"Vuln Tracker","category":"dev","var":"","mode":"write","tags":["meta","security","github"],"depends_on":["vuln-scanner"],"requires":["GH_TOKEN?","GH_GLOBAL?"]}
> **${var}** — Scope selector for the lifecycle poll: > - empty → **full lifecycle poll**: PR/advisory status + PVR triage + disclosure-queue aging (default). > - `prs` (also `pr` / `tracker`) → **Arm A only** — PR/advisory status audit + stars-secured dashboard. > - `pvr` → **Arm B only** — PVR triage-state poll on submitted advisories. > - `queue` (also `disclosures` / `backlog`) → **Arm C only** — pending-disclosure queue aging + escalation. > - a bare `GHSA-xxxx-xxxx-xxxx` → **Arm B, single-advisory mode** — check just that one advisory's triage state on demand. Today is ${today}. This skill is the daily read/poll arm of the vuln pipeline: `vuln-scanner` opens PRs, submits PVRs, and queues disclosure drafts, then moves on. This skill polls everything it produced and surfaces what the operator must look at — it does not open PRs or submit advisories itself (those are `vuln-scanner`'s write actions). ## Voice If `soul/SOUL.md` and `soul/STYLE.md` are populated, read them and match the operator's voice in every notification. If empty or absent, use a clear, direct, neutral tone. ## Capability mode This skill runs `mode: write` deliberately. It is a read/poll arm, but three of its capabilities cannot run under `read-only`: - **Arm B polls private, unpublished advisory triage state** via `gh api repos/$REPO/security-advisories/$GHSA`. Draft/triage advisories are visible only to the repo maintainers and the reporter, so the read is intrinsically authenticated; `read-only` strips `gh`, and a bare `$SECRET` on the command line is refused by the Bash permission layer (so a hand-rolled authenticated `curl` isn't an option either) — the read needs `gh api`, which handles auth internally. - **Arm B persists state transitions** — it rewrites `state:`/`last_checked:`/`resolved_at:` frontmatter in `memory/pending-disclosures/*.md` in place and **moves** resolved files to `memory/pending-disclosures/resolved/` (`Edit`/`git mv` — both stripped in `read-only`). - **Arm A leans on authenticated `gh api`** for the PVR-state endpoint (`repos/$REPO/private-vulnerability-reporting`) and repo/advisory reads. No sibling writes a repo file **outside** `memory/`, so there was nothing to relocate — the only outside-`memory/` writes are the ephemeral `.pending-notify-temp/` notify-staging files. Keeping `write` preserves every absorbed capability. ## Shared preamble (run for every invocation) 1. Read `memory/MEMORY.md` for context. 2. Read the last ~3 days of `memory/logs/` and drop anything already reported — don't re-surface the same signal twice. 3. Read `soul/SOUL.md` + `soul/STYLE.md` if populated (voice). 4. **Parse `${var}` → scope** (deterministic; trim + lowercase, except a `GHSA-` value which is compared case-insensitively but preserved verbatim): - empty → `scope = full` (run Arm A, then B, then C). - matches `^GHSA-` (case-insensitive) → `scope = pvr`, `single_advisory = <the GHSA value>` (Arm B filtered to one advisory). - `prs` / `pr` / `tracker` → `scope = prs` (Arm A only). - `pvr` → `scope = pvr` (Arm B only, all advisories). - `queue` / `disclosures` / `backlog` → `scope = queue` (Arm C only). - anything else → log `VULN_TRACKER_BAD_VAR: unrecognized scope '<var>'`, send no notification, exit. 5. `mkdir -p .pending-notify-temp` and start an empty combined-notification buffer at `.pending-notify-temp/vuln-tracker-${today}.md`. Each arm that has signal **appends its section** to this buffer; at the very end (step "Notify") the skill sends the buffer **once** if it is non-empty. This keeps notifications tight — a full poll with signal in two arms is one message, not two. Then run the arm(s) selected by `scope`, and finish with the shared **Notify** and **Log** steps. ## Network Note - **Arm A & Arm B (GitHub reads):** all data via `gh api` / `gh search` / `gh pr view`. `gh` handles auth internally via `GH_TOKEN` (and Arm B's private-advisory reads need the elevated `GH_GLOBAL` PAT). No env-var-authenticated `curl` from bash — a bare `$SECRET` on the command line is refused by the Bash permission layer, so `gh api` (auth handled internally) is the reliable path; no postprocess scripts needed. Arm B keeps a documented `curl` fallback for the advisory endpoint — see Arm B step B2 — but `gh api` is preferred. - **Arm C (local only):** reads only local files (`memory/pending-disclosures/`, `memory/issues/`, `memory/topics/pr-status.md`). No outbound network or auth required. --- ## Arm A — PR & advisory lifecycle status (scope `full` or `prs`) Audit the lifecycle status of every disclosure `vuln-scanner` has produced. Without a follow-up loop, three things rot silently: - **Merged-but-uncelebrated wins** — landed fixes never reach self-improve / reflect (retro) without manual aggregation. - **Maintainer questions on open PRs** — a maintainer comments asking for clarification; if the bot doesn't see it, the PR ages out. - **Queued drafts past their disclosure window** — entries with `channel: "skipped"` (no-safe-channel) vanish into `vuln-scanned.json` with no recurring re-probe. This arm cross-references `memory/vuln-scanned.json` against live GitHub state and surfaces anything the operator should look at. ### A1. Load the canonical scan history **`memory/vuln-scanned.json` may be a flat top-level array of scan rows, not a `{scans: [...]}` object** - vuln-scanner writes the flat shape. Read it in a way that accepts both: ```bash # Accepts both the flat `[ {repo,scanned_at,findings,channel}, ... ]` shape # and the legacy `{scans: [...]}` object. jq -c 'if type=="array" then .[] else (.scans[]? // empty) end' memory/vuln-scanned.json 2>/dev/null ``` A bare `jq -c '.scans[]'` **errors** on a flat-array file (`Cannot index array with string "scans"`) and, with `2>/dev/null` swallowing the error, silently yields zero rows - so the arm reads an empty scan history and reports only what other passes happen to surface. The `if type=="array"` guard is the fix; it keeps the legacy object shape working too. If `memory/vuln-scanned.json` doesn't exist or the parsed row count is 0, log `VULN_TRACKER_SKIP: no scan history` for this arm and skip Arm A (no notification section - first runs of `vuln-scanner` haven't happened yet). In a `full` poll, continue to Arm B/C. Each scan entry has at minimum: `repo`, `scanned_at`, `findings`, `channel`, `severity`. Public-PR entries also have `pr` (URL). Pending-disclosure entries have `draft_at` and `patch_branch`. Skipped entries have `reason`. **Retro-active coverage:** if the JSON was written after vuln-scanner started running, some PRs won't be in the JSON. Pull all bot-authored security PRs from GitHub directly (next step) to fill the gap. ### A2. Pull all bot-authored security PRs from GitHub `vuln-scanner` opens security-fix PRs on a **`security/`** branch. The **title** varies: dependency-bump PRs use **`fix(deps):`** (the A5a template is `fix(deps): bump <pkg> to patch <CVE>`), and older or hand-written ones use **`fix(security):`** or a bare **`security:`**. **Match all three title prefixes, and treat the `security/` branch prefix as the real invariant** - the branch name is mandated by A5a, the title wording is not. > **Do not filter on `fix(security):` alone.** vuln-scanner's dominant output is `fix(deps):`, so a `fix(security):`-only filter silently drops most in-flight security PRs - that is how `alibaba/open-code-review#541` (conflicted, lint-failing, one unaddressed review) was missed by a poll while other PRs were tracked. Also exclude `docs(security):` - those are `security-sync` website PRs, not disclosures. `gh search prs --json` does **not** expose `headRefName` - that field is only available via GraphQL. So run **both** passes below every time and union them on `repository + number`; neither alone is complete. The bot author is whoever the workflow uses (typically `github-actions[bot]` or a dedicated account configured in the workflow). Determine the author from `aeon.yml` or the workflow file; default to whatever account opened the most recent security-fix PR (any of the prefixes above) you can find. **Pass 1 (title prefixes, works everywhere):** ```bash BOT_AUTHOR="<resolved bot author>" gh search prs --author "$BOT_AUTHOR" --json number,title,url,state,createdAt,closedAt,repository --limit 200 \ | jq '[.[] | select( (.title | startswith("fix(deps):")) or (.title | startswith("fix(security):")) or (.title | startswith("security:")) )]' ``` **Pass 2 (GraphQL, required - picks up every `security/` branch whatever the title says):** ```bash gh api graphql -f query=' { search(query: "author:'"$BOT_AUTHOR"' is:pr sort:created-desc", type: ISSUE, first: 100) { nodes { ... on PullRequest { number title url state createdAt closedAt mergedAt repository { nameWithOwner } headRefName }} } }' | jq '[.data.search.nodes[] | select((.headRefName // "") | startswith("security/"))]' ``` Union the two result sets, dedup by `repository + number` (equivalently, by URL). Cross-reference with `vuln-scanned.json`: - PR present in JSON → use JSON's `severity` / `cwe` / `note` for the row. - PR not in JSON → mark as `pre-history`; fill severity from the PR title if obvious. - JSON entry with `channel != "public-pr"` → no PR to fetch; goes in the "queued" / "skipped" sections. ### A3. Fetch live state for each open PR For each open PR (state from step A2): ```bash gh pr view "$REPO/$NUM" --json state,merged,closedAt,createdAt,reviews,comments,reviewDecision,author ``` Per-PR signals: - **Maintainer-needs-answer**: any comment whose `author.login != $BOT_AUTHOR` posted **after** the most recent comment by `$BOT_AUTHOR` (or after PR creation if the bot hasn't commented). Also `reviewDecision == "CHANGES_REQUESTED"` always counts. - **Stale-no-review**: `state == "OPEN"` AND no review AND no maintainer comment AND `createdAt` > 7d ago. - **Aging-with-engagement**: `state == "OPEN"` AND any maintainer activity AND open > 14d. If a scan entry has `advisory_ids` (one or more GHSA IDs), check each one's published state: ```bash gh api "repos/$ORIGIN_REPO/security-advisories/$GHSA_ID" --jq '.state // "not found"' ``` `state == "published"` → public advisory visible. 404 → osv-scanner referenced it but the upstream repo never published its own advisory. ### A4. Fetch star counts for every secured repo For every unique `repo` across the union from step A2 (JSON history + bot-authored security PRs): ```bash gh api "repos/$REPO" --jq '{stars: .stargazers_count, archived: .archived}' 2>/dev/null ``` **Refetch every run.** Do NOT carry star counts forward from the previous `memory/topics/vuln-followup.md` — per-repo counts drift between runs and the secured-stars headline is the operator's load-bearing metric. Cache only within a single run, keyed by `nameWithOwner`, so a repo with multiple PRs is fetched once. Repo-state handling: - **200 with stars**: use `.stargazers_count` (raw integer). - **200 with `archived: true`**: still use the star count, but suffix the repo cell with ` (archived)` so the operator knows the maintainer isn't responsive. - **404 / 403**: repo was deleted, renamed, or made private. Record `null` and render as `repo-deleted`. Exclude from `total_stars_*` aggregates entirely so dead repos don't quietly zero out the totals. - **Other non-2xx**: record `null` and render `★?`. Flag in the run log for operator follow-up. These per-repo counts power both the **Stars Secured** aggregate (step A6) and the `Stars` column on every per-repo table in step A6. ### A5. Re-probe `channel: "skipped"` and `channel: "pending-disclosure"` repos For each historical entry where the disclosure couldn't ship, re-check whether the situation changed: - **`channel: "skipped"`** with `reason` containing "no PVR": ```bash PVR_NOW=$(gh api "repos/$REPO/private-vulnerability-reporting" --jq .enabled 2>/dev/null || echo "false") ``` If `PVR_NOW=true` and the original was `false`, surface as **newly-actionable**. - **`channel: "skipped"`** with `reason` containing "no SECURITY.md" — re-check `gh api repos/$REPO/contents/SECURITY.md` and `.github/SECURITY.md`. If now present, surface as **newly-actionable**. - **`channel: "pending-disclosure"`** — cross-reference with `memory/pending-disclosures/` to see if the draft is still on disk. If the file is gone but the JSON entry says `pending-disclosure`, mark as `lost-draft` so it stops being escalated forever. ### A6. Categorize every entry, then rewrite the dashboard | Status | Meaning | |---|---| | `merged` | PR merged. One-time celebration — drop from notifications after 30d. | | `open-clean` | PR open, no maintainer activity yet, < 7d old. Wait. | | `needs-answer` | Maintainer commented or requested changes. **Operator action.** | | `stale-no-review` | Open > 7d, zero maintainer activity. Consider polite ping or close. | | `aging-engaged` | Open > 14d with engagement. Operator should triage. | | `closed-no-merge` | PR closed without merging. Capture the reason for review. | | `queued` | `pending-disclosure` draft on disk, not yet shipped. | | `skipped-rechecked` | Channel was "skipped" originally; re-probe still shows no channel. | | `newly-actionable` | Skipped originally; PVR or SECURITY.md now present. **Operator action.** | | `lost-draft` | JSON says pending-disclosure but draft file is gone. Display once, then suppress. | | `pre-history` | PR found via search but predates `vuln-scanned.json`. Fill what we can. | Then rewrite `memory/topics/vuln-followup.md` (rewrite — don't append; this file is a living dashboard, not a log). The **Stars Secured** block goes at the top so the operator sees aggregate impact before drilling into rows. `total_stars_secured` = sum of stargazers across every unique repo where vuln-scanner has landed at least one merged PR. `total_stars_in_flight` = sum across repos with an open PR. `total_stars_tracked` = sum across the full union. Track all three because celebration uses `secured`, prioritization uses `in_flight`, and historical review uses `tracked`. Round star counts to abbreviated form for the headline (12.4k, 1.8k, 940). Keep raw integers in the per-repo tables so sort/diff stays exact. ```markdown # Vuln Tracker Status *Last updated: ${today}* ## Stars Secured - **Merged-PR repos (secured):** ★ <total_stars_secured> across <secured_repo_count> repos
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen