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.

Informações da origem

Repositório
aeonfun/aeon
Última atividade na origem
1 de outubro de 2026 às 18:24
Idioma detectado do SKILL.md
inglês
Estrelas
759
Forks
271

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub