@xemahq/biomes-platform-imap 0.6.0 → 0.7.0
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/SECURITY-BOUNDS.md +85 -0
- package/dist/THIRD-PARTY-NOTICES.txt +657 -0
- package/dist/connector-adapters/imap-client.factory.d.ts +8 -2
- package/dist/connector-adapters/imap-fetch.d.ts +94 -13
- package/dist/connector-adapters/imap-message-parts.d.ts +8 -1
- package/dist/connector-adapters/index.js +76264 -127
- package/dist/connector-adapters/mail-dial-guard.d.ts +74 -0
- package/package.json +36 -10
- package/xema-biome.json +2 -2
- package/dist/connector-adapters/imap-client.factory.d.ts.map +0 -1
- package/dist/connector-adapters/imap-client.factory.js +0 -77
- package/dist/connector-adapters/imap-client.factory.js.map +0 -1
- package/dist/connector-adapters/imap-fetch.d.ts.map +0 -1
- package/dist/connector-adapters/imap-fetch.js +0 -554
- package/dist/connector-adapters/imap-fetch.js.map +0 -1
- package/dist/connector-adapters/imap-message-parts.d.ts.map +0 -1
- package/dist/connector-adapters/imap-message-parts.js +0 -103
- package/dist/connector-adapters/imap-message-parts.js.map +0 -1
- package/dist/connector-adapters/index.d.ts.map +0 -1
- package/dist/connector-adapters/index.js.map +0 -1
- package/dist/connector-adapters/mail-dial-guard.d.ts.map +0 -1
- package/dist/connector-adapters/mail-dial-guard.js +0 -220
- package/dist/connector-adapters/mail-dial-guard.js.map +0 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# IMAP connector: memory bounds against a hostile server
|
|
2
|
+
|
|
3
|
+
The connector runs on a shared adapter host and dials servers that users
|
|
4
|
+
configure. This file covers imapflow 1.7.8, the version in the lockfile. For
|
|
5
|
+
every place where imapflow builds more memory than the server sent, or keeps
|
|
6
|
+
adding to a structure across responses, it says what bounds it. Update this
|
|
7
|
+
file when imapflow is bumped.
|
|
8
|
+
|
|
9
|
+
## Where the checks run
|
|
10
|
+
|
|
11
|
+
imapflow parses each server response and then calls its logger once, before
|
|
12
|
+
any handler of that response runs (`lib/imap-flow.js`, `handleResponse`). The
|
|
13
|
+
connector's logger (`imap-client.factory.ts`, `checkResponse`) does not write
|
|
14
|
+
anything. It runs the checks below, and on a refusal it closes the connection.
|
|
15
|
+
`close()` clears the command in flight and the selected mailbox, so the
|
|
16
|
+
refused response reaches no handler. Every refusal is a typed error, logged
|
|
17
|
+
as `mail.response.refused`.
|
|
18
|
+
|
|
19
|
+
| Check | Bound | Code |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| Untagged `ESEARCH` or `VANISHED` | Refused every time | `MAIL_UNEXPECTED_RESPONSE` |
|
|
22
|
+
| A keyword hidden in the log (a quoted string over 100 characters) | Refused every time | `MAIL_UNEXPECTED_RESPONSE` |
|
|
23
|
+
| Control budget: the logged length of every response except an untagged FETCH, SEARCH, EXISTS, EXPUNGE or RECENT | 1 MiB per connection, measured on the logged form: imapflow logs a string over 100 characters as a ~40-byte placeholder, so long strings are under-charged. The true bound is `maxReceivedBytes` | `MAIL_UNEXPECTED_RESPONSE` |
|
|
24
|
+
| Per-SEARCH budget: bytes received while a SEARCH runs | `maxSearchBytes` (4 MiB) | `MAIL_SEARCH_TOO_LARGE` |
|
|
25
|
+
| Inbound ceiling: `stats().received`, counted after COMPRESS inflation | `maxReceivedBytes` (128 MiB) | `MAIL_INBOUND_TOO_LARGE` |
|
|
26
|
+
|
|
27
|
+
The byte checks can be passed by at most the one response that crosses them.
|
|
28
|
+
The keyword is read from the logged form the same way imapflow's
|
|
29
|
+
`normalizeUntaggedCommand` reads it. A test checks the two agree.
|
|
30
|
+
|
|
31
|
+
## Bounds imapflow enforces itself (configured by the connector)
|
|
32
|
+
|
|
33
|
+
- One line: `maxLineLength` = `maxSearchBytes`. A longer line fails the stream
|
|
34
|
+
(`MAIL_SEARCH_TOO_LARGE`).
|
|
35
|
+
- One literal: `maxLiteralSize` = `maxMessageBytes`.
|
|
36
|
+
- One response: `maxResponseSize` = `maxMessageBytes` + 64 KiB. This bounds the bytes of one response, not its parse time or memory.
|
|
37
|
+
- Nesting: the token parser refuses more than its `MAX_NODE_DEPTH`. The
|
|
38
|
+
connector refuses BODYSTRUCTURE nested deeper than 32
|
|
39
|
+
(`MAIL_BODYSTRUCTURE_TOO_DEEP`).
|
|
40
|
+
|
|
41
|
+
## The commands the connector makes imapflow send
|
|
42
|
+
|
|
43
|
+
During connect: CAPABILITY, ID (if offered), STARTTLS, LOGIN or AUTHENTICATE,
|
|
44
|
+
NAMESPACE, COMPRESS (if offered) and ENABLE (CONDSTORE, UTF8=ACCEPT,
|
|
45
|
+
IMAP4rev2; never QRESYNC).
|
|
46
|
+
|
|
47
|
+
For `getMailboxLock`: LIST "" <path> when the folder is not cached, then
|
|
48
|
+
SELECT. If SELECT answers NO, imapflow sends LIST again.
|
|
49
|
+
|
|
50
|
+
The actions use UID SEARCH, UID FETCH (listing, BODYSTRUCTURE, download), UID
|
|
51
|
+
STORE (`mark`) and LOGOUT. imapflow may also start IDLE on its own. The
|
|
52
|
+
connector passes FETCH and STORE ranges as strings, so imapflow never resolves
|
|
53
|
+
a range through its own SEARCH.
|
|
54
|
+
|
|
55
|
+
## Every expansion and accumulation path these commands reach
|
|
56
|
+
|
|
57
|
+
| Path (imapflow 1.7.8) | What it builds | Bound |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `commands/search.js` ESEARCH handler, on a plain SEARCH (lines 242-307) | Walks `ALL` ranges into the result Set, up to min(EXISTS, 2^24), in one synchronous loop. About 45 wire bytes after `* 4294967295 EXISTS` | Refused at the logger. The connector never sends `SEARCH RETURN` |
|
|
60
|
+
| `commands/search.js` SEARCH handler | Adds UIDs to a Set across any number of lines, up to 2^24 | Per-SEARCH budget plus the line cap |
|
|
61
|
+
| `imap-flow.js` `untaggedVanished` (global, and the SELECT handler) | `expandRange` up to 2^24 UIDs, one awaited expunge event each | Refused at the logger. QRESYNC is never enabled |
|
|
62
|
+
| `commands/list.js` LIST / XLIST / LSUB and STATUS handlers (LIST during `getMailboxLock`) | One entry object per line, with a Set, arrays and strings, and a STATUS map | Control budget (logged form: long strings are under-charged; the true bound is `maxReceivedBytes`) |
|
|
63
|
+
| `commands/select.js` FLAGS, `OK [PERMANENTFLAGS ...]` and other `OK [...]` codes | One Set per response, replaced by the next. Other codes are scalars | Control budget (logged form: long strings are under-charged; the true bound is `maxReceivedBytes`) |
|
|
64
|
+
| `commands/select.js` and global EXISTS, global EXPUNGE | One bounded number (`parseUintValue`, 10 digits) | Nothing kept. Inbound ceiling |
|
|
65
|
+
| `commands/select.js` FETCH and global `untaggedFetch` (STORE, IDLE, unsolicited) | One formatted message per response, emitted as a `flags` event that nothing listens to, then dropped | Per response. Inbound ceiling |
|
|
66
|
+
| `commands/fetch.js` FETCH (the connector's own fetches) | One response at a time (back-pressure through `next`) | The listing budget `maxListBytes`. Each response is charged its envelope, BODYSTRUCTURE and flags at 2 bytes a character, plus 256 bytes per response and 64 bytes per flag. Unrequested UIDs, counts or attributes: `MAIL_UNREQUESTED_RESPONSE`. Downloads: `maxMessageBytes` and `maxFetchBytes` |
|
|
67
|
+
| `commands/enable.js` ENABLED | Adds to a Set across lines | Control budget (logged form: long strings are under-charged; the true bound is `maxReceivedBytes`) |
|
|
68
|
+
| `commands/id.js` ID | Adds keys to a map across lines | Control budget (logged form: long strings are under-charged; the true bound is `maxReceivedBytes`) |
|
|
69
|
+
| `commands/namespace.js` NAMESPACE, global CAPABILITY, and the `[CAPABILITY]` section on any response, tagged included | One object or map per response, replaced by the next. The `authCapabilities` map keeps AUTH= names | Control budget, which also charges tagged responses |
|
|
70
|
+
| `commands/copyuid-parser.js` `expandRange` (COPYUID) | UID lists | Not reachable: the connector never sends COPY or MOVE |
|
|
71
|
+
| `resolveRange` with a search object | A SEARCH before the command | Not reachable: ranges are strings |
|
|
72
|
+
| QUOTA, STATUS, a full LIST, APPEND | n/a | Not reachable: the connector never sends them |
|
|
73
|
+
|
|
74
|
+
## What these bounds do not cover
|
|
75
|
+
|
|
76
|
+
- Parsing a single response. imapflow parses the whole response before the
|
|
77
|
+
logger sees it. Each token costs an object, so one response costs about 27
|
|
78
|
+
times its wire size while it is handled. For a 4 MiB line of one-character
|
|
79
|
+
atoms that is 107 MiB, measured. The memory is released after the response.
|
|
80
|
+
`maxLineLength` bounds each line and `maxResponseSize` bounds the bytes of
|
|
81
|
+
one response, but neither bounds parse time or memory: imapflow's parser can
|
|
82
|
+
be super-linear on literal-dense input, which was not measured. Isolation is
|
|
83
|
+
planned for imap 0.8.0.
|
|
84
|
+
- Time. The operation deadline (`MAIL_OPERATION_TIMEOUT`) bounds it, not the
|
|
85
|
+
checks above.
|