imapflow 1.4.7 → 1.4.8
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +8 -0
- package/lib/commands/authenticate.js +8 -1
- package/lib/imap-flow.js +4 -2
- package/package.json +6 -6
- package/test/commands-integration-test.js +55 -0
- package/test/connection-edge-cases-test.js +2 -2
- package/test/connection-test.js +1 -1
- package/.github/codeql/codeql-config.yml +0 -12
- package/.github/workflows/codeql.yml +0 -102
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.8](https://github.com/postalsys/imapflow/compare/v1.4.7...v1.4.8) (2026-07-21)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **auth:** describe the real connection in the OAUTHBEARER payload ([aca7cb3](https://github.com/postalsys/imapflow/commit/aca7cb3ab7e67704cb692ad59f6db0792040a888))
|
|
9
|
+
* default a non-secure connection to IMAP port 143, not POP3 110 ([5896488](https://github.com/postalsys/imapflow/commit/589648821c5dd1b1750959fa2917b3571ba93db5))
|
|
10
|
+
|
|
3
11
|
## [1.4.7](https://github.com/postalsys/imapflow/compare/v1.4.6...v1.4.7) (2026-07-10)
|
|
4
12
|
|
|
5
13
|
|
|
@@ -40,7 +40,14 @@ async function authOauth(connection, username, accessToken) {
|
|
|
40
40
|
// OAUTHBEARER payload per RFC 7628: fields separated by \x01 (SASL GS2 framing).
|
|
41
41
|
// Format: "n,a=<user>," \x01 "host=..." \x01 "port=..." \x01 "auth=Bearer <token>" \x01 \x01
|
|
42
42
|
// The trailing empty strings produce the required double-\x01 terminator.
|
|
43
|
-
|
|
43
|
+
// Both fields must describe the connection actually in use. The port was hardcoded to 993,
|
|
44
|
+
// so an OAuth2 server reached over 143/STARTTLS advertised a payload that did not match, and
|
|
45
|
+
// `servername` is set to false for a bare-IP host, which rendered as a literal "host=false".
|
|
46
|
+
// A server validating either field rejects with status `invalid_request` - a permanent
|
|
47
|
+
// failure that refreshing the access token can never clear.
|
|
48
|
+
oauthbearer = [`n,a=${username},`, `host=${connection.servername || connection.host}`, `port=${connection.port}`, `auth=Bearer ${accessToken}`, '', ''].join(
|
|
49
|
+
'\x01'
|
|
50
|
+
);
|
|
44
51
|
command = 'OAUTHBEARER';
|
|
45
52
|
// "AQ==" is base64 for \x01 -- sent as the error continuation to abort the SASL exchange
|
|
46
53
|
breaker = 'AQ==';
|
package/lib/imap-flow.js
CHANGED
|
@@ -277,12 +277,14 @@ class ImapFlow extends EventEmitter {
|
|
|
277
277
|
*/
|
|
278
278
|
this.secureConnection = !!this.options.secure;
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
// 993 is IMAPS, 143 is IMAP over cleartext/STARTTLS. The non-secure default used to be 110,
|
|
281
|
+
// which is POP3 - a client created without an explicit port could never connect.
|
|
282
|
+
this.port = Number(this.options.port) || (this.secureConnection ? 993 : 143);
|
|
281
283
|
this.host = this.options.host || 'localhost';
|
|
282
284
|
this.servername = this.options.servername ? this.options.servername : !net.isIP(this.host) ? this.host : false;
|
|
283
285
|
|
|
284
286
|
if (typeof this.options.secure === 'undefined' && this.port === 993) {
|
|
285
|
-
// if secure option is not set but port is
|
|
287
|
+
// if secure option is not set but port is 993, then default to secure
|
|
286
288
|
this.secureConnection = true;
|
|
287
289
|
}
|
|
288
290
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.8",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "lib/imap-flow.js",
|
|
6
6
|
"types": "lib/imap-flow.d.ts",
|
|
@@ -28,18 +28,18 @@
|
|
|
28
28
|
"homepage": "https://imapflow.com/",
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@eslint/js": "10.0.1",
|
|
31
|
-
"@types/node": "26.1.
|
|
32
|
-
"c8": "
|
|
33
|
-
"eslint": "10.
|
|
31
|
+
"@types/node": "26.1.1",
|
|
32
|
+
"c8": "12.0.0",
|
|
33
|
+
"eslint": "10.7.0",
|
|
34
34
|
"eslint-config-nodemailer": "1.2.0",
|
|
35
35
|
"eslint-config-prettier": "10.1.8",
|
|
36
36
|
"grunt": "1.6.2",
|
|
37
37
|
"grunt-cli": "1.5.0",
|
|
38
38
|
"grunt-contrib-nodeunit": "5.0.0",
|
|
39
39
|
"grunt-eslint": "26.0.0",
|
|
40
|
-
"prettier": "3.9.
|
|
40
|
+
"prettier": "3.9.5",
|
|
41
41
|
"proxyquire": "^2.1.3",
|
|
42
|
-
"typescript": "
|
|
42
|
+
"typescript": "7.0.2"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@zone-eu/mailsplit": "5.4.14",
|
|
@@ -30,6 +30,9 @@ const createMockConnection = (overrides = {}) => {
|
|
|
30
30
|
states,
|
|
31
31
|
state: overrides.state || states.SELECTED,
|
|
32
32
|
id: 'test-connection-id',
|
|
33
|
+
// Mirrors imap-flow.js, which always resolves a port before authenticating. Without a
|
|
34
|
+
// default the OAUTHBEARER payload builds `port=undefined`.
|
|
35
|
+
port: overrides.port || 993,
|
|
33
36
|
capabilities: new Map(overrides.capabilities || [['IMAP4rev1', true]]),
|
|
34
37
|
enabled: new Set(overrides.enabled || []),
|
|
35
38
|
authCapabilities: new Map(),
|
|
@@ -58,6 +61,9 @@ const createMockConnection = (overrides = {}) => {
|
|
|
58
61
|
};
|
|
59
62
|
};
|
|
60
63
|
|
|
64
|
+
// Decodes the base64 SASL payload that authenticate() hands to exec().
|
|
65
|
+
const decodeSaslPayload = execArgs => Buffer.from(execArgs.args[1].value, 'base64').toString();
|
|
66
|
+
|
|
61
67
|
// ============================================
|
|
62
68
|
// CAPABILITY Command Tests
|
|
63
69
|
// ============================================
|
|
@@ -7441,6 +7447,55 @@ module.exports['Commands: authenticate with OAUTHBEARER'] = async test => {
|
|
|
7441
7447
|
test.equal(execArgs.cmd, 'AUTHENTICATE');
|
|
7442
7448
|
test.equal(execArgs.args[0].value, 'OAUTHBEARER');
|
|
7443
7449
|
test.ok(connection.authCapabilities.has('AUTH=OAUTHBEARER'));
|
|
7450
|
+
test.ok(decodeSaslPayload(execArgs).includes('port=993'), 'the payload should report the connection port');
|
|
7451
|
+
test.done();
|
|
7452
|
+
};
|
|
7453
|
+
|
|
7454
|
+
module.exports['Commands: OAUTHBEARER payload reports the port actually in use'] = async test => {
|
|
7455
|
+
// Regression: the port field was hardcoded to 993 - see lib/commands/authenticate.js.
|
|
7456
|
+
let execArgs = null;
|
|
7457
|
+
const connection = createMockConnection({
|
|
7458
|
+
state: 1,
|
|
7459
|
+
capabilities: new Map([['AUTH=OAUTHBEARER', true]]),
|
|
7460
|
+
servername: 'imap.example.com',
|
|
7461
|
+
port: 143,
|
|
7462
|
+
authCapabilities: new Map(),
|
|
7463
|
+
exec: async (cmd, args) => {
|
|
7464
|
+
execArgs = { cmd, args };
|
|
7465
|
+
return { next: () => {} };
|
|
7466
|
+
},
|
|
7467
|
+
write: () => {}
|
|
7468
|
+
});
|
|
7469
|
+
|
|
7470
|
+
await authenticateCommand(connection, 'user@example.com', { accessToken: 'token123' });
|
|
7471
|
+
|
|
7472
|
+
const payload = decodeSaslPayload(execArgs);
|
|
7473
|
+
test.ok(payload.includes('port=143'), `expected port=143 in the SASL payload, got: ${JSON.stringify(payload)}`);
|
|
7474
|
+
test.ok(payload.includes('host=imap.example.com'), 'the host field should still describe the connection');
|
|
7475
|
+
test.done();
|
|
7476
|
+
};
|
|
7477
|
+
|
|
7478
|
+
module.exports['Commands: OAUTHBEARER payload falls back to host when servername is false'] = async test => {
|
|
7479
|
+
// imap-flow.js sets servername = false for a bare-IP host, which rendered as "host=false".
|
|
7480
|
+
let execArgs = null;
|
|
7481
|
+
const connection = createMockConnection({
|
|
7482
|
+
state: 1,
|
|
7483
|
+
capabilities: new Map([['AUTH=OAUTHBEARER', true]]),
|
|
7484
|
+
servername: false,
|
|
7485
|
+
host: '198.51.100.7',
|
|
7486
|
+
authCapabilities: new Map(),
|
|
7487
|
+
exec: async (cmd, args) => {
|
|
7488
|
+
execArgs = { cmd, args };
|
|
7489
|
+
return { next: () => {} };
|
|
7490
|
+
},
|
|
7491
|
+
write: () => {}
|
|
7492
|
+
});
|
|
7493
|
+
|
|
7494
|
+
await authenticateCommand(connection, 'user@example.com', { accessToken: 'token123' });
|
|
7495
|
+
|
|
7496
|
+
const payload = decodeSaslPayload(execArgs);
|
|
7497
|
+
test.ok(payload.includes('host=198.51.100.7'), `expected the host as fallback, got: ${JSON.stringify(payload)}`);
|
|
7498
|
+
test.ok(!payload.includes('host=false'), 'the literal "host=false" must never be sent');
|
|
7444
7499
|
test.done();
|
|
7445
7500
|
};
|
|
7446
7501
|
|
|
@@ -143,7 +143,7 @@ module.exports['Connection Edge: Connection with default values'] = test => {
|
|
|
143
143
|
});
|
|
144
144
|
|
|
145
145
|
test.ok(client, 'Client should be created with defaults');
|
|
146
|
-
test.equal(client.port,
|
|
146
|
+
test.equal(client.port, 143, 'Should use default port');
|
|
147
147
|
test.equal(client.secureConnection, false, 'Should default to non-secure');
|
|
148
148
|
test.done();
|
|
149
149
|
};
|
|
@@ -155,7 +155,7 @@ module.exports['Connection Edge: Port number handling'] = test => {
|
|
|
155
155
|
secure: false,
|
|
156
156
|
auth: { user: 'test', pass: 'test' }
|
|
157
157
|
});
|
|
158
|
-
test.equal(client1.port,
|
|
158
|
+
test.equal(client1.port, 143, 'Default non-secure port');
|
|
159
159
|
|
|
160
160
|
let client2 = new ImapFlow({
|
|
161
161
|
host: 'imap.example.com',
|
package/test/connection-test.js
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
name: "ImapFlow CodeQL config"
|
|
2
|
-
|
|
3
|
-
# Exclude code that is not part of the published library runtime from analysis.
|
|
4
|
-
# These paths generate only false-positive noise:
|
|
5
|
-
# - test fixtures intentionally feed malformed/hostile input and exercise
|
|
6
|
-
# edge cases that are not representative of production usage
|
|
7
|
-
# - the example scripts hardcode demo credentials and connection details for
|
|
8
|
-
# local experimentation and are not maintained as production code
|
|
9
|
-
paths-ignore:
|
|
10
|
-
- test
|
|
11
|
-
- '**/test/**'
|
|
12
|
-
- examples
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# For most projects, this workflow file will not need changing; you simply need
|
|
2
|
-
# to commit it to your repository.
|
|
3
|
-
#
|
|
4
|
-
# You may wish to alter this file to override the set of languages analyzed,
|
|
5
|
-
# or to provide custom queries or build logic.
|
|
6
|
-
#
|
|
7
|
-
# ******** NOTE ********
|
|
8
|
-
# We have attempted to detect the languages in your repository. Please check
|
|
9
|
-
# the `language` matrix defined below to confirm you have the correct set of
|
|
10
|
-
# supported CodeQL languages.
|
|
11
|
-
#
|
|
12
|
-
name: "CodeQL Advanced"
|
|
13
|
-
|
|
14
|
-
on:
|
|
15
|
-
push:
|
|
16
|
-
branches: [ "master" ]
|
|
17
|
-
pull_request:
|
|
18
|
-
branches: [ "master" ]
|
|
19
|
-
schedule:
|
|
20
|
-
- cron: '40 17 * * 6'
|
|
21
|
-
|
|
22
|
-
jobs:
|
|
23
|
-
analyze:
|
|
24
|
-
name: Analyze (${{ matrix.language }})
|
|
25
|
-
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
|
26
|
-
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
|
27
|
-
# - https://gh.io/supported-runners-and-hardware-resources
|
|
28
|
-
# - https://gh.io/using-larger-runners (GitHub.com only)
|
|
29
|
-
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
|
30
|
-
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
|
31
|
-
permissions:
|
|
32
|
-
# required for all workflows
|
|
33
|
-
security-events: write
|
|
34
|
-
|
|
35
|
-
# required to fetch internal or private CodeQL packs
|
|
36
|
-
packages: read
|
|
37
|
-
|
|
38
|
-
# only required for workflows in private repositories
|
|
39
|
-
actions: read
|
|
40
|
-
contents: read
|
|
41
|
-
|
|
42
|
-
strategy:
|
|
43
|
-
fail-fast: false
|
|
44
|
-
matrix:
|
|
45
|
-
include:
|
|
46
|
-
- language: actions
|
|
47
|
-
build-mode: none
|
|
48
|
-
- language: javascript-typescript
|
|
49
|
-
build-mode: none
|
|
50
|
-
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
|
51
|
-
# Use `c-cpp` to analyze code written in C, C++ or both
|
|
52
|
-
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
|
53
|
-
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
|
54
|
-
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
|
55
|
-
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
|
56
|
-
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
|
57
|
-
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
|
58
|
-
steps:
|
|
59
|
-
- name: Checkout repository
|
|
60
|
-
uses: actions/checkout@v6
|
|
61
|
-
|
|
62
|
-
# Add any setup steps before running the `github/codeql-action/init` action.
|
|
63
|
-
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
|
64
|
-
# or others). This is typically only required for manual builds.
|
|
65
|
-
# - name: Setup runtime (example)
|
|
66
|
-
# uses: actions/setup-example@v1
|
|
67
|
-
|
|
68
|
-
# Initializes the CodeQL tools for scanning.
|
|
69
|
-
- name: Initialize CodeQL
|
|
70
|
-
uses: github/codeql-action/init@v4
|
|
71
|
-
with:
|
|
72
|
-
languages: ${{ matrix.language }}
|
|
73
|
-
build-mode: ${{ matrix.build-mode }}
|
|
74
|
-
config-file: ./.github/codeql/codeql-config.yml
|
|
75
|
-
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
76
|
-
# By default, queries listed here will override any specified in a config file.
|
|
77
|
-
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
78
|
-
|
|
79
|
-
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
|
80
|
-
# queries: security-extended,security-and-quality
|
|
81
|
-
|
|
82
|
-
# If the analyze step fails for one of the languages you are analyzing with
|
|
83
|
-
# "We were unable to automatically build your code", modify the matrix above
|
|
84
|
-
# to set the build mode to "manual" for that language. Then modify this step
|
|
85
|
-
# to build your code.
|
|
86
|
-
# ℹ️ Command-line programs to run using the OS shell.
|
|
87
|
-
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
|
88
|
-
- name: Run manual build steps
|
|
89
|
-
if: matrix.build-mode == 'manual'
|
|
90
|
-
shell: bash
|
|
91
|
-
run: |
|
|
92
|
-
echo 'If you are using a "manual" build mode for one or more of the' \
|
|
93
|
-
'languages you are analyzing, replace this with the commands to build' \
|
|
94
|
-
'your code, for example:'
|
|
95
|
-
echo ' make bootstrap'
|
|
96
|
-
echo ' make release'
|
|
97
|
-
exit 1
|
|
98
|
-
|
|
99
|
-
- name: Perform CodeQL Analysis
|
|
100
|
-
uses: github/codeql-action/analyze@v4
|
|
101
|
-
with:
|
|
102
|
-
category: "/language:${{matrix.language}}"
|