Introduction
In a production web service's package-lock.json, we discovered a high-severity vulnerability in the Nodemailer dependency at version 8.0.7. This wasn't a simple coding mistake—it was a fundamental design flaw in how Nodemailer handled its raw message option, completely bypassing security controls that developers explicitly configured to prevent dangerous operations.
The vulnerability (GHSA-p6gq-j5cr-w38f) allowed attackers to read arbitrary files from the server and perform full-response SSRF attacks, even when the application had properly configured disableFileAccess: true and disableUrlAccess: true on the transport. For a web service handling user-influenced input, this meant remote attackers could potentially exfiltrate /etc/passwd, AWS credentials, or probe internal network services—all through the email functionality.
The Vulnerability Explained
What Are disableFileAccess and disableUrlAccess?
Nodemailer provides two critical security options when creating a transport:
const transporter = nodemailer.createTransport({
host: 'smtp.example.com',
port: 587,
disableFileAccess: true, // Should prevent file:// URLs
disableUrlAccess: true // Should prevent http:// and https:// URLs
});
These settings are designed to prevent email content from including:
- File attachments via path: Reading arbitrary files from the server filesystem
- Remote content via URL: Making HTTP requests to fetch content (SSRF vector)
The Bypass Mechanism
The vulnerability existed in how Nodemailer processed the raw option at the message level. When composing an email, developers can use raw to provide pre-built MIME content:
// Vulnerable pattern in Nodemailer < 9.0.1
transporter.sendMail({
from: 'sender@example.com',
to: 'victim@example.com',
raw: maliciousRawContent // This bypassed security controls!
});
The raw option was processed through a different code path that did not check the transport-level disableFileAccess and disableUrlAccess settings. This meant an attacker who could influence the raw content could:
- Read arbitrary files: Include
file:///etc/passwdorfile:///app/.envreferences - Perform SSRF: Include URLs to internal services like
http://169.254.169.254/latest/meta-data/(AWS metadata endpoint)
Attack Scenario for This Web Service
Since this is a web service where vulnerabilities in request handlers are directly exploitable by remote attackers, consider this attack flow:
- An attacker identifies an endpoint that sends emails with user-controlled content
- The attacker crafts a request that injects malicious content into the
rawemail field - Despite
disableFileAccess: truebeing configured, the email includes:
```
Content-Type: text/plain
Content-Disposition: attachment; filename="config.txt"
[Contents of /app/config/secrets.json]
```
4. The email is sent, and the attacker receives sensitive server files as attachments
For SSRF, the attacker could probe internal services:
http://internal-api.local:8080/admin/users
http://localhost:6379/ (Redis)
http://169.254.169.254/latest/meta-data/iam/security-credentials/
Real-World Impact
For this specific application, the impact includes:
- Credential theft: Reading .env files, AWS credentials, database connection strings
- Internal network mapping: Discovering and probing internal services
- Data exfiltration: Accessing application data through internal APIs
- Cloud metadata exposure: In cloud environments, accessing instance metadata for privilege escalation
The Fix
The fix was straightforward but critical: upgrade Nodemailer from version 8.0.7 to 9.0.1, where the security controls are properly enforced.
Before (Vulnerable)
// package.json
{
"dependencies": {
"nodemailer": "^8.0.7"
}
}
// package-lock.json
"node_modules/nodemailer": {
"version": "8.0.7",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.7.tgz",
"integrity": "sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow=="
}
After (Fixed)
// package.json
{
"dependencies": {
"nodemailer": "^9.0.1"
}
}
// package-lock.json
"node_modules/nodemailer": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.1.tgz",
"integrity": "sha512-Gwv8SQewT616ZM/URn0H54b8PWo/Wum7md3EW2aWy1lO27+WZCX+Xyak3J+NlmHUjDh3ME+uesJUDRbR3Ye8Bw=="
}
Why This Works
In Nodemailer 9.0.1, the security fix ensures that:
1. The raw option processing now checks transport-level security settings
2. disableFileAccess: true prevents all file access, regardless of how content is composed
3. disableUrlAccess: true prevents all URL fetching, even through raw MIME content
The fix was scoped to just two files (package.json and package-lock.json) because this is a dependency vulnerability—the vulnerable code exists within Nodemailer itself, not in the application code.
Prevention & Best Practices
1. Keep Dependencies Updated
Use automated dependency scanning in your CI/CD pipeline:
# Example GitHub Actions workflow
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'HIGH,CRITICAL'
2. Defense in Depth for Email Functionality
Don't rely solely on Nodemailer's security settings:
// Validate and sanitize email content before passing to Nodemailer
function sanitizeEmailContent(content) {
// Remove any file:// or suspicious URLs
const dangerousPatterns = [
/file:\/\//gi,
/http:\/\/169\.254\./gi, // AWS metadata
/http:\/\/localhost/gi,
/http:\/\/127\./gi,
/http:\/\/\[::1\]/gi
];
for (const pattern of dangerousPatterns) {
if (pattern.test(content)) {
throw new Error('Potentially malicious content detected');
}
}
return content;
}
3. Network Segmentation
Even if SSRF bypasses application controls, network-level restrictions can limit damage:
- Block outbound requests from application servers to internal services
- Use AWS VPC endpoints and security groups to restrict metadata access
- Implement egress filtering for email servers
4. Audit User-Controlled Email Content
Review all code paths where user input flows into email composition:
// Dangerous: User controls raw content
app.post('/send-email', (req, res) => {
transporter.sendMail({
raw: req.body.emailContent // NEVER DO THIS
});
});
// Safer: Structured input with validation
app.post('/send-email', (req, res) => {
const { to, subject, body } = req.body;
// Validate each field
if (!isValidEmail(to)) throw new Error('Invalid recipient');
if (containsDangerousContent(body)) throw new Error('Invalid content');
transporter.sendMail({
to: sanitize(to),
subject: sanitize(subject),
text: sanitize(body)
});
});
Key Takeaways
- Never assume security controls are comprehensive: The
disableFileAccessanddisableUrlAccesssettings appeared to protect against file/URL access, but therawoption created an unexpected bypass - Dependency vulnerabilities require immediate attention: This high-severity issue was fixed by a simple version bump, but delayed patching leaves applications exposed
- Web services with email functionality are high-value targets: Email features often handle user input and can be weaponized for SSRF and data exfiltration
- The
rawoption in Nodemailer should be treated as dangerous: If you must use it, ensure you're on version 9.0.1+ and implement additional validation - Automated scanning caught what manual review might miss: Trivy identified this vulnerability in
package-lock.jsonthrough pattern matching against the GHSA database
How Orbis AppSec Detected This
- Source: User-influenced input flowing into Nodemailer email composition in the web service
- Sink: Nodemailer's
sendMail()function with potentialrawoption usage, wheredisableFileAccess/disableUrlAccesscould be bypassed - Missing control: Nodemailer version 8.0.7 lacked enforcement of security settings when using the
rawmessage option - CWE: CWE-918 (Server-Side Request Forgery) and CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
- Fix: Upgraded Nodemailer from 8.0.7 to 9.0.1, which properly enforces
disableFileAccessanddisableUrlAccessacross all message composition methods
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
The Nodemailer raw option bypass (GHSA-p6gq-j5cr-w38f) serves as a stark reminder that security controls can have unexpected gaps. Even when developers properly configure disableFileAccess: true and disableUrlAccess: true, the raw option in versions prior to 9.0.1 completely circumvented these protections.
For web services handling user input, this vulnerability was particularly dangerous—enabling remote attackers to read sensitive files and perform SSRF attacks through email functionality. The fix was simple (a dependency upgrade), but the consequences of leaving it unpatched could have been severe.
Keep your dependencies updated, implement defense in depth, and use automated scanning to catch vulnerabilities like this before they reach production.