-
Notifications
You must be signed in to change notification settings - Fork 4
Add PVR triage taskflow #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
anticomputer
wants to merge
17
commits into
main
Choose a base branch
from
anticomputer/pvr-triage
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+2,948
−0
Draft
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
af5f4d9
Add PVR triage taskflow
anticomputer c233dac
Address PR review: add SPDX headers, pass GH_TOKEN in toolbox env
anticomputer 6d97ef5
Add PVR triage batch scoring, write-back, and reporter reputation tra…
anticomputer 9dd96c2
Add run_pvr_triage.sh: local test and demo script for pvr triage task…
anticomputer 0d83c6f
pvr_triage_batch: skip already-triaged advisories by default
anticomputer 0568973
Add SCORING.md: reference for batch priority, quality signals, fast-c…
anticomputer cbe2184
Address PR review feedback
anticomputer e0e29a8
Self-review: robustness and logic fixes
anticomputer 7fd9074
fetch_file_at_ref: raise default length from 50 to 100 lines
anticomputer 9436f3d
pvr-triage: add bulk respond taskflow, 3-path fast-close, reputation …
anticomputer 57c1bbf
SCORING.md: update 3-path decision table and reputation thresholds
anticomputer fcb3654
Fix ruff linter errors in test_pvr_mcp
anticomputer 388f145
Fix advisory state: incoming PVRs use triage state, not draft
anticomputer c84904f
Fix advisory state API: reject→closed, remove withdraw_pvr_advisory
anticomputer f5b9d26
Add accept_pvr_advisory: triage→draft state transition
anticomputer 2c331cb
Update overview doc: accept/reject state transitions in diagram and o…
anticomputer c1b5dba
Remove comment posting: no GitHub REST API for advisory comments
anticomputer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| # PVR Triage Taskflows — Overview | ||
|
|
||
| > 30-minute sync reference. Last updated: 2026-03-03. | ||
|
|
||
| --- | ||
|
|
||
| ## The Problem | ||
|
|
||
| OSS maintainers get flooded with low-quality vulnerability reports via GitHub's Private Vulnerability Reporting (PVR). Most are vague, duplicated, or AI-generated. Reviewing each one manually is expensive. | ||
|
|
||
| --- | ||
|
|
||
| ## The Solution: 4 Taskflows | ||
|
|
||
| ``` | ||
| ┌─────────────────────────────────────────────────────────────┐ | ||
| │ INBOX │ | ||
| │ (GHSAs in triage state via GitHub PVR) │ | ||
| └───────────────────────┬─────────────────────────────────────┘ | ||
| │ | ||
| ▼ | ||
| ┌─────────────────────────┐ | ||
| │ pvr_triage_batch │ "What's in my inbox?" | ||
| │ │ | ||
| │ • List triage GHSAs │ | ||
| │ • Score each by │ | ||
| │ severity + quality │ | ||
| │ • Show Age (days) │ | ||
| │ • Rank: highest first │ | ||
| │ (oldest wins ties) │ | ||
| └────────────┬────────────┘ | ||
| │ ranked queue saved to REPORT_DIR | ||
| ▼ | ||
| ┌─────────────────────────┐ | ||
| │ pvr_triage │ "Is this real?" | ||
| │ (one advisory) │ | ||
| │ │ | ||
| │ Task 1: init │ | ||
| │ Task 2: fetch & parse │ | ||
| │ Task 3: quality gate ──┼──► fast-close? ──► skip to Task 7 | ||
| │ Task 4: verify code │ | ||
| │ Task 5: write report │ | ||
| │ Task 6: save report │ | ||
| │ Task 7: draft response │ | ||
| │ Task 8: save + record │ | ||
| └────────────┬────────────┘ | ||
| │ _triage.md + _response_triage.md saved | ||
| ▼ | ||
| Maintainer reviews | ||
| (edits draft if needed) | ||
| │ | ||
| ┌────────┴────────┐ | ||
| │ │ | ||
| ▼ ▼ | ||
| ┌──────────────────┐ ┌──────────────────────┐ | ||
| │ pvr_respond │ │ pvr_respond_batch │ | ||
| │ (one at a time) │ │ (all at once) │ | ||
| │ │ │ │ | ||
| │ confirm-gated: │ │ • list_pending │ | ||
| │ accept (→draft) │ │ • for each: │ | ||
| │ reject (→closed)│ │ - confirm-gated │ | ||
| │ │ │ state change │ | ||
| │ mark as applied │ │ - mark as applied │ | ||
| │ post draft │ │ • post drafts │ | ||
| │ manually via UI │ │ manually via UI │ | ||
| └──────────────────┘ └──────────────────────┘ | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## The Quality Gate (Task 3) — Key Logic | ||
|
|
||
| ``` | ||
| Reporter has history? | ||
| │ | ||
| ├── HIGH TRUST ──────────────────► Always full verification | ||
| │ (≥60% confirmed, ≤20% low) | ||
| │ | ||
| ├── SKEPTICISM ──────────────────► Fast-close if 0 quality signals | ||
| │ (≤20% confirmed OR ≥50% low) (no prior report needed) | ||
| │ | ||
| └── NORMAL / NEW ────────────────► Fast-close only if: | ||
| 0 quality signals | ||
| AND prior similar report exists | ||
| ``` | ||
|
|
||
| **Quality signals:** file paths cited · PoC provided · line numbers cited | ||
|
|
||
| **Fast-close effect:** skip code verification → use canned response template requesting specifics | ||
|
|
||
| --- | ||
|
|
||
| ## Scoring (batch) | ||
|
|
||
| ``` | ||
| priority_score = severity_weight + quality_weight | ||
|
|
||
| severity: critical=4 high=3 medium=2 low=1 | ||
| quality: +1 per signal (files, PoC, lines) → max +3 | ||
|
|
||
| ≥5 Triage Immediately | ||
| ≥3 Triage Soon | ||
| 2 Triage | ||
| ≤1 Likely Low Quality — Fast Close | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Output Files (all in REPORT_DIR) | ||
|
|
||
| | File | Written by | What it is | | ||
| |---|---|---| | ||
| | `GHSA-xxxx_triage.md` | pvr_triage | Full analysis report | | ||
| | `GHSA-xxxx_response_triage.md` | pvr_triage | Draft reply to reporter | | ||
| | `GHSA-xxxx_response_sent.md` | pvr_respond / batch | State-transition applied marker (idempotent) | | ||
| | `batch_queue_<repo>_<date>.md` | pvr_triage_batch | Ranked inbox table | | ||
|
|
||
| --- | ||
|
|
||
| ## Reporter Reputation (background) | ||
|
|
||
| Every completed triage records **verdict + quality** against the reporter's GitHub login in a local SQLite DB. Score feeds back into the next triage's quality gate automatically. No manual configuration. | ||
|
|
||
| --- | ||
|
|
||
| ## One-liner workflow | ||
|
|
||
| ```bash | ||
| ./scripts/run_pvr_triage.sh batch owner/repo # see inbox | ||
| ./scripts/run_pvr_triage.sh triage owner/repo GHSA-xxx # analyse one | ||
| ./scripts/run_pvr_triage.sh respond owner/repo GHSA-xxx accept # accept one (triage→draft) | ||
| ./scripts/run_pvr_triage.sh respond owner/repo GHSA-xxx reject # reject one (triage→closed) | ||
| ./scripts/run_pvr_triage.sh respond_batch owner/repo reject # bulk state transition | ||
| # Then post each *_response_triage.md manually via the advisory URL | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Further reading | ||
|
|
||
| - [`taskflows/pvr_triage/README.md`](../src/seclab_taskflows/taskflows/pvr_triage/README.md) — full usage docs for all four taskflows | ||
| - [`taskflows/pvr_triage/SCORING.md`](../src/seclab_taskflows/taskflows/pvr_triage/SCORING.md) — authoritative scoring reference and fast-close decision tables |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| #!/bin/bash | ||
| # SPDX-FileCopyrightText: GitHub, Inc. | ||
| # SPDX-License-Identifier: MIT | ||
| # | ||
| # Local test / demo script for the PVR triage taskflows. | ||
| # | ||
| # Usage: | ||
| # ./scripts/run_pvr_triage.sh batch <owner/repo> | ||
| # ./scripts/run_pvr_triage.sh triage <owner/repo> <GHSA-xxxx-xxxx-xxxx> | ||
| # ./scripts/run_pvr_triage.sh respond <owner/repo> <GHSA-xxxx-xxxx-xxxx> <accept|comment|reject> | ||
| # ./scripts/run_pvr_triage.sh respond_batch <owner/repo> <accept|comment|reject> | ||
| # ./scripts/run_pvr_triage.sh demo <owner/repo> | ||
| # | ||
| # Environment (any already-set values are respected): | ||
| # GH_TOKEN — GitHub token; falls back to: gh auth token | ||
| # AI_API_TOKEN — AI API key (required, must be set before running) | ||
| # AI_API_ENDPOINT — defaults to https://api.githubcopilot.com | ||
| # REPORT_DIR — defaults to ./reports | ||
| # LOG_DIR — defaults to ./logs | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| __root="$(cd "${__dir}/.." && pwd)" | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Usage (defined early so --help can fire before env validation) | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| usage() { | ||
| cat <<EOF | ||
| Usage: $(basename "$0") <command> [args] | ||
|
|
||
| Commands: | ||
| batch <owner/repo> | ||
| Score unprocessed triage advisories and save a ranked queue table to REPORT_DIR. | ||
| Advisories already present in REPORT_DIR are skipped. | ||
|
|
||
| triage <owner/repo> <GHSA-xxxx-xxxx-xxxx> | ||
| Run full triage on one advisory: verify code, generate report + response draft. | ||
|
|
||
| respond <owner/repo> <GHSA-xxxx-xxxx-xxxx> <action> | ||
| Apply a state transition to a GitHub advisory. action = accept | reject | ||
| Requires pvr_triage to have been run first for the given GHSA. | ||
| Post the response draft manually via the advisory URL after running. | ||
|
|
||
| respond_batch <owner/repo> <action> | ||
| Scan REPORT_DIR and apply state transitions to all pending advisories. | ||
| action = accept | reject | ||
|
|
||
| demo <owner/repo> | ||
| Full pipeline on the given repo (batch → triage on first triage advisory → report preview). | ||
| Does not post anything to GitHub. | ||
|
|
||
| Environment: | ||
| GH_TOKEN — GitHub token; falls back to: gh auth token | ||
| AI_API_TOKEN — AI API key (required, must be set before running) | ||
| AI_API_ENDPOINT — defaults to https://api.githubcopilot.com | ||
| REPORT_DIR — defaults to ./reports | ||
| LOG_DIR — defaults to ./logs | ||
| EOF | ||
| } | ||
|
|
||
| case "${1:-}" in | ||
| -h|--help|help|"") usage; exit 0 ;; | ||
| esac | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Environment setup | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| # Prepend local venv to PATH if present (resolves 'python' for MCP servers) | ||
| if [ -d "${__root}/.venv/bin" ]; then | ||
| export PATH="${__root}/.venv/bin:${PATH}" | ||
| fi | ||
|
|
||
| # GitHub token | ||
| if [ -z "${GH_TOKEN:-}" ]; then | ||
| if command -v gh &>/dev/null; then | ||
| GH_TOKEN="$(gh auth token 2>/dev/null)" || true | ||
| fi | ||
| if [ -z "${GH_TOKEN:-}" ]; then | ||
| echo "ERROR: GH_TOKEN not set and 'gh auth token' failed." >&2 | ||
| exit 1 | ||
| fi | ||
| export GH_TOKEN | ||
| fi | ||
|
|
||
| # AI API token | ||
| if [ -z "${AI_API_TOKEN:-}" ]; then | ||
| echo "ERROR: AI_API_TOKEN is not set." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| export AI_API_ENDPOINT="${AI_API_ENDPOINT:-https://api.githubcopilot.com}" | ||
|
|
||
| export REPORT_DIR="${REPORT_DIR:-${__root}/reports}" | ||
| mkdir -p "${REPORT_DIR}" | ||
|
|
||
| export LOG_DIR="${LOG_DIR:-${__root}/logs}" | ||
| mkdir -p "${LOG_DIR}" | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Helpers | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| run_agent() { | ||
| python -m seclab_taskflow_agent "$@" | ||
| } | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Commands | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| cmd_batch() { | ||
| local repo="${1:?Usage: $0 batch <owner/repo>}" | ||
| echo "==> Scoring inbox for ${repo} ..." | ||
| run_agent \ | ||
| -t seclab_taskflows.taskflows.pvr_triage.pvr_triage_batch \ | ||
| -g "repo=${repo}" | ||
| } | ||
|
|
||
| cmd_triage() { | ||
| local repo="${1:?Usage: $0 triage <owner/repo> <GHSA>}" | ||
| local ghsa="${2:?Usage: $0 triage <owner/repo> <GHSA>}" | ||
| echo "==> Triaging ${ghsa} in ${repo} ..." | ||
| run_agent \ | ||
| -t seclab_taskflows.taskflows.pvr_triage.pvr_triage \ | ||
| -g "repo=${repo}" \ | ||
| -g "ghsa=${ghsa}" | ||
| } | ||
|
|
||
| cmd_respond() { | ||
| local repo="${1:?Usage: $0 respond <owner/repo> <GHSA> <action>}" | ||
| local ghsa="${2:?Usage: $0 respond <owner/repo> <GHSA> <action>}" | ||
| local action="${3:?Usage: $0 respond <owner/repo> <GHSA> <action>}" | ||
| case "${action}" in | ||
| accept|reject) ;; | ||
| *) echo "ERROR: action must be accept or reject" >&2; exit 1 ;; | ||
| esac | ||
| echo "==> Responding to ${ghsa} in ${repo} (action=${action}) ..." | ||
| run_agent \ | ||
| -t seclab_taskflows.taskflows.pvr_triage.pvr_respond \ | ||
| -g "repo=${repo}" \ | ||
| -g "ghsa=${ghsa}" \ | ||
| -g "action=${action}" | ||
| } | ||
|
|
||
| cmd_respond_batch() { | ||
| local repo="${1:?Usage: $0 respond_batch <owner/repo> <action>}" | ||
| local action="${2:?Usage: $0 respond_batch <owner/repo> <action>}" | ||
| case "${action}" in | ||
| accept|reject) ;; | ||
| *) echo "ERROR: action must be accept or reject" >&2; exit 1 ;; | ||
| esac | ||
| echo "==> Bulk respond for ${repo} (action=${action}) ..." | ||
| run_agent \ | ||
| -t seclab_taskflows.taskflows.pvr_triage.pvr_respond_batch \ | ||
| -g "repo=${repo}" \ | ||
| -g "action=${action}" | ||
| } | ||
|
|
||
| cmd_demo() { | ||
| local repo="${1:?Usage: $0 demo <owner/repo>}" | ||
|
|
||
| # Pick the first triage advisory, or bail if none | ||
| local ghsa | ||
| ghsa="$(gh api "/repos/${repo}/security-advisories?state=triage&per_page=1" \ | ||
| --jq '.[0].ghsa_id // empty' 2>/dev/null)" || true | ||
|
|
||
| if [ -z "${ghsa}" ]; then | ||
| echo "No triage advisories found in ${repo}. Create one at:" >&2 | ||
| echo " https://github.com/${repo}/security/advisories/new" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "==> Demo: ${repo} advisory: ${ghsa}" | ||
| echo | ||
|
|
||
| echo "--- Step 1: batch inbox score ---" | ||
| cmd_batch "${repo}" | ||
| echo | ||
|
|
||
| echo "--- Step 2: full triage ---" | ||
| cmd_triage "${repo}" "${ghsa}" | ||
| echo | ||
|
|
||
| echo "--- Reports written to ${REPORT_DIR} ---" | ||
| ls -1 "${REPORT_DIR}"/*.md 2>/dev/null || true | ||
| echo | ||
| echo "To accept (triage → draft) or reject (triage → closed):" | ||
| echo " $0 respond ${repo} ${ghsa} accept" | ||
| echo " $0 respond ${repo} ${ghsa} reject" | ||
| echo "Then post the response draft manually via the advisory URL." | ||
| } | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Dispatch | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| case "${1:-}" in | ||
| batch) shift; cmd_batch "$@" ;; | ||
| triage) shift; cmd_triage "$@" ;; | ||
| respond) shift; cmd_respond "$@" ;; | ||
| respond_batch) shift; cmd_respond_batch "$@" ;; | ||
| demo) shift; cmd_demo "$@" ;; | ||
| *) echo "ERROR: unknown command '${1}'" >&2; usage; exit 1 ;; | ||
| esac | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.