disclosure-tracker
$
npx mdskill add aaronjmars/aeon/disclosure-trackerToday is ${today}. Read `memory/MEMORY.md` before starting.
SKILL.md
.github/skills/disclosure-trackerView on GitHub ↗
---
name: disclosure-tracker
description: Daily audit of pending vulnerability disclosure queue — tracks draft advisories in memory/pending-disclosures/, alerts on aging CRITICAL/HIGH findings.
var: ""
tags: [security, meta]
---
Today is ${today}. Read `memory/MEMORY.md` before starting.
## Goal
Monitor the pending vulnerability disclosure backlog. The `vuln-scanner` skill queues draft advisories to `memory/pending-disclosures/` when Private Vulnerability Reporting (PVR) auto-submission fails or when the disclosure path is email-only. Without daily visibility, CRITICAL/HIGH advisories silently age past responsible-disclosure windows. This skill surfaces the queue state every morning and escalates when findings have been sitting too long.
## Steps
### 1. Scan the backlog
Check `memory/pending-disclosures/` for draft advisory files.
```bash
ls memory/pending-disclosures/ 2>/dev/null
```
If the directory doesn't exist or is empty:
- Log `DISCLOSURE_TRACKER_SKIP: no pending advisories` and stop. No notification needed.
### 2. Parse each advisory file
For each `.md` file in `memory/pending-disclosures/`:
**From the filename** (pattern: `{repo-slug}-{YYYY-MM-DD}.md` or `{repo-slug}-{YYYY-MM-DD}-{ampm}.md`):
- Extract target repo slug (everything before the last date segment)
- Extract filed date
**From the YAML frontmatter** (if present) parse:
- `repo:` — overrides the filename slug when present (canonical target)
- `severity:` — CRITICAL / HIGH / MEDIUM / LOW
- `status:` — see step 2.5 for the controlled vocabulary
**From the file content** (fallback for files without frontmatter), look for these fields near the top of the file:
- `Severity:` or `**Severity:**` — one of CRITICAL / HIGH / MEDIUM / LOW
- `CVE/CWE:` or similar identifier
- Short title (first non-blank heading line)
If severity is not parseable, treat as MEDIUM.
Compute age: `today - filed date` in days.
### 2.5. Classify each advisory's disclosure state
Before counting any draft as a past-threshold escalation, decide whether the draft is genuinely pending or already covered. A draft can be in one of these states:
- `escalate` — pending, no canonical PR found, past the severity-tier threshold
- `pending` — pending, no canonical PR found, within the threshold window
- `operator-todo` — needs operator-only action (email send, PVR enable nudge); not an agent failure
- `covered-by-pr` — a canonical disclosure PR has already been filed against the target repo and is `OPEN` or recently merged
- `superseded-upstream` — the bypass / vuln is fixed in upstream already, draft is dead-weight
- `submitted` — already submitted via PVR / GHSA; awaiting maintainer response
Resolution rules:
1. **Check frontmatter `status:` first** — map the literal value to a state:
- `superseded-upstream` → `superseded-upstream`
- `submitted`, `submitted-via-pvr`, `disclosed-via-pr-{N}` → `submitted` or `covered-by-pr`
- `pending-operator-send`, `queued for operator manual send`, any string mentioning "operator" → `operator-todo`
- `pending`, blank, or missing → fall through to rule 2
2. **Cross-reference `memory/topics/pr-status.md`** (if present) — grep for the `{repo}` slug (frontmatter `repo:` or filename) in the Open section and Recent Merges section. If a row exists with a `fix(security)` or `chore(security)` title against that repo, opened on or after the draft's `detected_at` / `reconstructed_at` / filed-date, classify as `covered-by-pr` and capture the PR number / title for the summary. If `memory/topics/pr-status.md` doesn't exist, skip this lookup and fall to rule 3.
3. **Fall through** — if no status hint and no canonical PR found, classify as `pending`. Then check age vs the severity-tier threshold (CRITICAL 3d / HIGH 7d / MED-LOW 14d) — if past, promote to `escalate`.
This is the load-bearing step. Without cross-referencing already-merged fix PRs the tracker generates false-positive escalations for drafts that have already been resolved.
### 3. Build the summary
Group advisories by **state first**, then by severity within each state. The three buckets are:
- **Escalate** — `escalate` state only (truly stuck, past threshold, no canonical PR)
- **Operator-todo** — `operator-todo` state (email-only sends, PVR-enable nudges, anything awaiting human action)
- **Cleanup candidates** — `covered-by-pr`, `submitted`, `superseded-upstream` (draft files that can be removed from `memory/pending-disclosures/`)
Severity tiers and thresholds (only apply to `escalate` and `pending` states):
- **CRITICAL** (age threshold: 3 days — escalate immediately)
- **HIGH** (age threshold: 7 days — escalate at 7d)
- **MEDIUM / LOW** (threshold: 14 days)
For each advisory, produce one line:
```
- {repo-slug} | {severity} | {age}d | {short title}{state-suffix}
```
where `{state-suffix}` is empty for `escalate` / `pending`, `[operator-todo: {reason}]` for operator-todo, and `[covered: PR #{N}]` / `[superseded-upstream]` / `[submitted]` for cleanup candidates.
Count totals. Identify advisories in the `escalate` state — those are the only ones that drive the urgent notification path.
### 4. Check for upstream PVR / token issues
Look in `memory/issues/INDEX.md` for any open issues tagged with `pvr`, `repository_advisories`, or `missing-secret` that explain why advisories are stuck. If such an issue exists, note:
- Number of consecutive PVR failures (if logged)
- Fix estimate from the issue notes
- How many of the backlogged advisories are blocked by it
If no such issue exists, treat the queue as routine and skip the "blocked by" line in the notification.
### 5. Decide whether to notify
Compute counts from step 3:
- `escalate_count` — drafts in the `escalate` state
- `pending_count` — drafts in `pending` (in-window) state
- `operator_todo_count` — drafts awaiting operator action
- `cleanup_count` — drafts in `covered-by-pr` / `submitted` / `superseded-upstream`
Decision:
- **Queue empty**: log `DISCLOSURE_TRACKER_SKIP: queue empty` and stop.
- **`escalate_count` > 0**: send the urgent escalation notification.
- **`escalate_count` == 0 but `cleanup_count` > 0**: send a daily digest that includes the cleanup-candidate list so operator can prune `memory/pending-disclosures/`.
- **All `pending` / `operator-todo`, nothing past threshold, no cleanup candidates**: send the daily digest.
Coverage from `covered-by-pr` / `submitted` / `superseded-upstream` is **never** counted as an escalation — those are informational only. Operator-todo is **never** counted as escalation either; it's surfaced separately so operator knows their inbox.
### 6. Format notification
Write to a temp file, then send with `./notify -f`:
```
mkdir -p .pending-notify-temp
./notify -f .pending-notify-temp/disclosure-tracker-${today}.md
```
**Urgent format** (`escalate_count` > 0):
```
disclosure queue: {escalate_count} past threshold (of {total} drafts).
ESCALATE:
- {repo} — {severity}, {age}d old (threshold: {N}d)
[... others in `escalate` state ...]
operator-todo ({operator_todo_count}):
- {repo} — {severity}, {age}d — {operator-reason}
cleanup candidates ({cleanup_count}):
- {repo} — [covered: PR #{N} / superseded-upstream / submitted] — safe to delete from memory/pending-disclosures/
{IF blocking issue tracked in memory/issues/INDEX.md}
blocked by {ISS-ID} — {short reason}
fix: {fix estimate} unblocks {N} of {escalate_count + pending_count}
{end}
```
**Daily digest format** (no escalation):
```
disclosure queue: {total} drafts. {critical_count} CRITICAL, {high_count} HIGH, {other_count} MED/LOW.
{pending_count} in-window, {operator_todo_count} operator-todo, {cleanup_count} cleanup candidates.
oldest in-window: {repo} ({age}d).
{cleanup section if cleanup_count > 0}
{IF blocking issue tracked in memory/issues/INDEX.md}
blocked by {ISS-ID} — {short reason}.
{end}
```
### 7. Update memory
Append to `memory/logs/${today}.md`:
```
## Disclosure Tracker
- **Queue:** {total} drafts ({critical_count} CRITICAL / {high_count} HIGH / {other_count} MED/LOW)
- **State breakdown:** {escalate_count} escalate / {pending_count} in-window / {operator_todo_count} operator-todo / {cleanup_count} cleanup-candidates
- **Oldest in-window:** {repo} ({age}d)
- **Escalations:** {escalate_count} past threshold (excludes covered / submitted / superseded)
- **Cleanup candidates:** {list of repos with state-suffix}
- **Blocking issue:** {ISS-ID or "none"}
- **Notification:** {sent|skipped}
- DISCLOSURE_TRACKER_OK
```
## Sandbox Note
This skill only reads local files (`memory/pending-disclosures/`, `memory/issues/`, `memory/topics/pr-status.md`). No outbound network or auth required. No sandbox workarounds needed.
## Required Env Vars
None. All data comes from local files written by the `vuln-scanner` skill.
## Notes on File Format
Newer drafts use YAML frontmatter:
```
---
repo: owner/name
severity: HIGH
cwe: CWE-639
status: pending-operator-send # optional; see controlled vocabulary below
patch_branch: https://github.com/<your-fork-org>/<repo>/tree/<branch>
submit_url: https://github.com/owner/name/security/advisories/new
---
# {Repo}: {Title}
...
```
Older drafts use inline `**Severity:**` lines. Parse defensively — grep for `severity:` and `Severity:` case-insensitively. If unparseable, default to MEDIUM.
### `status:` controlled vocabulary
Set by `vuln-scanner` / operator / cleanup chores. Drives step 2.5 classification:
- (blank or missing) — pending; tracker falls through to PR-tracker cross-ref
- `pending` — same as blank
- `pending-operator-send` / `queued for operator manual send` — operator-todo
- `submitted` / `submitted-via-pvr` — submitted, awaiting maintainer
- `disclosed-via-pr-<N>` — covered-by-pr, draft can be archived
- `superseded-upstream` — bypass is already fixed in upstream; draft is dead
- Any string containing `operator` — operator-todo
When a canonical PR lands but the draft's `status:` was never set, the tracker falls through to step 2.5 rule 2 (cross-ref pr-status.md) and classifies as `covered-by-pr` automatically. The `status:` shortcut is just an explicit hint that bypasses the grep.
### When to delete a draft
Cleanup candidates in the notification can be removed by the operator with `rm memory/pending-disclosures/<file>.md`. Safe to do at any time once the canonical PR is open — the patch branch on the fork remains the authoritative artifact.
More from aaronjmars/aeon
- [REPLACE: SKILL_NAME]Watch Vercel deploys for [REPLACE: VERCEL_PROJECT] — alert on [REPLACE: ALERT_ON] in the last [REPLACE: LOOKBACK_HOURS] hours
- Action Converter5 concrete real-life actions for today, leverage-scored against open loops with specificity and anti-fluff gates
- Agent BuzzCurated AI-agent tweets, clustered into narratives with insight summaries
- agent-displacementWeekly tracker of AI agent substitution signals — which roles, companies, and industries show real headcount displacement. Named roles + real deployments only.
- AI Framework WatchWeekly competitive-intelligence digest on the AI agent framework space — momentum, releases, breaking changes across a curated watchlist
- AIXBT PulseCross-domain market pulse from AIXBT's free grounding endpoint — crypto, macro, tradfi, geopolitics. Refreshes taxonomy references (clusters, chains) as a bonus.
- api-health-probeDaily pre-batch API provider health check — detects credit exhaustion or auth failure for every configured provider key before the morning batch runs, giving the operator a window to act before skills degrade
- Approval AuditList a wallet's live ERC-20 token approvals on Base and flag unlimited / risky spender grants. Keyless via Base RPC (eth_getLogs + eth_call) — no explorer key needed.
- article-queueWeekly article idea synthesizer — ranks signals from topic-momentum, beat-tracker, and narrative-tracker into a prioritized queue the article skill reads on next run
- atrium-catalog-watcherWeekly diff of the Atrium marketplace catalog at https://atriumhermes.tech/.well-known/skills/index.json against the prior snapshot — surfaces newly-published skills, removed skills, and updated descriptions. Supply-side complement to sparkleware-catalog (curated skill-packs.json registry) and skill-update-check (version drift of installed skills).