Files
awesome-copilot/extensions/sentry-triage/README.md
T
Liz TomandCopilot App c72fb21484 Harden sentry-triage repo/URL metadata validation and split issue/PR repos (#2815)
* Harden sentry-triage repo/URL metadata validation and split issue/PR repos

The canvas relays model-reported GitHub artifact URLs and numbers (from a
turn that also ingests untrusted Sentry text) into tracking badges and the
"Fix with Copilot" flow. Validate that data against trusted, outside-the-model
anchors so injected text can't mint misleading badges or steer a write to the
wrong repo, and support issue/PR repos that legitimately differ.

- Add path-type-aware URL validation (repoRefNumber/urlInRepo): a URL must be
  http(s), on the trusted host, in the expected owner/repo, and match the exact
  artifact kind (/issues/<n> vs /pull/<n>) with a path-segment boundary after
  the id. The id must be a positive safe integer. This rejects look-alikes like
  /pull/123evil, cross-kind spoofs (an issue URL minting a "PR #N" badge), and
  overflow/zero ids.
- Split the issue anchor from the PR anchor (deriveRepoAnchors): the tracking
  issue and the fix PR can live in different repos. Cloud mode anchors the PR on
  the issue/cloud repo; an explicitly selected local project anchors it on that
  project's config-time repo (frozen at save time); "Current project" anchors it
  on the trusted current-project repo from the git remote — never the issue repo.
  A missing/malformed PR repo fails closed with a clear, actionable error.
- Derive tracking issue/PR numbers from the validated URLs (not the model), and
  strip every pr* field when no concrete /pull/<n> in the PR repo is present, so
  a badge can't point at a different artifact than the one verified.
- Freeze the selected project's repo into PR-target config at save time
  (page.mjs/state.mjs/server.mjs) and include mode + local.repo in the
  repo-change identity so switching projects re-derives annotations.
- Roll the optimistic rescan overlay back on a failed /api/refresh POST instead
  of leaving a blocking overlay up until the fallback timer expires.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Derive displayed numbers from validated URLs; isolate project enumeration

Address Copilot review on #2815.

Comment #2: every displayed issue/PR number is now re-extracted from the
same URL that was validated against the expected repo, instead of trusting
the model-reported *Number field. Fixes the skipped/dedup, done, and
submit_work_pr paths so a reply pairing /pull/1 with prNumber 999 can no
longer render "PR #999" linking to PR 1. Numbers with no concrete anchor
(placeholder repo / non-GitHub tracker) keep the model value.

Comment #1: the projectId->repo mapping is a security anchor (it decides
which repo fix-session PRs are validated against), so it must not be
produced in a turn that also ingests untrusted Sentry titles. Enumeration
now runs in its own Sentry-free turn (loadProjectOptions), and the save
path re-binds the repo/name server-side from the selected projectId against
trusted project state; the browser no longer supplies the auth repo.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Demote sentry-triage project map from an authorization anchor

The projectId->repo map returned by the model's list_projects tool was
being used as the trust anchor for fix-session PR validation, and was
isolated into a separate "project enumeration" turn to keep untrusted
Sentry titles out of the prompt that produced it. But that turn joins the
same foreground conversation, so it is not a real trust boundary.

The SDK exposes no isolated model context and no trusted project-
enumeration API, so instead stop treating the model-relayed repo as an
authorization anchor at all. An explicitly selected project is authorized
by its host-resolved project_id (a trusted handle at create_session time),
so deriveRepoAnchors now returns prExpectedRepo='' + prProjectSelected for
that case and the preflight/URL gates skip the unbackable repo check. The
map is now display/dedup-search only, so enumeration is folded back into
the triage round-trip and the separate turn is removed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Fix fallout from demoting the sentry-triage project map

Follow-ups from review after the project-map demotion:

- Reject stale/forged local project IDs in set-pr-config. An unknown
  non-empty projectId left boundProject null but was still stored, and
  deriveRepoAnchors treats any non-empty id as an explicit selection —
  which skips the missing-repo/PR-URL preflight. Store the id only when it
  resolves to a known project, so an unresolvable id fails closed to ''
  (Current-project mode, which requires a trusted git remote).

- Restore tracked-PR badges for split-repo selected projects. The tracked
  badge validated the model-reported PR URL against the now-empty trusted
  PR anchor, so repoRefNumber always returned null and every discovered PR
  was stripped. The badge is read-only display, not a write gate, so anchor
  its search + URL/number validation on the model-declared project repo
  when there is no trusted anchor (still pins number->URL and host).

- Update set-pr-config and page.mjs comments: the bound repo/name are
  display + dedup-search metadata, not PR authorization, and drop the stale
  loadProjectOptions / "Sentry-free turn" references.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Fail closed on an unconfirmable dedup skip for selected projects

After demoting the project map, prExpectedRepo is '' for an explicitly
selected project, so the dedup "already being worked on" skip accepted a
bare model-reported PR number as authoritative. Because the dedup result
and the project list come from the same Sentry-bearing model turn, injected
text could forge a skip to both suppress the trusted host-resolved
create_session flow and surface an arbitrary PR link.

An authoritative skip suppresses the trusted flow and surfaces a PR link,
so it now requires a trusted PR-repo anchor: a URL-bearing PR validated
against a concrete prExpectedRepo. A selected project has no trusted anchor,
so its skip is unconfirmable and fails closed to a retryable error instead
of being honored. The downstream dedup validation is now only reached with a
concrete PR anchor, so its dead no-anchor branches are removed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Anchor Current-project PR repo to the checkout cwd, not GITHUB_REPOSITORY

runtimeDefaults.repo prefers GITHUB_REPOSITORY, which is the configured ISSUE
target and may point at a separate cloud repo; seedDefaultsFromSession() does not
overwrite an explicit GITHUB_REPOSITORY. All three deriveRepoAnchors calls passed
that value as currentProjectRepo, so when the issue repo differed from the driving
checkout, Current-project mode gated PR validation and dedup on the issue repo and
rejected legitimate PRs from the current project.

Derive the Current-project anchor unconditionally from the resolved session cwd via
currentCheckoutRepo(defaults) = repoFromPath(localPath), which co-derives from the
same git remote as the trusted host, and use it at all three anchor derivations.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Restrict local Copilot handoff to the current project

Remove the model-relayed "selected project" local write-handoff and its
whole plumbing (the Settings project dropdown, submit_projects/list_projects
enumeration, projectOptions state, and the server-side projectId re-bind).

The dropdown was the only thing feeding a model-supplied project_id into
create_session, and the only local mode whose PR repo came solely from the
untrusted Sentry triage turn. That made it both the write-steering vector the
reviewer flagged (server.mjs set-pr-config / deriveRepoAnchors) and the one
local mode where a dedup "skip" could never be verified against a trusted repo.

