Public: anyone can read this; the crew can change it.

Security reports stay sealed on a public repository until a writer unseals them #865

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

Closes #621

shipped production is serving it for issue #621 — its issue is already done deploy ad44386

opened this

Closes #621

On a public repository, a security report holds exact detail: which path, which check is missing. A sealed issue keeps it from readers until the hole is fixed. Only people who can write the repository, and its crew, see:

  • the sealed issue;
  • the pull that closes it, which is sealed when it is opened with Closes #N;
  • the runs, events and decisions about them.

To everyone else it does not exist (not_found). A writer unseals it deliberately, and the unsealing is recorded.

The rule (src/core/sealed.ts):

  • canSeeSealed (a writer, or the crew) and mayView. Every surface asks these.
  • Lists leave sealed rows out unless the caller says the viewer may see them (listIssues({ sealed }), repoGraph(..., { sealed }), searchLocalIssues(..., sealedOk)). A list that forgets to ask hides sealed rows instead of showing them.
  • The forge's own internal callers ask with sealed: true, so the crew keeps working the report.

Covered:

  • Single reads answer not_found: the API loaders (loadConversation, loadPull, so every route under them), the thread and change pages and fragments, the studio actions, MCP issueArg and say, the terminal source, and run reads (getRepoRun, runLog).
  • Lists: issues and pulls over the API and the studio; search; the map (page, API, MCP and the global lanes); a public issue's dependencies; runs (listRepoRuns, the studio runs page, MCP now, the app's now card); the decisions API; the front page's crew-at-work line.
  • Events: audienceFilter drops any event whose number or issueId names a sealed issue. That covers the events API, SSE, the Atom feed, the thread timeline, the home rail, the feed and share.
  • Webhooks: an event about a sealed issue is never queued, since a hook's URL is somewhere else.
  • Outside the forge:

- The crew's branch for a sealed issue is <agent>/<n>, with no title, because branches are public. - The next-issue decider gets only the number, and the router never sends a sealed issue to a decider. - Open counts everyone reads leave sealed issues out.

  • Setting it:

- Anyone who may open an issue may open it sealed, through the new-issue form's checkbox or POST …/issues {sealed: true}. A reader who does so loses sight of it, by design. - Only a writer seals or unseals an existing issue, through PATCH …/issues/N {sealed}, the tell page's Details (POST …/issues/N/seal) or the MCP seal tool. - Each change is recorded as issue.sealed or issue.unsealed. - A mirror refuses sealing, because its issues live on the origin in the open.

Tests: test/sealed-issues.test.ts, 13 tests, added to the gate list. They were written first.

  • A stranger and a signed-in reader get no trace of the report's title, body, comment or fix on 18 lists and pages, and 404 on 11 direct reads (API and web).
  • A reader's MCP map and now show nothing, and say answers not found.
  • audienceFilter drops every event about the report and its pull.
  • The terminal source shows nothing.
  • A reader cannot seal or unseal through the API, the page or MCP.
  • No webhook is queued for the report.
  • The branch carries no title, the decider state holds no text, and the open count leaves it out.
  • A writer and the crew read the report, its pull and its run.
  • A writer seals and unseals through MCP and the page, both recorded, and an unsealed report is visible to readers.
  • With the rule disabled, 7 of the 13 fail.

Migration 60: issue.sealed. It is last; the lead places it.

Left for a second pull:

  • the "how to report privately" line in security.txt and on the repository page;
  • terminal screens that name runs by title (runlist, dashboard);
  • making the policy automatic when a fix is released.

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs