Skip to content

fix(github): stop the ticket endpoint filing issues for anyone, with any label - #422

Open
QSchlegel wants to merge 1 commit into
preprodfrom
claude/daily-2026-10-24-issue-report-guard
Open

QSchlegel wants to merge 1 commit into
preprodfrom
claude/daily-2026-10-24-issue-report-guard

Conversation

@QSchlegel

Copy link
Copy Markdown
Collaborator

What

POST /api/github/create-issue (the in-app "Create a ticket" dialog) turned every request into a public issue on MeshJS/multisig, written with the project's GITHUB_TOKEN:

  • any HTTP method, no session, no rate limit — a one-line loop could flood the repo with issues;
  • type went straight into labels, so callers could attach any label (and, with a push-scoped token, create new ones);
  • a missing title made Octokit throw → unhandled 500; GitHub's error text was never caught.

Change

src/pages/api/github/create-issue.ts

  • POST only (405 + Allow: POST otherwise).
  • Requires the app's wallet session cookie (same check as /api/pinata-storage/put). The button is only rendered for signed-in users (layout.tsx, isLoggedIn branch). The cookie is SameSite=Lax, so other sites can't file through a visitor's browser. I didn't add an Origin check because validateOrigin would also need CORS_ORIGINS to list the app's own origin.
  • parseIssueReport: title 1–200 chars, body 1–5000 chars (both trimmed), type ∈ {bug, enhancement} → 400 otherwise. Requests over 16 KB → 413.
  • 5 filed reports per IP per 10 min (applyStrictRateLimit). The limit is applied after the auth and validation checks, so a user who hits "title is required" a few times isn't locked out.
  • A GitHub failure → 502 with a generic message; the details go to the server log.

dialog-report-wrapper.tsx

  • Before, a failed request left the spinner running forever and the error went unhandled. Now the dialog stays open with the text intact, shows the server's reason in a toast (e.g. "connect and authorize a wallet first"), and disables Submit while title or body is empty.

Tests

  • New src/__tests__/githubCreateIssue.test.ts: 21 tests (validation table, 405/401/400/413/429/503/502, trimmed payload, rejected requests not counted against the limit). Against the old handler, 17 of the 19 initial tests fail.
  • npx tsc --noEmit: clean.
  • node scripts/run-tests.mjs: 151 suites / 1677 tests passed (2 skipped) + ESM 11 suites / 97 tests passed.
  • next build --webpack + check-bundle-budget --report: within budget, no route changed by more than 0.1 KB.
  • An independent cold review found nothing blocking. Two of its points are already in this PR: the limit moved after the auth and validation checks, and Submit is disabled while fields are empty.

Not verified / open

  • I did not run the dialog in a browser against a real GitHub token.
  • Behaviour change: a signed-in user whose wallet-session cookie is missing or expired (they dismissed the authorize modal, or the 7-day cookie lapsed) now gets a 401 toast instead of filing a ticket.
  • Residual risk: anyone can get a session by signing a nonce with a fresh key, and the rate limiter is in-memory per instance. This raises the cost of spamming but doesn't make it impossible. Real protection would mean a captcha, or tying reports to a known user/wallet. That is a product decision, so it isn't in this PR.
  • src/components/common/overall-layout/dialog-report.tsx is dead code (nothing imports it) and is left untouched.

Generated by Claude Code

…any label

POST /api/github/create-issue turned every request into a public issue on
MeshJS/multisig using the project's GITHUB_TOKEN: any method, no session,
no rate limit, and `type` passed straight into `labels`, so a script could
flood the repo and attach (with a push-scoped token, create) arbitrary
labels. A missing title made Octokit throw an unhandled 500.

The handler now takes POST only, requires the app's wallet session (the
"Create a ticket" button is only shown to signed-in users), validates and
bounds title/body/type (400 instead of 500), caps the request at 16 KB,
limits each IP to 5 filed reports per 10 minutes (rejected requests don't
count), and answers 502 without echoing GitHub's error text.

The dialog no longer hangs on its spinner when the request fails: it keeps
the text, shows the server's reason in a toast, and disables submit while
title or body is empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L881FoNAawqTZFuZmkJCGd
@vercel

vercel Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
multisig Ready Ready Preview Oct 24, 2026 5:13am UTC

Request Review

This branch was successfully deployed

1 active deployment
Preview — 2f4f8736 Deployed Oct 24, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants