Skip to content

🚨 [security] [ruby] Update net-imap 0.6.3 → 0.6.4 (minor)#5751

Merged
StephenHulme merged 1 commit intodevelopfrom
depfu/update/net-imap-0.6.4
May 6, 2026
Merged

🚨 [security] [ruby] Update net-imap 0.6.3 → 0.6.4 (minor)#5751
StephenHulme merged 1 commit intodevelopfrom
depfu/update/net-imap-0.6.4

Conversation

@depfu
Copy link
Copy Markdown
Contributor

@depfu depfu Bot commented May 5, 2026


🚨 Your current dependencies have known security vulnerabilities 🚨

This dependency update fixes known security vulnerabilities. Please see the details below and assess their impact carefully. We recommend to merge and deploy this as soon as possible!


Here is everything you need to know about this update. Please take a good look at what changed and the test results before merging this pull request.

What changed?

↗️ net-imap (indirect, 0.6.3 → 0.6.4) · Repo · Changelog

Security Advisories 🚨

🚨 net-imap vulnerable to STARTTLS stripping via invalid response timing

Summary

A man-in-the-middle attacker can cause Net::IMAP#starttls to return "successfully", without starting TLS.

Details

When using Net::IMAP#starttls to upgrade a plaintext connection to use TLS, a man-in-the-middle attacker can inject a tagged OK response with an easily predictable tag. By sending the response before the client finishes sending the command, the command completes "successfully" before the response handler is registered. This allows #starttls to return without error, but the response handler is never invoked, the TLS connection is never established, and the socket remains unencrypted.

This allows man-in-the-middle attackers to perform a STARTTLS stripping attack, unless the client code explicitly checks Net::IMAP#tls_verified?.

Impact

TLS bypass, leading to cleartext transmission of sensitive information.

Mitigation

  • Upgrade to a patched version of net-imap that raises an exception whenever #starttls does not establish TLS.
  • Connect to an implicit TLS port, rather than use STARTTLS with a cleartext port.
    This is strongly recommended anyway:
    • RFC 8314: Cleartext Considered Obsolete: Use of Transport Layer Security (TLS) for Email Submission and Access
    • NO STARTTLS: Why TLS is better without STARTTLS, A Security Analysis of STARTTLS in the Email Context
  • Explicitly verify Net::IMAP#tls_verified? is true, before using the connection after #starttls.

🚨 net-imap has quadratic complexity when reading response literals

Summary

Net::IMAP::ResponseReader has quadratic time complexity when reading large responses containing many string literals. A hostile server can send responses which are crafted to exhaust the client's CPU for a denial of service attack.

Details

For each literal in a response, ResponseReader rescans the entire growing response buffer. The regular expression that is used to scan the response buffer runs in linear time. With many literals, this becomes O(n²) total work. The regular expression should run in constant time: it is anchored to the end and only the last 23 bytes of the buffer are relevant.

Because the algorithmic complexity is super-linear, this bypasses protection from max_response_size: a response can stay well below the default size limit while still causing very large CPU cost.

Net::IMAP::ResponseReader runs continuously in the receiver thread until the connection closes.

Impact

This consumes disproportionate CPU time in the client's receiver thread. A hostile server could use this to exhaust the client's CPU for a denial of service attack.

For a response near the default max_response_size, each individual regexp scan could take between 100 to 200ms on common modern hardware, and this may be repeated 200k times per megabyte of response. While the regexp is scanning, it retains the Global VM lock, preventing other threads from running.

Although other threads should not be completely blocked, their run time will be significantly impacted.

Mitigation

  • Upgrade to a patched version of net-imap that reads responses more efficiently.
  • Do not connect to untrusted IMAP servers.
  • When connecting to untrusted servers, a much smaller max_response_size (for example: 8KiB) will limit the impact. Although this is too small for fetching unpaginated message bodies, it should be enough for most other operations.

🚨 net-imap vulnerable to denial of service via high iteration count for `SCRAM-*` authentication

Summary

When authenticating a connection with SCRAM-SHA1 or SCRAM-SHA256, a hostile server can perform a computational denial-of-service attack on the client process by sending a big iteration count value.

Details

A hostile IMAP server can send an arbitrarily large PBKDF2 iteration count in the SCRAM server-first-message, causing the client to perform an expensive OpenSSL::KDF.pbkdf2_hmac call. Because the PBKDF2 function is a blocking C extension and holds onto Ruby’s Global VM Lock, it can freeze the entire Ruby VM for the duration of the computation.

OpenSSL enforces an effective maximum by using a 32-bit signed integer for the iteration count, Depending on hardware capabilities and OpenSSL version, this iteration count may be sufficient for to block all Ruby threads in the process for over seven minutes.

This is listed as one of the "Security Considerations", in RFC 7804:

A hostile server can perform a computational denial-of-service attack on clients by sending a big iteration count value. In order to defend against that, a client implementation can pick a maximum iteration count that it is willing to use and reject any values that exceed that threshold (in such cases, the client, of course, has to fail the authentication).

Impact

During SCRAM authentication to a hostile server, the entire Ruby VM will be locked for the duration of the computation. Depending on hardware capabilities and OpenSSL version, this may take many minutes.

OpenSSL::KDF.pbkdf2_hmac is a blocking C function, so Timeout cannot be used to guard against this. And it retains the Global VM lock, so other ruby threads will also be unable to run.

Mitigation

  • Upgrade to a patched version of net-imap that adds the max_iterations option to the SASL-* authenticators, and call Net::IMAP#authenticate with a max_iterations keyword argument.

    NOTE: The default max_iterations is 2³¹ - 1, the maximum signed 32 bit integer, the maximum allowed by OpenSSL.
    To prevent a denial of service attack, this must be set to a safe value, depending on hardware and version of OpenSSL.
    It is the user's responsibility to enforce minimum and maximum iteration counts that are appropriate for their security context.

  • Alternatively, avoid SCRAM-* mechanisms when authenticating to untrusted servers.

🚨 net-imap vulnerable to command Injection via unvalidated Symbol inputs

Summary

Symbol arguments to commands are vulnerable to a CRLF Injection / IMAP Command injection via Symbol arguments passed to IMAP commands.

Details

Symbol arguments represent IMAP "system flags", which are formatted as "atoms" (with no quoting) with a "\" prefix. Vulnerable versions of Net::IMAP sends the symbol name directly to the socket, with no validation.

Because the Symbol input is unvalidated, it could contain invalid flag characters, including SP and CRLF, which could be used to finish the current command and inject new commands.

Although IMAP flag arguments are only valid input for a few IMAP commands, most Net::IMAP commands use generic argument handling, and will allow Symbol (flag) inputs.

Note also that the list of valid symbol inputs should be restricted to an enumerated set of standard RFC defined flag types, which have each been given specific defined semantics. Any user-provided values outside of that list of standard "system flags" needs to use the IMAP keyword syntax, which are sent as atoms, i.e: string inputs. Under no circumstances should #to_sym ever be called on unvetted user-provided input: that will always be a bug in the calling code for the simple reason that user_input_atom is as \user_input_atom.

For forward compatibility with future IMAP extentions, Net::IMAP, does not restrict flag inputs to an enumerated list. That is the responsibility of the calling application code, which knows which flag semantics are valid for its context.

Impact

If a developer passes user-controlled input as a Symbol to most Net::IMAP commands, an attacker can append CRLF sequence followed by a new IMAP command (like DELETE mailbox).

Mitigation

  • Upgrade to a version of Net::IMAP that validates Symbols are valid as an IMAP flag.

  • User-provided input should never be able to control calling #to_sym on string arguments.

    For example, do not unsafely serialize and deserialize command arguments (e.g. with YAML or Marshal) in a way that could create unvetted Symbol arguments.

  • For the few IMAP commands which do allow flag arguments, it may be appropriate to hard-code Symbol arguments or restrict them to an enumerated list which is valid for the calling application.

🚨 net-imap vulnerable to command Injection via "raw" arguments to multiple commands

Summary

Several Net::IMAP commands accept a raw string argument that is sent to the server without validation or escaping. If this string is derived from user-controlled input, it may contain contain CRLF sequences, which an attacker can use to inject arbitrary IMAP commands.

Details

Net::IMAP's generic argument handling, used by most command arguments, interprets string arguments as an IMAP astring. Depending on the string contents and the connection's UTF-8 support, this encodes strings as either a atom, quoted, or literal. These are safe from command or argument injection.

But the following commands transform specific String arguments to Net::IMAP::RawData, which bypasses normal argument validation and encoding and prints the string directly to the socket:

  • #uid_search, #search
    • when criteria is a String, it is sent raw
  • #uid_fetch, #fetch
    • when attr is a String, it is sent raw
    • when attr is an Array, each String in attr is sent raw
  • #uid_store, #store
    • when attr is a String, it is sent raw
  • #setquota:
    • limit is interpolated with #to_s and that string is sent raw

Because these string arguments are sent without any neutralization, they serve as a direct vector for command splitting. Any user controlled data interpolated into these strings can be used to break out of the intended command context.

Using "raw data" arguments for #uid_store, #store, and #setquota I both inappropriate and unnecessary. Net::IMAP's generic argument handling is sufficient to safely validate and encode their arguments. Users of the library probably do not expect arguments to these commands to be sent raw and might not be wary of passing unvalidated input.

The API for search criteria and fetch attributes is intentionally low-level and "close to the wire". It allows developers to use some IMAP extensions without requiring explicit support from the library and allows developers to use complex IMAP grammar without complex argument translation. Even so, basic validation is appropriate and could neutralize command injection.

Although this was explicitly documented for search criteria, it was insufficiently documented for fetch attr. So developers may not have realized that the attr argument to #fetch and #uid_fetch is sent as "raw data".

Impact

If a developer passes an unvalidated user-controlled input for one of these method arguments, an attacker can append CRLF sequence followed by a new IMAP command (like DELETE mailbox). Although this does not directly enable data exfiltration, it could be combined with other attack vectors or knowledge of the target system's attributes, e.g.: shared mail folders or the application's installed response handlers.

The SEARCH, STORE, and FETCH commands, and their UID variants are some of the most commonly used features of the library. Applications that build search queries or fetch attributes dynamically based on user input (e.g., mail clients or archival tools) may be at significant risk.

Expected use of Net::IMAP#setquota is much more limited: SETQUOTA is often only usable by users with special administrative privileges. Depending on the server, quota administration might be managed through server configuration rather than via the IMAP protocol SETQUOTA command. It is expected to be uncommonly used in system administration scripts or in interactive sessions, it should be completely controlled by trusted users, and should only use trusted inputs. Calling #setquota with untrusted user input is expected to be a very uncommon use case. Please note however this might be combined with other attacks, for example CSRF, which provide unauthorized access to trusted inputs, and may specifically target users or scripts with administrator privileges.

Mitigation

  • Update to a patched version of net-imap which:
    • validates that Net::IMAP::RawData is composed of well-formed IMAP text, literal, and literal8 values, with no unescaped NULL, CR, or LF bytes.
    • does not use Net::IMAP::RawData for #store, #uid_store, or #setquota.
  • Prefer to send search criteria as an array of key value pairs. Avoid sending it as an interpolated string.
  • If an immediate upgrade is not possible:
    • String inputs to search criteria and fetch attributes can be validated against command injection by checking for \r and \n characters.
    • Hard-coding the store attr argument is often appropriate. Alternatively, user controlled inputs can be restricted to a small enumerated list which is valid for the calling application.
    • Use Kernel#Integer to coerce and validate user controlled inputs to #setquota limit.
Release Notes

0.6.4

What's Changed

🔒 Security

This release contains fixes for multiple vulnerabilities concerning STARTTLS stripping, argument validation, and denial of service attacks.

Warning

#664 fixes a STARTTLS stripping vulnerability (GHSA-vcgp-9326-pqcp).
Without this fix, a man-in-the-middle attacker can cause Net::IMAP#starttls to return "successfully", without starting TLS.

Important

Argument validation is significantly improved. Several injection vulnerabilities have been fixed:
#657 fixes CRLF/command/argument injection via Symbol arguments (GHSA-75xq-5h9v-w6px).
#658 fixes CRLF/command/argument injection via the attr argument to #store/#uid_store (GHSA-hm49-wcqc-g2xg)
#659 fixes CRLF/command/argument injection via the storage_limit argument to #setquota (GHSA-hm49-wcqc-g2xg).
#660 fixes CRLF/command injection via RawData (GHSA-hm49-wcqc-g2xg):

  • #search and #uid_search send criteria as raw data, when it is a String
  • #fetch and #uid_fetch send attr as raw data, when it is a String.
    When attr is an Array, its String members are sent as raw data.

Caution

RawData does not defend against other forms of argument injection! It is an intentionally low-level API.

Note

Two denial of service vulnerabilities have been addressed.
These are generally only relevant when connecting to an untrusted hostile server (or without TLS).

#642 fixes quadratic time complexity when reading large responses containing many string literals (GHSA-q2mw-fvj9-vvcw).
#654 adds a configurable max_iterations count for SCRAM-* authentication (GHSA-87pf-fpwv-p7m7).

The default ScramAuthenticator#max_iterations is 2**31 - 1 (max 32-bit signed int), which was already OpenSSL's maximum value. It provides no protection against hostile servers unless it is explicitly set to a lower value by the user.

Breaking Changes

  • ResponseReader memoizes Config#max_response_size in #642.
    Changes to #max_response_size now take effect once per response, not on every IO#read.
    NOTE: It is not expected that this will affect any current usage. See the PR for details.

Added

  • ✨ Support BINARY extention to #append (RFC3516) by @nevans in #616
  • ✨ Support LITERAL+ and LITERAL- non-synchronizing literals (RFC7888) by @nevans in #649
  • 🔒 Add ScramAuthenticator#max_iterations by @nevans in #654
  • 🏷️ Add number64 and nz-number64 to NumValidator by @nevans in #625
  • ♻️ Add MailboxQuota#quota_root alias by @nevans in #636
  • 🔍 Simplify Net::IMAP#inspect with basic state by @nevans in #612
  • 🥅 Add ResponseParseError#parser_methods (and override #==) by @nevans in #615

Fixed

  • 🔒 Fix STARTTLS stripping vulnerability in #664, reported by @Masamuneee
  • Argument validation, reported by @manunio
    • 🔒️ Strictly validate symbol (\flag) arguments in #657
    • 🔒️ Validate and send STORE attr as an atom in #658
    • 🔒 Validate #setquota storage limit argument in #659
    • 🔒 Validate RawData for CRLF injection in #660
    • 📚 Improve documentation of RawData arguments in #661
  • ⚡ Much faster ResponseReader performance by @nevans in #642
  • 🥅 Successfully parse invalid response code data by @nevans in #614
  • Fix JRuby SSL connection failure: use SSLContext#setup instead of #freeze by @idahomst in #627
  • 🐛 Fix InvalidResponseError in #get_tagged_response by @nevans in #633
  • Pass an Exception to #raise by @eregon in #643
  • 🐛 Fix empty SearchResult#to_sequence_set in #644, reported by @Quintasan
  • 🐛 Wait to continue RawData literals by @nevans in #660

Documentation

  • 📚 Fix rdoc 7.2 compatibility (section bugfix) by @nevans in #617
  • 📚 Switch back to rdoc's darkfish generator (🚧TMP) by @nevans in #618
  • 📚 Use .document and .rdoc_options files, where possible by @nevans in #619
  • Update README example: Expunge is implicit in MOVE by @sebbASF in #623
  • 📚️ Fix QUOTA documentation by @nevans in #636
  • 📚 Minor documentation fixes by @nevans in #638
  • 📚 Improve documentation of RawData arguments by @nevans in #661

Other Changes

  • Handle deep response recursion as ResponseParseError by @Masamuneee in #629

Miscellaneous

  • ✅ Fix typo in FakeServer (tests only) by @nevans in #620
  • ⬆️ Bump step-security/harden-runner from 2.14.2 to 2.15.0 by @dependabot[bot] in #621
  • Bump step-security/harden-runner from 2.15.0 to 2.15.1 by @dependabot[bot] in #626
  • ⬆️ Bump step-security/harden-runner from 2.15.1 to 2.16.0 by @dependabot[bot] in #628
  • ⬆️ Bump actions/configure-pages from 5 to 6 by @dependabot[bot] in #635
  • ✅ Test #setquota by @nevans in #636
  • ⬆️ Bump actions/deploy-pages from 4 to 5 by @dependabot[bot] in #634
  • ⬆️ Bump step-security/harden-runner from 2.16.0 to 2.17.0 by @dependabot[bot] in #639
  • Test TruffleRuby release in CI for improved stability by @eregon in #640
  • ⬆️ Bump actions/upload-pages-artifact from 4 to 5 by @dependabot[bot] in #646
  • ⬆️ Bump step-security/harden-runner from 2.17.0 to 2.19.0 by @dependabot[bot] in #647

New Contributors

Full Changelog: v0.6.3...v0.6.4

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.


Depfu Status

Depfu will automatically keep this PR conflict-free, as long as you don't add any commits to this branch yourself. You can also trigger a rebase manually by commenting with @depfu rebase.

All Depfu comment commands
@​depfu rebase
Rebases against your default branch and redoes this update
@​depfu recreate
Recreates this PR, overwriting any edits that you've made to it
@​depfu merge
Merges this PR once your tests are passing and conflicts are resolved
@​depfu cancel merge
Cancels automatic merging of this PR
@​depfu close
Closes this PR and deletes the branch
@​depfu reopen
Restores the branch and reopens this PR (if it's closed)
@​depfu pause
Ignores all future updates for this dependency and closes this PR
@​depfu pause [minor|major]
Ignores all future minor/major updates for this dependency and closes this PR
@​depfu resume
Future versions of this dependency will create PRs again (leaves this PR as is)

@depfu depfu Bot added dependencies Pull requests that update a dependency file Security Update A label to identify dependency updates containing security fixes labels May 5, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.29%. Comparing base (7bcf30f) to head (e650e3e).

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #5751      +/-   ##
===========================================
- Coverage    87.30%   87.29%   -0.02%     
===========================================
  Files         1476     1476              
  Lines        33517    33517              
  Branches      3549     3549              
===========================================
- Hits         29263    29258       -5     
- Misses        4233     4238       +5     
  Partials        21       21              
Flag Coverage Δ
javascript 50.00% <ø> (ø)
ruby 87.31% <ø> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@StephenHulme StephenHulme merged commit 0d497b7 into develop May 6, 2026
18 checks passed
@StephenHulme StephenHulme deleted the depfu/update/net-imap-0.6.4 branch May 6, 2026 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file Security Update A label to identify dependency updates containing security fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant