Repository navigation
Description
Contact Details
What happened?
Severity: Medium
CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N → 5.3
CWE: CWE-770 (Allocation of Resources Without Limits or Throttling), CWE-183 (Permissive List of Allowed Inputs)
Affected versions: All versions (master @ ab67c6d)
Patched versions: None
Affected products: worknenjoy/gitpay — backend (src/)
Summary
Three task routes are registered before the secure middleware and therefore require no authentication. Each one sends email through the platform's SendGrid identity to a recipient and with content supplied by the caller. There is no rate limit. This lets an unauthenticated attacker use Gitpay as an email relay for phishing or spam that appears to originate from Gitpay, or to flood any mailbox.
Affected code
src/app/routes/tasks.ts:12-14 (all before router.use(secure))
12: router.post('/:id/invite/', controllers.inviteUserToTask)
13: router.post('/:id/funding/', controllers.inviteToFundingTask)
14: router.post('/:id/report', controllers.reportTask)
src/modules/tasks/taskInvite.ts — sends to body.email with body.name / body.message and the task's title/URL.
src/modules/tasks/taskFunding.ts — sends to body.email with body.comment, suggestedValue, username.
src/modules/tasks/taskReport.ts — builds an approval URL containing jwt.sign(task.id, SECRET_PHRASE) and emails it to the internal report address, using body.baseUrl, task.title, and body.reason.
Proof of concept
Runnable PoC: poc/09-unauthenticated-email-relay.js
POST /tasks/1/invite/ HTTP/1.1
Host:
Content-Type: application/json
{
"email": "attacker-controlled@example.com",
"name": "Gitpay Security",
"message": "Verify your account at https://attacker.example/login"
}
No Authorization header is required. The platform sends the email to the supplied address with the attacker's text, wrapped in Gitpay branding and a link to the task.
The same applies to:
POST /tasks/1/funding/
Content-Type: application/json
{"email":"victim@example.com","comment":"...","suggestedValue":"...","username":"..."}
Expected: authentication required; recipient fixed to the registered user; rate limiting.
Actual: unauthenticated send to an arbitrary address.
Impact
Gitpay's verified sending domain is used to deliver attacker-controlled content to arbitrary recipients (phishing/spam), and any address can be mailbox-flooded. The taskReport endpoint also lets an unauthenticated caller generate report emails containing signed task-deletion links.
Workarounds / Remediation
Move all three routes behind secure. Require the caller to own the task for invite/funding. Never take the recipient address from the client for invite/funding; send to the registered user's email. Add per-IP and per-account rate limits. For report, rate-limit and ignore client-supplied baseUrl.
References
gitpay/src/app/routes/tasks.ts
Lines 12 to 14 in ab67c6d
| router.post('/:id/invite/', controllers.inviteUserToTask) | |
| router.post('/:id/funding/', controllers.inviteToFundingTask) | |
| router.post('/:id/report', controllers.reportTask) |
https://github.com/worknenjoy/gitpay/blob/ab67c6d741a7245ec200636c1b81c7bcc468d677/src/modules/tasks/taskInvite.ts
https://github.com/worknenjoy/gitpay/blob/ab67c6d741a7245ec200636c1b81c7bcc468d677/src/modules/tasks/taskFunding.ts
https://github.com/worknenjoy/gitpay/blob/ab67c6d741a7245ec200636c1b81c7bcc468d677/src/modules/tasks/taskReport.ts
regards
Alwoares
09-unauthenticated-email-relay.js
What browsers are you seeing the problem on?
Chrome
Relevant log output
💰 Is there a bounty for this issue?
Yes
💵 Bounty Amount (if any)
No response
📜 Bounty Details (if applicable)
No response
Activity
johannschiffer commentedon Oct 26, 2026
I have submitted a comprehensive fix for the unauthenticated email relay and authorization defects reported here in PR #1634 (#1634).
Summary of Fix:
src/app/routes/tasks.ts): MovedPOST /tasks/:id/invite/,POST /tasks/:id/funding/, andPOST /tasks/:id/reportbehindrouter.use(secure), enforcing bearer token authentication (returns401 Unauthorized).src/modules/tasks/taskInvite.ts&taskFunding.ts): Strictly verifiesNumber(task.userId) === Number(user.id), returning403 Forbiddenif a caller tries to send invitations or funding notifications on someone else's task.src/modules/tasks/taskInvite.ts,taskFunding.ts,taskReport.ts): Verifies the target task exists before sending any emails, returning404 Not Foundon non-existent IDs.src/modules/tasks/taskReport.ts): Replaces client-suppliedapproveURLwith trusted server base URL (process.env.API_HOST || process.env.FRONTEND_HOST || 'https://gitpay.me').src/app/controllers/task.ts): Passesreq.userto modules and propagates 400/401/403/404 HTTP statuses.test/api/task/taskCrud.test.tsverifying 401 unauthenticated, 403 unauthorized, and 404 non-existent task handling across all three endpoints.fix(security): prevent unauthenticated email relay on task invite, fu…
alfredoalvarado53-ui commentedon Nov 10, 2026
Implemented in #1660:
router.use(secure).taskInviteso only the task owner can send invitations.taskReportto use trusted server host environment variables rather than client-suppliedbaseUrl.test/api/task/taskInviteFundingAuth.test.ts.PR: #1660
Contributor Payout Address:
0xa554a9f6b26e6a418df3623b41e222e18a3b8da6fix(tasks): restore template literal backticks in taskInvite, taskFun…
fix(test): accept 401/403 from secure auth middleware in taskInviteFu…