postal-mime 2.4.5 → 2.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.4.6](https://github.com/postalsys/postal-mime/compare/v2.4.5...v2.4.6) (2025-10-01)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * add security limits for MIME parsing ([defbf11](https://github.com/postalsys/postal-mime/commit/defbf11e85c8233e6ff01a3d6fc10534b784c499))
9
+
3
10
  ## [2.4.5](https://github.com/postalsys/postal-mime/compare/v2.4.4...v2.4.5) (2025-09-29)
4
11
 
5
12
 
package/README.md CHANGED
@@ -111,6 +111,8 @@ PostalMime.parse(email, options) -> Promise<ParsedEmail>
111
111
  - `"base64"`
112
112
  - `"utf8"`
113
113
  - `"arraybuffer"` (no decoding, returns `ArrayBuffer`)
114
+ - **maxNestingDepth** (number, default: `256`): Maximum allowed MIME part nesting depth. Throws an error if exceeded.
115
+ - **maxHeadersSize** (number, default: `2097152`): Maximum allowed total header size in bytes (default 2MB). Throws an error if exceeded.
114
116
 
115
117
  **Returns**: A Promise that resolves to a structured object with the following properties:
116
118
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postal-mime",
3
- "version": "2.4.5",
3
+ "version": "2.4.6",
4
4
  "description": "Email parser for browser environments",
5
5
  "main": "./src/postal-mime.js",
6
6
  "exports": {
package/postal-mime.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export type RawEmail = string | ArrayBuffer | Uint8Array | Blob | Buffer | ReadableStream;
2
2
 
3
- export type Header = Record<string, string>;
3
+ export type Header = {
4
+ key: string;
5
+ value: string;
6
+ };
4
7
 
5
8
  export type Address = {
6
9
  name: string;
@@ -22,7 +25,7 @@ export type Attachment = {
22
25
 
23
26
  export type Email = {
24
27
  headers: Header[];
25
- from: Address;
28
+ from?: Address;
26
29
  sender?: Address;
27
30
  replyTo?: Address[];
28
31
  deliveredTo?: string;
@@ -31,7 +34,7 @@ export type Email = {
31
34
  cc?: Address[];
32
35
  bcc?: Address[];
33
36
  subject?: string;
34
- messageId: string;
37
+ messageId?: string;
35
38
  inReplyTo?: string;
36
39
  references?: string;
37
40
  date?: string;
@@ -56,7 +59,9 @@ declare function decodeWords (
56
59
  declare type PostalMimeOptions = {
57
60
  rfc822Attachments?: boolean,
58
61
  forceRfc822Attachments?: boolean,
59
- attachmentEncoding?: "base64" | "utf8" | "arraybuffer"
62
+ attachmentEncoding?: "base64" | "utf8" | "arraybuffer",
63
+ maxNestingDepth?: number,
64
+ maxHeadersSize?: number
60
65
  }
61
66
 
62
67
  declare class PostalMime {
package/src/mime-node.js CHANGED
@@ -4,20 +4,31 @@ import Base64Decoder from './base64-decoder.js';
4
4
  import QPDecoder from './qp-decoder.js';
5
5
 
6
6
  export default class MimeNode {
7
- constructor(opts) {
8
- opts = opts || {};
7
+ constructor(options) {
8
+ this.options = options || {};
9
9
 
10
- this.postalMime = opts.postalMime;
10
+ this.postalMime = this.options.postalMime;
11
11
 
12
- this.root = !!opts.parentNode;
12
+ this.root = !!this.options.parentNode;
13
13
  this.childNodes = [];
14
- if (opts.parentNode) {
15
- opts.parentNode.childNodes.push(this);
14
+
15
+ if (this.options.parentNode) {
16
+ this.parentNode = this.options.parentNode;
17
+
18
+ this.depth = this.parentNode.depth + 1;
19
+ if (this.depth > this.options.maxNestingDepth) {
20
+ throw new Error(`Maximum MIME nesting depth of ${this.options.maxNestingDepth} levels exceeded`);
21
+ }
22
+
23
+ this.options.parentNode.childNodes.push(this);
24
+ } else {
25
+ this.depth = 0;
16
26
  }
17
27
 
18
28
  this.state = 'header';
19
29
 
20
30
  this.headerLines = [];
31
+ this.headerSize = 0;
21
32
 
22
33
  this.contentType = {
23
34
  value: 'text/plain',
@@ -262,6 +273,14 @@ export default class MimeNode {
262
273
  this.state = 'body';
263
274
  return this.processHeaders();
264
275
  }
276
+
277
+ this.headerSize += line.length;
278
+
279
+ if (this.headerSize > this.options.maxHeadersSize) {
280
+ let error = new Error(`Maximum header size of ${this.options.maxHeadersSize} bytes exceeded`);
281
+ throw error;
282
+ }
283
+
265
284
  this.headerLines.push(getDecoder().decode(line));
266
285
  break;
267
286
  case 'body': {
@@ -6,6 +6,9 @@ import { base64ArrayBuffer } from './base64-encoder.js';
6
6
 
7
7
  export { addressParser, decodeWords };
8
8
 
9
+ const MAX_NESTING_DEPTH = 256;
10
+ const MAX_HEADERS_SIZE = 2 * 1024 * 1024;
11
+
9
12
  export default class PostalMime {
10
13
  static parse(buf, options) {
11
14
  const parser = new PostalMime(options);
@@ -14,9 +17,14 @@ export default class PostalMime {
14
17
 
15
18
  constructor(options) {
16
19
  this.options = options || {};
20
+ this.mimeOptions = {
21
+ maxNestingDepth: this.options.maxNestingDepth || MAX_NESTING_DEPTH,
22
+ maxHeadersSize: this.options.maxHeadersSize || MAX_HEADERS_SIZE
23
+ };
17
24
 
18
25
  this.root = this.currentNode = new MimeNode({
19
- postalMime: this
26
+ postalMime: this,
27
+ ...this.mimeOptions
20
28
  });
21
29
  this.boundaries = [];
22
30
 
@@ -78,7 +86,8 @@ export default class PostalMime {
78
86
 
79
87
  this.currentNode = new MimeNode({
80
88
  postalMime: this,
81
- parentNode: boundary.node
89
+ parentNode: boundary.node,
90
+ ...this.mimeOptions
82
91
  });
83
92
  }
84
93