Local "Work on selected" now always runs the fix session in the CURRENT
project (the canvas's own checkout, host-trusted from its git remote), so the
PR repo is always a trusted anchor and dedup is always verifiable. Cross-repo
work goes through Cloud mode, whose repo the user types directly in Settings.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-27 11:35:35 +10:00

5.6 KiB

Sentry Triage Canvas

A GitHub Copilot canvas for on-call error triage. It scans your live Sentry issues, groups them by why they need attention, and lets you hand a selected issue off to Copilot — either to file a tracking issue or to spin up a session that drafts a fix pull request.

Features

  • Live scan of Sentry issues for an organization (optionally narrowed to a single project), grouped by urgency.
  • Plain-English titles toggle that rewrites the raw Sentry error into a one-sentence, user-facing summary.
  • Tracking detection so already-triaged issues (filed with the sentry-triage label) are recognized on later scans.
  • 📝 Create issue files or reuses a tracking issue only — no code, branch, or PR.
  • 🔧 Fix with Copilot files/reuses the tracking issue and spawns a dedicated session that drafts a fix PR.
  • Settings panel to confirm the repository, local checkout, base branch, and issue tracker before anything is created.

Files

  • extension.mjs — canvas declaration, agent hand-off tools, and orchestration.
  • server.mjs — loopback HTTP server that backs the canvas webview.
  • state.mjs — per-canvas state and render coordination.
  • styles.mjs — canvas styling.
  • sentry.mjs / sentryClient.mjs — Sentry CLI (library mode) access: scan issues, list orgs and projects.
  • preflight.mjs — Sentry sign-in / connection checks with the setup gate.
  • prefs.mjs — persisted user preferences.
  • escape.mjs — prompt/HTML escaping helpers.
  • components/page.mjs, card.mjs, and category.mjs webview components.
  • assets/preview.png — preview image for the extensions gallery.
  • package.json — ESM entry point and runtime dependencies.
  • copilot-extension.json — Copilot extension name/version metadata.

Prerequisites

  • Node.js 22 or newer.

  • The GitHub Copilot app canvas / UI-extensions experiment enabled.

  • A Sentry sign-in. This canvas reads issues through the Sentry CLI in library mode — there is no MCP server to configure. After installing the dependency (see Install below), sign in once with the package-local CLI, run from the extension folder:

    npx sentry auth login
    

    This stores an OAuth credential the canvas auto-detects. For non-interactive environments, export a token instead:

    export SENTRY_AUTH_TOKEN=<your-token>
    

    The login or token needs these scopes: event:read, org:read, project:read. The canvas checks for a valid sign-in each time it opens; if you aren't signed in it shows a setup gate with the exact reason.

Install

Drop this folder at ~/.copilot/extensions/sentry-triage/ for user scope, or in a repository at .github/extensions/sentry-triage/ for project scope.

This canvas depends on the sentry npm package at runtime, which isn't bundled with the extension source. If it's missing, the canvas still opens and shows a setup gate explaining what to do instead of crashing.

Because the canvas runs inside GitHub Copilot, the agent can install the dependency for you. Paste this into Copilot:

Locate the loaded sentry-triage canvas extension folder — the directory that contains its package.json — run npm install there, then reload extensions.

(That folder is ~/.copilot/extensions/sentry-triage/ for user scope, .github/extensions/sentry-triage/ for project scope, or, if you installed the published plugin, com.github.copilot/extensions/sentry-triage inside the installed plugin.)

Then finish the one interactive step yourself — sign in so Copilot never handles a raw secret. Run this from the same extension folder (npx resolves the CLI the local npm install just placed in node_modules):

npx sentry auth login

Or install it manually

# User scope
cd ~/.copilot/extensions/sentry-triage

# Or project scope, from the repository root
cd .github/extensions/sentry-triage

# Or, if you installed the published plugin (`copilot plugin install`), the source
# lives inside the installed plugin — cd into its extension folder:
cd <installed-plugin-path>/com.github.copilot/extensions/sentry-triage

npm install
npx sentry auth login

Reload extensions in the GitHub Copilot app, then open the sentry-triage canvas.

Open the canvas in the correct repository scope

Open the canvas from a Copilot session scoped to the repository where you want issues and draft pull requests created, so it can detect the repository, local checkout, and base branch automatically. If you opened it outside the intended repository, open Settings and confirm the targets before selecting Create issue or Fix with Copilot.

Use the canvas

  1. Open the canvas from the repository you want to work in.
  2. Confirm the Sentry organization (auto-detected from your sign-in). If your account has more than one org, pick it from the dropdown. Scan issues stays disabled until an organization is set.
  3. Optionally narrow to a single project using the autocomplete field. Leave it blank to scan the whole org.
  4. Review the issues grouped by why they need attention. Toggle Plain-English titles to swap the raw Sentry error for a readable summary.
  5. Select issues and choose an action in the toolbar — 📝 Create issue or 🔧 Fix with Copilot.

Agent tools

The canvas exposes structured hand-off tools the agent calls instead of printing JSON into the timeline: submit_issue_summaries, submit_tracking, submit_related, and submit_work_pr.

License

MIT — see LICENSE.