Repository navigation
GHSA draft cards: place on the board for the affected line, and include affected versions + reporter #2462
Description
Blocked until we get a Github app with a credential that can see the advisories and write to the board.
Problem
GHSA draft cards are created on the wrong board and carry too little triage information:
- Every advisory draft goes on Add tab and approval flow for server -> client sampling #28, whatever line it affects. GHSA-6x67-4w4w-9f3w affects
@modelcontextprotocol/inspector@0.22.0(v1), but its card was put on the v2 board. When the cards were checked on 2026-09-23, 9 of the 16 GHSA drafts on Add tab and approval flow for server -> client sampling #28 were v1-only. - The card shows neither the affected versions nor the reporter. To decide which line a card belongs to, a maintainer has to follow the link into the advisory.
Expected behavior
Whatever creates the draft cards should:
- Put each card on the board for the line it affects, using the advisory's
vulnerabilities[].vulnerable_version_range. Every version below2.0.0is v1.x (0.xand1.x, including@modelcontextprotocol/inspector-server@1.0.x). v1 is deprecated but still gets maintenance releases for Urgent and High security issues.- A range entirely below
2.0.0→ board get tools working #11, Status only, since get tools working #11 has no Priority field. - A range that reaches
2.x→ board Add tab and approval flow for server -> client sampling #28. - A range spanning both lines (for example
>= 0.14.1or<= 2.4.0) → decide explicitly. One card per line is the natural reading, since the fix ships on each line separately. - A missing or
nullrange → do not default to v2. Fall back to the code paths the report cites (server/src/…andclient/src/…are v1;clients/…andcore/…are v2), or flag the card for a maintainer.
- A range entirely below
- Include these lines in the card body, under the
**Advisory:**link:**Affected versions:**followed by each package and range (for example`@modelcontextprotocol/inspector` `0.22.0`)**Reporter:** @<login>, written with the@so it links. The login comes from the advisory'scredits_detailed[type=reporter], falling back toauthor.
There is no nightly job, so new advisories go uncarded
No nightly job creates these cards, and none ever has. A nightly was requested on 2026-09-18, and the idea was dropped the same session. The job would need organization projects: write, which GITHUB_TOKEN cannot hold, and the org has no PROJECT_TOKEN. All 16 existing drafts were created by hand in one batch on 2026-09-18 between 23:46 and 23:47 UTC, using a maintainer's local gh token. No card has been created automatically since.
So any advisory reported after that batch goes uncarded until someone runs the skill's step 1 by hand. On 2026-09-23, two advisories awaiting triage had no card on either board:
| Advisory | Reported | Severity | Affected versions | Board it belongs on |
|---|---|---|---|---|
| GHSA-rgh2-rcwx-8qr6 | 2026-09-22 | medium | @modelcontextprotocol/inspector-server >= 1.0.0, <= 1.0.2 |
#11 (v1) |
| GHSA-hr3j-35f8-r7xw | 2026-09-22 | critical | @modelcontextprotocol/inspector >= 2.0.0, <= 2.7.0 |
#28 (v2) |
Fix: make the nightly real. Any of these would work:
- Get the credential described below (a GitHub App, or a fine-grained PAT stored as
PROJECT_TOKEN, the secretdependabot-alerts.ymlalready expects), and use it in a scheduled advisory workflow.- The dependabot sweep already reads
secrets.PROJECT_TOKEN, but the secret does not exist. The repo has no Actions secrets, and the only org secret it can see isANTHROPIC_API_KEY. Its 2026-09-23 run loggedPROJECT_TOKEN:as empty. The sweep works without it: it files its issues withGITHUB_TOKENand leaves the card to/issue-triage(chore(deps): bump@vitest/mockerto4.1.11inclients/web/package-lock.json(1 advisory) #2310–chore(deps): bumpvitestto4.1.11inpackage-lock.json(1 advisory) #2313 were filed that way and have no board card). An advisory sweep has no such fallback. It cannot file a public issue, because that would disclose the advisory, so the draft card is its only output, and that needs the token. One secret, created by an org admin, turns on the dependabot sweep's board write and unblocks this. - Unverified: can
GITHUB_TOKENread private repository advisories?GET /repos/{owner}/{repo}/security-advisoriesneeds repository security advisory read access, and it is not yet confirmed that workflowpermissions:can grant that toGITHUB_TOKEN. Test this on a runner before designing around it (comparevulnerability-alerts: read, which turned out to be valid despite stale public docs saying otherwise). IfGITHUB_TOKENcannot read advisories, the sweep reads them with the credential instead (the GitHub App's permission list already includes advisory read), and option 3 below needs the credential as well. It also needs a deterministic script, like the other sweeps, with no model. The script: - lists advisories in
triage, paginated - removes duplicates against both boards by the
[GHSA-…]title prefix. It must check that the listing is complete (totalCount), because board Add tab and approval flow for server -> client sampling #28 already has more items than the default--limitreturns. - chooses the board from the version range
- writes the title and body
- sets Status to Incoming
- The dependabot sweep already reads
- A scheduled Claude routine running under a maintainer's account, which already has the
projectscope. This needs no new secret, but it depends on one person's token and cannot run as part of CI. - Keep it manual, and make that visible. A scheduled workflow with read-only scope would compare the advisories in
triageagainst the boards and fail loudly when one has no card. It cannot write the card, but a gap would stop going unnoticed. This needs no board write, so it works withGITHUB_TOKENalone, but only if that token can read advisories (the unverified question under option 1).
Option 1 is the only one that fully closes the gap. Option 3 is worth having in any case, as the check that catches a credential that has expired, been revoked, or been uninstalled.
The credential: Actions needs to be able to write to the org project board
What we need is a credential that lets GitHub Actions in the Inspector repo write to our org project board, stored as an org Actions secret available to the Inspector repo. The built-in Actions token (GITHUB_TOKEN) is scoped to the repo and can never be granted write access to an org-owned project board, whatever permissions: says. The sweeps may also need the credential to read the repo's security advisories; that part is unverified (see option 1). PROJECT_TOKEN is only the name of the secret dependabot-alerts.yml reads, not a kind of token.
-
Preferred: a GitHub App installed on the org, with
- Organization → Projects: read & write
- Repository → Issues: read & write
- Repository → Repository security advisories: read
installed on the inspector repo only, with its app id and private key stored as org Actions secrets limited to the inspector repo. Each run mints a short-lived token (
actions/create-github-app-token), and nothing is tied to one person or expires. With this route, each workflow needs a token-minting step whose output takesPROJECT_TOKEN's place. -
Alternative: a fine-grained personal access token with the org as resource owner and the same permissions, stored as an org Actions secret named
PROJECT_TOKENlimited to the inspector repo. It is quicker to set up and works with the existingsecrets.PROJECT_TOKENwiring as it is, but it belongs to one person and has to be renewed.
This has been requested from a core maintainer (2026-09-23).
Setting up the GitHub App
The App is not a program. Here it is only an identity with permissions attached: no code, no server, nothing running anywhere. The work is still done by our existing workflows, on GitHub's normal Actions runners.
One-time setup by an org admin, all in the GitHub UI:
- Register it under the org's Settings → Developer settings → GitHub Apps → New GitHub App. Give it a name (for example "Inspector Board Bot") and a homepage URL; the repo URL is fine.
- Untick "Webhook: Active". That is what makes it identity-only: GitHub has nothing to send events to, so no server is needed.
- Set its permissions: Organization → Projects: read & write; Repository → Issues: read & write; Repository → Repository security advisories: read.
- Generate a private key. GitHub downloads a
.pemfile. - Install it on the org, limited to the inspector repo.
- Store the app id and the
.pemcontents as org Actions secrets available to the inspector repo.
How a workflow uses it. Each job that needs board access adds one step at the start. The step mints an installation token that is good for one hour, and the scripts use it exactly as they would a PAT:
- uses: actions/create-github-app-token@v2
id: app
with:
app-id: ${{ secrets.BOARD_APP_ID }}
private-key: ${{ secrets.BOARD_APP_KEY }}
owner: modelcontextprotocol
- run: node scripts/dependabot-alerts.mjs
env:
PROJECT_TOKEN: ${{ steps.app.outputs.token }}The secret names are placeholders; use whatever the admin chooses. AGENTS.md's SDK-watch rule, "no model runs in a write-capable job", applies to this token as much as to issues: write.
What it gets us over a PAT:
- Board changes are credited to the App, not to a maintainer.
- There is no expiry to renew; the key stays valid until someone revokes it.
- It survives a maintainer leaving the org.
- Each run's token lasts an hour, so a leaked log is worth very little.
The cost is about six setup screens instead of two, plus one step per workflow.
The same credential also lets the other sweeps add their own cards
No sweep has ever added a card by itself. Every card on #28 and #11 was added by a maintainer, by hand or in a session running under their gh token.
| Sweep | Files | Adds its own card today? | With the credential |
|---|---|---|---|
dependabot-alerts.yml (daily) |
one issue per bump | No, because the secret is missing (the 2026-09-23 run logged an empty PROJECT_TOKEN:) |
Works right away; the placement code already exists |
sdk-watch.yml (nightly) |
one issue per SDK release | No. It has no board code by design (scripts/sdk-watch.mjs, "No board write"). For example, #2309 was filed 2026-09-10 05:44 UTC and added to #28 by hand the next day. |
Needs a board step |
dependency-refresh.yml (monthly) |
one tracking issue | No, by design | Needs a board step |
| GHSA drafts (this issue) | a draft card only | Not automated at all | Needs the new sweep |
For those two, add a board step that follows the dependabot sweep's pattern: add the card when the credential is present, otherwise leave the issue for /issue-triage. Update the "never boards" wording in AGENTS.md and in the scripts' header comments to match.
What has to change
The process is currently step 1 of the security-advisory skill, done by hand. No workflow or routine creates these cards: PROJECT_TOKEN does not exist in this org, and GITHUB_TOKEN cannot hold organization projects: write. A nightly job that writes to the board needs an org-project PAT, and that decision belongs in this issue. The docs currently encode "advisory drafts live on #28 only", so each of these places needs updating:
-
.claude/skills/security-advisory/SKILL.md:- step 1: choosing the board from the affected range, and the new body lines
- step 2: re-checking the range
- step 6: the v1-only path no longer deletes a Add tab and approval flow for server -> client sampling #28 draft. The v1 draft is already on get tools working #11. A draft card has no Convert-to-issue that targets the right repo and labels, so decide whether to convert on get tools working #11 or to file an issue and delete the draft.
- the "Do not propose a nightly workflow" note, if a nightly is adopted with a PAT
- the "Do not copy the vulnerability description onto the card" warning in step 1. That rule assumes the board is visible to more people than the advisory is. That is no longer true: advisory access and board access are both limited to maintainers and core maintainers. So the rule should say that a card carries the affected versions and the reporter (
@login), and the skill should stop presenting the two audiences as different. Also update the other places that repeat the claim: step 1's reason for keeping the score arithmetic free of explanations, and theissue-triagenote that the boards are private.
-
.claude/skills/board-ops/SKILL.md: the GHSA lookup searches get tools working #11 as well as Add tab and approval flow for server -> client sampling #28 ("Advisory drafts live on Add tab and approval flow for server -> client sampling #28 only"). -
.claude/skills/issue-triage/SKILL.md: the audit's[GHSA-draft carve-out must accept drafts on get tools working #11 instead of reporting them as misplaced. -
AGENTS.md→ Issue-driven Work Style: update the advisory draft-card exception, which currently says v1 is filed on get tools working #11 and the Add tab and approval flow for server -> client sampling #28 draft is deleted. - Get the credential (GitHub App preferred, or a fine-grained PAT named
PROJECT_TOKEN; see above), stored as org Actions secrets limited to the inspector repo. Requested from a core maintainer on 2026-09-23. - Add a board step to
sdk-watch.mjsanddependency-refresh.mjs, following the dependabot sweep's pattern, and update the "never boards" wording inAGENTS.mdand in the scripts' header comments. - Adopt one of the options above. For option 1: wire in the credential, add the workflow and script, and write tests for version range → board classification, for duplicate removal against both boards, and for the incomplete-listing guard.
- Card the two uncarded advisories in the table above. Done by hand on 2026-09-23: GHSA-hr3j on Add tab and approval flow for server -> client sampling #28, GHSA-rgh2 on get tools working #11.
Current state (cleaned up by hand on 2026-09-23)
The v1-only drafts on #28 were deleted and recreated on #11, and every GHSA draft on either board now has the affected-versions and reporter lines. There is no API to move a project item between projects, so moving a card means deleting it and creating it again.
Activity
cliffhall commentedon Sep 30, 2026
Cross-reference: #2542 (a nightly Dependabot + code-scanning sweep) will run on the same GitHub App. When the App is created, add two read permissions beyond the three listed here: Dependabot alerts: read and Code scanning alerts: read (optionally Administration: read, to make the sweep's
automated-security-fixescheck a real assertion). The table is in #2542.cliffhall commentedon Sep 30, 2026
The credential this issue describes now has its own card: #2544 (create the GitHub App), with the full permission list, including the two alert-read permissions the security sweep needs. This issue is blocked on #2544, and is now a sub-issue of tracker #2543, milestoned v2.10.0.
cliffhall commentedon Sep 30, 2026
Re-triage: Priority Urgent → High (maintainer decision)
This issue is now blocked on #2544 (the GitHub App), so it cannot move ahead of the credential it depends on. It is prioritized with the rest of tracker #2543.