VYPR
High severity7.5OSV Advisory· Published Nov 14, 2025· Updated May 11, 2026

CVE-2025-13033

CVE-2025-13033

Description

A vulnerability was identified in the email parsing library due to improper handling of specially formatted recipient email addresses. An attacker can exploit this flaw by crafting a recipient address that embeds an external address within quotes. This causes the application to misdirect the email to the attacker's external address instead of the intended internal recipient. This could lead to a significant data leak of sensitive information and allow an attacker to bypass security filters and access controls.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
nodemailernpm
< 7.0.77.0.7

Affected products

1

Patches

1
1150d99fba77

fix(addressparser): Fixed addressparser handling of quoted nested email addresses

https://github.com/nodemailer/nodemailerAndris ReinmanOct 5, 2025via ghsa
2 files changed · +66 6
  • lib/addressparser/index.js+30 6 modified
    @@ -15,10 +15,12 @@ function _handleAddress(tokens) {
             address: [],
             comment: [],
             group: [],
    -        text: []
    +        text: [],
    +        textWasQuoted: [] // Track which text tokens came from inside quotes
         };
         let i;
         let len;
    +    let insideQuotes = false; // Track if we're currently inside a quoted string
     
         // Filter out <addresses>, (comments) and regular text
         for (i = 0, len = tokens.length; i < len; i++) {
    @@ -28,16 +30,25 @@ function _handleAddress(tokens) {
                 switch (token.value) {
                     case '<':
                         state = 'address';
    +                    insideQuotes = false;
                         break;
                     case '(':
                         state = 'comment';
    +                    insideQuotes = false;
                         break;
                     case ':':
                         state = 'group';
                         isGroup = true;
    +                    insideQuotes = false;
    +                    break;
    +                case '"':
    +                    // Track quote state for text tokens
    +                    insideQuotes = !insideQuotes;
    +                    state = 'text';
                         break;
                     default:
                         state = 'text';
    +                    insideQuotes = false;
                         break;
                 }
             } else if (token.value) {
    @@ -51,8 +62,14 @@ function _handleAddress(tokens) {
                 if (prevToken && prevToken.noBreak && data[state].length) {
                     // join values
                     data[state][data[state].length - 1] += token.value;
    +                if (state === 'text' && insideQuotes) {
    +                    data.textWasQuoted[data.textWasQuoted.length - 1] = true;
    +                }
                 } else {
                     data[state].push(token.value);
    +                if (state === 'text') {
    +                    data.textWasQuoted.push(insideQuotes);
    +                }
                 }
             }
         }
    @@ -74,8 +91,12 @@ function _handleAddress(tokens) {
             // If no address was found, try to detect one from regular text
             if (!data.address.length && data.text.length) {
                 for (i = data.text.length - 1; i >= 0; i--) {
    -                if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
    +                // Security fix: Do not extract email addresses from quoted strings
    +                // RFC 5321 allows @ inside quoted local-parts like "user@domain"@example.com
    +                // Extracting emails from quoted text leads to misrouting vulnerabilities
    +                if (!data.textWasQuoted[i] && data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
                         data.address = data.text.splice(i, 1);
    +                    data.textWasQuoted.splice(i, 1);
                         break;
                     }
                 }
    @@ -92,10 +113,13 @@ function _handleAddress(tokens) {
                 // still no address
                 if (!data.address.length) {
                     for (i = data.text.length - 1; i >= 0; i--) {
    -                    // fixed the regex to parse email address correctly when email address has more than one @
    -                    data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
    -                    if (data.address.length) {
    -                        break;
    +                    // Security fix: Do not extract email addresses from quoted strings
    +                    if (!data.textWasQuoted[i]) {
    +                        // fixed the regex to parse email address correctly when email address has more than one @
    +                        data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
    +                        if (data.address.length) {
    +                            break;
    +                        }
                         }
                     }
                 }
    
  • test/addressparser/addressparser-test.js+36 0 modified
    @@ -309,4 +309,40 @@ describe('#addressparser', () => {
             ];
             assert.deepStrictEqual(addressparser(input), expected);
         });
    +
    +    // Security tests for RFC 5321/5322 quoted local-part handling
    +    it('should not extract email from quoted local-part (security)', () => {
    +        let input = '"xclow3n@gmail.com x"@internal.domain';
    +        let result = addressparser(input);
    +        // Should preserve full address, NOT extract xclow3n@gmail.com
    +        assert.strictEqual(result.length, 1);
    +        assert.strictEqual(result[0].address.includes('@internal.domain'), true);
    +        assert.strictEqual(result[0].address, 'xclow3n@gmail.com x@internal.domain');
    +    });
    +
    +    it('should handle quoted local-part with attacker domain (security)', () => {
    +        let input = '"user@attacker.com"@legitimate.com';
    +        let result = addressparser(input);
    +        // Should route to legitimate.com, not attacker.com
    +        assert.strictEqual(result.length, 1);
    +        assert.strictEqual(result[0].address.includes('@legitimate.com'), true);
    +        assert.strictEqual(result[0].address, 'user@attacker.com@legitimate.com');
    +    });
    +
    +    it('should handle multiple @ in quoted local-part (security)', () => {
    +        let input = '"a@b@c"@example.com';
    +        let result = addressparser(input);
    +        // Should not extract a@b or b@c
    +        assert.strictEqual(result.length, 1);
    +        assert.strictEqual(result[0].address, 'a@b@c@example.com');
    +    });
    +
    +    it('should handle quoted local-part with angle brackets', () => {
    +        let input = 'Name <"user@domain.com"@example.com>';
    +        let result = addressparser(input);
    +        assert.strictEqual(result.length, 1);
    +        assert.strictEqual(result[0].name, 'Name');
    +        // When address is in <>, quotes are preserved as part of the address
    +        assert.strictEqual(result[0].address, '"user@domain.com"@example.com');
    +    });
     });
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.