-
-
Couldn't load subscription status.
- Fork 288
Prototype: Add router for drafting expenses from emails routed by Mailgun #11092
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| const authenticatedUrl = new URL(attachment.url); | ||
| authenticatedUrl.password = config.mailgun.apiKey; | ||
| authenticatedUrl.username = 'api'; | ||
| const response = await fetch(authenticatedUrl.toString()); |
Check failure
Code scanning / CodeQL
Server-side request forgery Critical
URL
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 28 days ago
General Fix:
To fix this SSRF, do not blindly fetch the URL given by user input (attachment.url). Instead, ensure the URL being fetched is on an allow-list of trusted Mailgun file storage domains. Only fetch files from Mailgun's official attachment storage endpoints, rejecting any attempts to fetch from other origins. This prevents attackers from abusing this endpoint to make arbitrary requests from your server.
Detailed Fix:
- Before using
attachment.url, parse it as a URL and verify its hostname matches the expected, known host(s) used by Mailgun for file attachments. (e.g.,api.mailgun.net,attachments.mailgun.org, or whatever is official/expected for your usage). - Reject (throw an error or skip) any attachments whose URLs do not match the required domain.
- This should be done in
processAttachmentbefore performing the fetch. - Use the built-in URL parsing, simple string/regex match, or, preferably, a robust allow-list.
- Add a test to verify the host—that is, only proceed to fetch if
authenticatedUrl.hostnameexactly matches the allowed host(s). - You may also want to restrict the protocol to
https:.
No new packages are needed; only add a hostname check and early rejection.
-
Copy modified lines R59-R72 -
Copy modified line R75
| @@ -56,9 +56,23 @@ | ||
|
|
||
| const processAttachment = async (attachment: MailgunAttachment, user: User): Promise<UploadedFile> => { | ||
| const authenticatedUrl = new URL(attachment.url); | ||
|
|
||
| // Allow-list the host for Mailgun attachments (update as per your Mailgun config) | ||
| // Typically, Mailgun uses "api.mailgun.net" or regional/vanity domains | ||
| const allowedHosts = [ | ||
| 'api.mailgun.net', | ||
| 'attachments.mailgun.org', // adjust for your Mailgun region/domain if different | ||
| ]; | ||
| if (!allowedHosts.includes(authenticatedUrl.hostname)) { | ||
| throw new Error(`Blocked attempt to fetch file from non-allowed host: ${authenticatedUrl.hostname}`); | ||
| } | ||
| if (authenticatedUrl.protocol !== 'https:') { | ||
| throw new Error(`Insecure protocol for attachment fetch: ${authenticatedUrl.protocol}`); | ||
| } | ||
|
|
||
| authenticatedUrl.password = config.mailgun.apiKey; | ||
| authenticatedUrl.username = 'api'; | ||
| // TODO: Add more validation that the attachments is stored in Mailgun | ||
| // Only fetch if URL is allow-listed | ||
| const response = await fetch(authenticatedUrl.toString()); | ||
|
|
||
| if (!response.ok) { |
8f6cc6e to
6b7fea2
Compare
Related to opencollective/opencollective#1862
Requires Mailgun receiving route rule similar to:
Simple prototype setting the basic flow we can use to draft expenses from email. Includes: