trackslash
TRACK-98 P2

Let people mark an issue private, even on a public project

0
All issues

Description

A public project (public or public_issues) shows every issue to everyone. There's no way to report a security vulnerability, or anything else that shouldn't be public, on the board itself. Individual issues should be able to be marked private. A private issue is invisible to non-members and returns 404 to them, even on a public board, exactly as if it didn't exist.

Whoever files an issue should be able to mark it private on every issue-creation form, including the help desk. The option should come with a short explanation of when you'd want it.

Who can see a private issue

  • The owner, members of any role (including read-only) and site admins: everything, as today.
  • The person who reported it, even if they aren't a member. Otherwise someone who files a vulnerability on a public_issues board would get a 404 straight after submitting. Suggestion: a non-member reporter sees it the way a help-desk reporter sees their issue (ReporterIssue: title, description, open/in progress/closed, shared comments, their own replies), reusing the TRACK-92 reporter path. That also keeps priority, assignee, links and history away from them.
  • Everyone else gets a 404, including public viewers, signed-in non-members and signed-out visitors.
    • It's the same response a missing issue gets: a 404 page, a 404 from REST, and not_found (not forbidden) from MCP. Nobody can tell a private issue from one that doesn't exist.
    • This follows the "missing, not forbidden" rule in SECURITY_MODEL.md, as another reporter's issue in a help desk already does.
    • Signed-out visitors aren't sent to sign in, because only a private issue would do that, which gives it away. If the 404 page offers a sign-in link, a missing issue's 404 must offer the same link.
  • Blocked users keep getting a 403 for everything, as today.

Setting it

  • On creation: a checkbox on every form:
    • the new-issue form (new_issue_panel.html), for members and for outsiders filing on public_issues;
    • the help-desk form (helpdesk.html);
    • REST and MCP (track_create_issue, including the help-desk path that currently accepts only title and description).
  • Suggested copy. Label: "Keep this issue private". Hint: "Only you and the project's members will see it. Use this for security vulnerabilities, or anything with personal or account details." Keep to AGENTS.md's design rules: a plain checkbox with muted hint text, not a callout.
  • On the help desk: help-desk issues are already visible only to the reporter and the members, so the hint must not suggest that ticking it changes who can see the issue now. It still matters for two reasons. It flags the report as sensitive to the members, and it keeps the issue hidden if the project is later made public. Adjust the copy there, for example: "Mark it private if it's a security issue or contains personal details. It stays private even if this project is made public."
  • After creation: members with write access can mark an issue private or public, in the UI (details panel), REST and track_update_issue. Suggestion: a non-member reporter may make their own issue private but never public, so someone who forgot to tick the box can fix it straight away.
  • Making an issue public again (for example, after a fix ships) is a deliberate member action, recorded in the changelog.

Nothing leaks to non-members

Every surface that lists, counts or names issues must leave private issues out for anyone who can't read them, across UI, REST, MCP and realtime:

  • Lists and boards: project issue lists, filters, sort orders, planned view, sprint boards and sprint history, the work panel, recently completed, Recents, Favorites and search.
  • Relationships:
    • A private sub-issue or linked issue doesn't appear on a public issue: no row, title or count.
    • A public sub-issue of a private parent doesn't reveal the parent.
  • Refs in text: a TRACK-12 ref to a private issue, in a public description or comment, doesn't expand into its title or status. It renders the way a ref to a missing issue does.
  • Content of the issue: its comments, attachments and object-N files answer 404, including by direct URL.
  • Changelog: entries about a private issue are members-only, the same way block history is (migration 0051).
  • Stats, insights and progress: counts shown to non-members exclude private issues.
  • Notifications and realtime: push notifications and realtime events about a private issue reach only people who can read it. Subscribing to a private issue fails the same way as subscribing to a missing one. A mention of a non-member doesn't grant them access or notify them.
  • Anything that names issues outwards, such as the GitHub integration.

Refs stay sequential, so a gap in the numbers shows that something had that number. That's accepted. The gap could just as well be a deleted issue, which non-members can't see either.

Showing it

  • A "Private" badge (Lucide lock, with a tooltip and aria-label) on rows (issue-summary-row), cards and the detail header, for people who can see the issue. Add it to COMPONENTS.md.
  • A private filter on the issue list for members would be useful, and track_list_issues / the REST list could take the same filter.

Security docs

  • SECURITY_MODEL.md:
    • Add private issues to "Who can see what".
    • Add them to the "missing, not forbidden" rule: a private issue answers a non-member exactly as a missing one does.
    • List them with help-desk reporters as an exception to the "Existence can be probed" trade-off.
  • SECURITY.md: once this ships, it could offer "file a private issue in TRACK" as a disclosure route alongside [email protected]. That address can't receive mail until TRACK-96's setup is done. Other public projects (for example, VAULT's disclosure question) could use the same route.

Open questions

  • In a private project the flag changes nothing today. Hide the checkbox there, or show it everywhere for consistency? (It would matter if the project is later made public.)
  • Should a non-member reporter see their private issue as a ReporterIssue (suggested), or as a normal public-viewer issue page?
  • The field name: private as a boolean, or visibility (shared/members) to match comments.

Done when

  • An issue can be marked private on creation from every form (members, public_issues outsiders, help desk), with the explanatory hint, and via REST and MCP. Members can change it afterwards.
  • On a public project, a signed-in non-member and a signed-out visitor get the same response for a private issue's page, API and MCP as for an issue that doesn't exist (404, or MCP not_found), with no title or details. They don't see it in any list, board, search, link, sub-issue, ref expansion, changelog, count, notification or realtime event.
  • The reporter can still follow their own private issue.
  • Integration tests are written from the outsider's side, per AGENTS.md:
    • They try each surface above against a private issue on a public and a public_issues project.
    • They check the response matches a missing issue's.
    • They cover the reporter's view.
  • SECURITY_MODEL.md and COMPONENTS.md are updated.

GitHub

0

No branches or pull requests linked.

Comments

1
Bradley

Fixed in https://github.com/badbundle/track-slash-app/pull/194 (merged as 91cda51).

Issues can be marked private. On a public project, a private issue answers everyone outside the project exactly as a missing issue does: a 404 (the same UI page, and no sign-in redirect for signed-out visitors), MCP not_found, and a realtime subscribe that fails the same way. It stays out of every list, board, count, chart, sub-issue list, link, file listing, changelog entry, realtime event and push notification. The non-member reporter follows it through the existing reporter view.

How it works:

  • ProjectPermissions.ForIssue narrows rights to one issue, and issueRouteAccess answers ErrNotFound.
  • Store lists take IncludePrivate, false unless the reader is a member.
  • Migration 0054 adds issues.private and related_issue_ids on changelog entries, backfilled from the refs older link entries name.
  • It also marks realtime events about a private issue members-only.

Decisions on the open questions:

  • Private projects: the checkbox shows everywhere, so the choice holds if the project is later made public.
  • Reporter's view: a non-member reporter gets a ReporterIssue (now with private) through the help-desk reporter path. Their page links back to the project, and it stays in their Recents.
  • Field: a private boolean.
  • Sub-issues are never more visible than their parent. One filed under a private issue is private. Making an issue private makes its sub-issues private. A sub-issue can't be made public under a private parent. This removes the "public child reveals private parent" case.
  • Setting it:
    • On every create form (new issue, help desk with its own hint, sub-issue dialog), REST and MCP.
    • Writers change it from the Details panel "Private" row, PATCH or track_update_issue.
    • There's a private list filter.

Not done:

  • Letting a non-member reporter make their own issue private after filing. Members can.
  • The SECURITY.md disclosure route ("file a private issue in TRACK"). It sits next to the contact-address change in TRACK-101, so it's your call there.

A review pass before merging also closed some history gaps:

  • the changelog entry for a deleted issue context;
  • older link entries naming an issue that is made private later;
  • realtime events for issue-scoped context.

SECURITY_MODEL.md and COMPONENTS.md are updated. The tests are written from the outsider's side on both public and public_issues.