Skip to content

fix(deps): update all dependencies (major) - #255

Merged
barrfalk merged 9 commits into
mainfrom
renovate/major-all-dependencies
Oct 9, 2026
Merged

barrfalk merged 9 commits into
mainfrom
renovate/major-all-dependencies

Conversation

@renovate

@renovate renovate Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence Type Update Pending
@eslint/js (source) ^9.38.0 → ^10.0.0 age confidence devDependencies major
@eslint/js (source) ^9.39.4 → ^10.0.0 age confidence devDependencies major
@nestjs/cli ^11.0.21 → ^12.0.0 age confidence dependencies major
@nestjs/common (source) ^11.0.6 → ^12.0.0 age confidence dependencies major 12.1.2
@nestjs/config ^4.0.0 → ^12.0.0 age confidence dependencies major
@nestjs/core (source) ^11.0.6 → ^12.0.0 age confidence dependencies major 12.1.2
@nestjs/passport ^11.0.5 → ^12.0.0 age confidence dependencies major
@nestjs/platform-express (source) ^11.0.6 → ^12.0.0 age confidence dependencies major 12.1.2
@nestjs/schematics ^11.0.0 → ^12.0.0 age confidence dependencies major
@nestjs/swagger ^11.0.3 → ^12.0.0 age confidence dependencies major
@nestjs/terminus ^11.0.0 → ^12.0.0 age confidence dependencies major
@nestjs/testing (source) ^11.0.6 → ^12.0.0 age confidence dependencies major 12.1.2
@nestjs/typeorm ^11.0.0 → ^12.0.0 age confidence dependencies major
@testing-library/jest-dom ^6.0.0 → ^7.0.0 age confidence devDependencies major
@vitejs/plugin-react (source) ^5.0.0 → ^6.0.0 age confidence dependencies major 6.1.2
@vitest/coverage-v8 (source) ^4.0.0 → ^5.0.0 age confidence devDependencies major 5.0.3
@vitest/coverage-v8 (source) ^4.0.0 → ^5.0.0 age confidence devDependencies major 5.0.3
@vitest/ui (source) ^4.0.0 → ^5.0.0 age confidence devDependencies major 5.0.3
actions/cache v5 → v6 age confidence action major
actions/checkout v6 → v7 age confidence action major
actions/checkout v4 → v7 age confidence action major
actions/setup-node v6 → v7 age confidence action major
bcgov/action-crunchy v1.2.6 → v2.1.0 age confidence action major
dotenv ^17.0.0 → ^18.0.0 age confidence dependencies major 18.0.5
dotenv ^17.0.0 → ^18.0.0 age confidence dependencies major 18.0.5
dotenv ^17.4.2 → ^18.0.0 age confidence dependencies major 18.0.5
eslint (source) ^9.38.0 → ^10.0.0 age confidence devDependencies major 10.12.0
eslint (source) ^9.39.4 → ^10.0.0 age confidence devDependencies major 10.12.0
ioredis ^5.3.7 → ^6.0.0 age confidence dependencies major
js-yaml ^4.1.0 → ^5.0.0 age confidence dependencies major
js-yaml 4.3.0 → 5.4.1 age confidence overrides major
js-yaml ^4.3.2 → ^5.0.0 age confidence dependencies major
jsdom ^28.0.0 → ^30.0.0 age confidence devDependencies major 30.1.2
msw (source) ^2.7.3 → ^3.0.0 age confidence devDependencies major 3.0.2 (+1)
nest-winston ^1.10.1 → ^2.0.0 age confidence dependencies major
nodemailer (source) ^9.0.3 → ^10.0.0 age confidence dependencies major
softprops/action-gh-release v2 → v3 age confidence action major
typeorm (source) ^0.3.21 → ^1.0.0 age confidence dependencies major
typescript (source) ^5.7.3 → ^7.0.0 age confidence devDependencies major
typescript (source) ^5.2.2 → ^7.0.0 age confidence devDependencies major
unplugin-swc (source) ^1.5.1 → ^2.0.0 age confidence devDependencies major
uuid ^11.1.1 → ^14.0.0 age confidence dependencies major
vite (source) ^7.0.0 → ^8.0.0 age confidence dependencies major 8.3.3 (+1)

JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases

CVE-2026-53550 / GHSA-h67p-54hq-rp68

More information

Details

Summary

A crafted YAML document can trigger algorithmic CPU exhaustion in js-yaml merge-key processing (<<) by repeating the same alias many times in a merge sequence.
This causes quadratic parse-time behavior relative to input size and can block a Node.js worker/event loop for seconds with a relatively small payload (tens of KB), resulting in denial of service.

Details

The issue is in merge handling inside lib/loader.js:

  • storeMappingPair(...) iterates every element of a merge sequence when key tag is tag:yaml.org,2002:merge.
  • For each element, it calls mergeMappings(...).
  • mergeMappings(...) computes Object.keys(source) and performs _hasOwnProperty.call(destination, key) checks for each key.

When input is of the form:

a: &a {k0:0, k1:0, ..., kK:0}
b: {<<: [*a, *a, *a, ... repeated M times ...]}
all *a entries refer to the same anchored object. After the first merge, subsequent merges are semantically no-ops, but the parser still reprocesses all keys each time.
Resulting work is O(K * M), while input size is O(K + M), giving quadratic scaling as payload grows.
Relevant code path:
lib/loader.js in storeMappingPair(...) merge branch (keyTag === 'tag:yaml.org,2002:merge')
lib/loader.js mergeMappings(...)

Root cause

File: lib/loader.js
Function: storeMappingPair(state, _result, overridableKeys, keyTag, keyNode,
valueNode, startLine, startLineStart, startPos)
Lines: ~359-366

if (keyTag === 'tag:yaml.org,2002:merge') {
  if (Array.isArray(valueNode)) {
    for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
      mergeMappings(state, _result, valueNode[index], overridableKeys);
    }
  } else {
    mergeMappings(state, _result, valueNode, overridableKeys);
  }
}

When the merge value is a sequence (YAML 1.1 <<: [ *a, *a, ... ]), each element
is handed to mergeMappings() without deduplication. mergeMappings() then does

sourceKeys = Object.keys(source);
for (index = 0; index < sourceKeys.length; index += 1) {
  key = sourceKeys[index];
  if (!_hasOwnProperty.call(destination, key)) {
    setProperty(destination, key, source[key]);
    overridableKeys[key] = true;
  }
}

Every alias reference in the sequence resolves (by design) to the SAME object
via state.anchorMap. After the first merge, every subsequent merge of that same
reference is a pure no-op semantically, but still performs:

  • one Object.keys(source) call (O(K))
  • K _hasOwnProperty.call checks on the destination

Total: M * K hasOwnProperty checks + M Object.keys allocations, while the final
object and all observable side effects are identical to a single merge.

YAML semantics for <<: are idempotent and commutative over duplicate sources,
so collapsing duplicates preserves behavior exactly; this isn't a spec trade-off.

PoC

Environment:
js-yaml version: 4.1.1
Node.js: v24.5.0
Platform: arm64 macOS (reproduced consistently)
Reproduction script:
Create many keys in one anchored map (&a).
Merge that same alias repeatedly via <<: [*a, *a, ...].
Measure parse time and compare with control payload using single merge (<<: *a).
Observed repeated runs (same machine):
K=M=1000, input 9,909 bytes: ~33–36 ms
K=M=2000, input 20,909 bytes: ~121–123 ms
K=M=4000, input 42,909 bytes: ~524–537 ms
K=M=6000, input 64,909 bytes: ~1,608–1,829 ms
K=M=8000, input 86,909 bytes: ~3,395–3,565 ms
Control (single merge, similar key counts):
K=2000: ~1–2 ms
K=4000: ~3 ms
K=8000: ~5 ms
Also verified: repeated-merge output equals single-merge output (same key count and same JSON), confirming excess time is redundant computation.

Impact

This is a denial-of-service vulnerability (CPU exhaustion / algorithmic complexity).
Any service parsing untrusted YAML with js-yaml can be impacted, including API backends, CI tools, config processors, and automation services. An attacker can submit crafted YAML to significantly increase CPU time and reduce availability.

Suggested fix:

Dedupe the merge source list by reference before invoking mergeMappings. Any of
the following are minimal and preserve YAML 1.1 merge semantics:

dedupe in storeMappingPair:

if (keyTag === 'tag:yaml.org,2002:merge') {
  if (Array.isArray(valueNode)) {
    var seen = new Set();
    for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
      var src = valueNode[index];
      if (seen.has(src)) continue;   // idempotent; skip redundant alias
      seen.add(src);
      mergeMappings(state, _result, src, overridableKeys);
    }
  } else {
    mergeMappings(state, _result, valueNode, overridableKeys);
  }
}

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


js-yaml: YAML merge-key chains can force quadratic CPU consumption

CVE-2026-59869 / GHSA-52cp-r559-cp3m

More information

Details

Impact

js-yaml can spend quadratic CPU time parsing a document whose size grows only linearly. The issue is triggered by a chain of mappings where each mapping merges the previous one:

a0: &a0 { k0: 0 }
a1: &a1 { <<: *a0, k1: 1 }
a2: &a2 { <<: *a1, k2: 2 }
a3: &a3 { <<: *a2, k3: 3 }
...
b: *aN

For each new mapping, the loader has to enumerate the keys inherited from the previous mapping. With N chained mappings, this results in roughly 1 + 2 + ... + N merged-key visits, i.e., O(N^2) work for O(N) input size.

PoC

From N = 4000 delay become > 1s (doc size < 100K)

import { performance } from 'node:perf_hooks'
import { Buffer } from 'node:buffer'
import { load, YAML11_SCHEMA } from 'js-yaml'

const n = Number(process.argv[2] || 4000)

function makeMergeChain (count) {
  const lines = ['a0: &a0 { k0: 0 }']

  for (let i = 1; i < count; i++) {
    lines.push(`a${i}: &a${i} { <<: *a${i - 1}, k${i}: ${i} }`)
  }

  lines.push(`b: *a${count - 1}`)
  return `${lines.join('\n')}\n`
}

const source = makeMergeChain(n)

console.log(source.split('\n').slice(0, 8).join('\n'))
console.log('...')
console.log(source.split('\n').slice(-4).join('\n'))
console.log()
console.log(`N: ${n}`)
console.log(`YAML size: ${Buffer.byteLength(source)} bytes`)

const started = performance.now()
const result = load(source, { schema: YAML11_SCHEMA })
const elapsed = performance.now() - started

console.log(`parse time: ${elapsed.toFixed(1)} ms`)
console.log(`top-level keys: ${Object.keys(result).length}`)
console.log(`b keys: ${Object.keys(result.b).length}`)
Patches

Fix released. The most robust protection is to limit the total number of merged keys per parse call. This should close all past and future edge cases with merge. The default 10K-key limit should be okay in most cases.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


JS-YAML: Quadratic CPU consumption in !!omap resolution (3.x and 4.x) — CVE-2026-59870 fix not backported

GHSA-5p4m-2wfm-xmqj

More information

Details

Quadratic CPU consumption in !!omap resolution (js-yaml 3.x and 4.x)
Summary

resolveYamlOmap() enforces key uniqueness for !!omap sequences with a linear
scan (objectKeys.indexOf(...)) inside the per-element loop, making resolution
O(n²) in the number of entries. A modestly sized YAML document therefore
consumes disproportionate CPU inside yaml.load(), giving a denial of service
against any consumer that parses untrusted YAML.

!!omap is registered in the default schema
(lib/schema/default.js → require('../type/omap')), so a plain
yaml.load(untrustedInput) with no options is affected — no custom schema or
non-default configuration is required.

This is the same weakness as CVE-2026-59870 / GHSA-724g-mxrg-4qvm, which was
fixed in the 5.x line in 5.2.1. That fix was never backported: both currently
maintained legacy lines still carry the original implementation.

Affected versions
Line Latest tested Status
3.x 3.15.0 Affected — objectKeys.indexOf(pairKey) at lib/type/omap.js:29
4.x 4.3.0 Affected — objectKeys.indexOf(pairKey) at lib/type/omap.js:30
5.x 5.2.2 Not affected — fixed in 5.2.1 (uses a Set)

Both figures are the newest release of each line at the time of writing, so
this is not a "you are on an old version" issue.

Details

lib/type/omap.js (js-yaml 4.3.0):

if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey)
else return false

objectKeys grows by one element per entry, and Array.prototype.indexOf is a
linear scan, so resolving an n-entry !!omap performs roughly
1 + 2 + … + n comparisons — quadratic in n. The work happens synchronously
inside yaml.load(), blocking the event loop for its whole duration.

The 5.x line already solves exactly this by tracking seen keys in a Set
(src/tag/sequence/omap.ts):

if (carrier.seen.has(key)) return 'duplicate key in ordered map'
carrier.seen.add(key)
Proof of concept
// poc.js  —  node poc.js
const yaml = require('js-yaml');
const doc = n => '!!omap\n' + Array.from({length: n}, (_, i) => `- k${i}: ${i}`).join('\n') + '\n';

for (const n of [10000, 20000, 40000, 80000]) {
  const d = doc(n), t = Date.now();
  yaml.load(d);                      // default schema, no options
  console.log(`n=${n} bytes=${d.length} load=${Date.now() - t}ms`);
}
Measured (node v20.20.2, default heap, no flags)

js-yaml 4.3.0

n=10000  bytes=137787   load=54ms
n=20000  bytes=297787   load=169ms
n=40000  bytes=617787   load=646ms
n=80000  bytes=1257787  load=2607ms

js-yaml 3.15.0

n=10000  bytes=137787   load=53ms
n=20000  bytes=297787   load=166ms
n=40000  bytes=617787   load=641ms
n=80000  bytes=1257787  load=2567ms

Runtime grows by a factor of ~4 for each doubling of n, which is the
signature of O(n²) (linear growth would be ~2×).

Scaling further: a 2.48 MB document with 150,000 entries blocked
yaml.load() for 10.8 seconds.

Impact

Any service that parses attacker-influenced YAML with js-yaml 3.x or 4.x can be
stalled with a small input. Because the loop is synchronous, a single request
blocks the Node.js event loop and stalls every other request in the process —
so the amplification is per-process, not just per-request.

Suggested severity: consistent with CVE-2026-59870 (the same weakness in
5.x), i.e. Availability-only impact, network attack vector, no privileges or
user interaction required.

Suggested fix

Mirror the 5.x fix — replace the linear scan with a Set:

// lib/type/omap.js
const seen = new Set()
// ...
if (seen.has(pairKey)) return false
seen.add(pairKey)

This preserves the existing duplicate-key rejection semantics exactly while
making resolution O(n). A maxOmapLength-style cap would also work, but the
Set matches what 5.x already ships and requires no new option.

References
  • CVE-2026-59870 / GHSA-724g-mxrg-4qvm — same weakness in 5.0.0–5.2.0, fixed in 5.2.1
  • lib/type/omap.js (3.x, 4.x) — the affected resolver
  • lib/schema/default.js — registers !!omap in the default schema
Discovery

Found by an automated static-analysis and executed-proof-of-concept scanner run
against js-yaml 4.2.0, then manually verified against 3.15.0 and 4.3.0 by
executing the proof of concept above. All timings in this report were measured
on the current releases of each line, not on the version originally scanned.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources

CVE-2026-84375 / GHSA-2883-xcg3-v3hh

More information

Details

Summary

maxTotalMergeKeys does not count empty mappings. An attacker can repeatedly merge a large sequence of them and consume significant CPU without reaching the configured limit.

Example
arr: &arr [{}, {}, {}, ...] # N empty mappings
targets:
  - <<: *arr                # repeated K times

For every target, the loader iterates all N elements of arr. This results in O(N * K) work while totalMergeKeys remains unchanged.

PoC
import { performance } from 'node:perf_hooks'
import { load, YAML11_SCHEMA } from 'js-yaml'

const n = 20000

const src =
  'arr: &arr [' + '{},'.repeat(n).slice(0, -1) + ']\n' +
  'targets:\n' +
  '  - <<: *arr\n'.repeat(n)

const started = performance.now()

load(src, { schema: YAML11_SCHEMA })

console.log(`${(performance.now() - started).toFixed(1)} ms`)

Observed results:

N YAML size Time
800 ~13 KB ~20 ms
3200 ~50 KB ~180 ms
20000 ~500 KB ~13 s
Impact

An attacker can submit a relatively small YAML document that causes prolonged CPU consumption despite the default maxTotalMergeKeys limit.

Fix

Count each merge-source mapping as one budget unit, in addition to counting its keys.

Difference with v5

In v3 & v4, merge is enabled by default. So, the severity score is higher.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources

GHSA-r3ph-w7gj-g6xm

More information

Details

Summary

maxTotalMergeKeys does not count empty mappings. An attacker can repeatedly merge a large sequence of them and consume significant CPU without reaching the configured limit.

Example
arr: &arr [{}, {}, {}, ...] # N empty mappings
targets:
  - <<: *arr                # repeated K times

For every target, the loader iterates all N elements of arr. This results in O(N * K) work while totalMergeKeys remains unchanged.

PoC
import { performance } from 'node:perf_hooks'
import { load, YAML11_SCHEMA } from 'js-yaml'

const n = 20000

const src =
  'arr: &arr [' + '{},'.repeat(n).slice(0, -1) + ']\n' +
  'targets:\n' +
  '  - <<: *arr\n'.repeat(n)

const started = performance.now()

load(src, { schema: YAML11_SCHEMA })

console.log(`${(performance.now() - started).toFixed(1)} ms`)

Observed results:

N YAML size Time
800 ~13 KB ~20 ms
3200 ~50 KB ~180 ms
20000 ~500 KB ~13 s
Impact

When merge keys are enabled, an attacker can submit a relatively small YAML document that causes prolonged CPU consumption despite the default maxTotalMergeKeys limit.

Fix

Count every merge source mapping as one budget unit in addition to counting its keys.

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nodemailer: Recipient-domain validation bypass via RFC 5322 comment mis-parsing leads to email delivery to an attacker-controlled domain

GHSA-cc9r-2j5m-2m83

More information

Details

Summary

Nodemailer's email-address parser treats an RFC 5322 comment ( ... ) inside the domain as a point to concatenate the surrounding text, rather than as folding whitespace (CFWS) that terminates the domain. Consequently a recipient address such as user@good-corp.com(x)evil.com is parsed and delivered to good-corp.comevil.com (registrable domain comevil.com, attacker‑controlled), while a conformant RFC 5322 parser terminates the domain at the comment and reads good-corp.com.

An application that decides whether it is allowed to email a recipient by parsing/validating the recipient's domain — with a strict RFC 5322 parser (used without inspecting parse defects) or with a naive prefix/substring allow‑list — and then hands the raw address to Nodemailer for delivery, can be induced to send mail to a domain the attacker controls. This is an Interpretation Conflict (CWE‑436), the same class as CVE‑2025‑13033, reached through the RFC 5322 comment construct (the "Comments" technique in PortSwigger's Splitting the email atom research, which produced a Postfix fix).

Severity is Moderate: exploitation requires the app's domain check to disagree with Nodemailer (see Impact for exactly which parsers do and do not). Verified end‑to‑end against a real RFC 5321 SMTP server (nodemailer 9.0.6 → aiosmtpd).

Details

Root cause is in lib/addressparser/index.js.

  1. The tokenizer registers the comment as an operator pair (Tokenizer.operators):
    '(': ')',            // line ~331
  2. When the closing ) is immediately followed by a non‑break character (anything other than space / tab / CR / LF / , / ;), the tokenizer marks that operator token with noBreak = true:
    // Tokenizer.checkChar, lines ~398-399
    if (nextChr && ![' ', '\t', '\r', '\n', ',', ';'].includes(nextChr)) {
        this.node.noBreak = true;
    }
  3. _handleAddress then glues the token that follows the comment onto the token that preceded it (dropping the comment):
    // _handleAddress, lines ~187-188
    if (prevToken && prevToken.noBreak && data[state].length) {
        data[state][data[state].length - 1] += token.value;   // <-- concatenation
    }

For the input user@good-corp.com(x)evil.com the tokens are text:"user@good-corp.com", op:"(", text:"x", op:")" (flagged noBreak), text:"evil.com". Step 3 appends evil.com onto user@good-corp.com, producing the single domain good-corp.comevil.com. The comment content (x) is discarded into the display‑name field.

RFC 5322 defines a comment as CFWS — semantically folding whitespace — and it may not appear inside a dot-atom. A comment therefore separates tokens and terminates the domain; the conformant reading of good-corp.com(x)evil.com is the domain good-corp.com (with the trailing evil.com being invalid/ignored). Nodemailer instead concatenates the two atoms across the removed comment, yielding a different, attacker‑registrable domain.

Nodemailer uses the parsed address for both the SMTP envelope (getEnvelope() → RCPT TO) and the emitted To:/From: headers, so the entire message is routed to the concatenated domain.

Related grammar defect (bonus, lower impact): nested comments are legal in RFC 5322, but the tokenizer closes the comment at the first ) (chr === this.operatorExpecting, line ~392), so a valid nested comment such as user@x.com(a(b)c) is mis‑balanced and mangled to x.comc). That particular output contains a stray ) and is rejected by a conformant MTA (501) — a bounce/robustness issue, not a misroute.

Suggested fix: treat a comment as folding whitespace that terminates the current token — i.e. do not propagate noBreak across a comment‑closing ) (restrict the noBreak optimization to quoted‑string closes), and support nested comments per RFC 5322. Equivalently, never emit a domain formed by concatenating two atoms that were separated only by a comment.

PoC

Environment: Node.js ≥ 18 and the published nodemailer@9.0.6. No special transport configuration is required; the discrepancy is in address parsing.

poc-comment.js:

'use strict';
const net = require('net');
const nodemailer = require('nodemailer'); // 9.0.6

const TRUSTED   = 'good-corp.com';
const RECIPIENT = 'user@good-corp.com(x)evil.com'; // RFC 5322 comment (x) between two domains

// tiny SMTP sink that prints the literal RCPT TO nodemailer transmits
const server = net.createServer(sock => {
  let buf = ''; sock.write('220 sink\r\n');
  sock.on('data', d => { buf += d; let i;
    while ((i = buf.indexOf('\r\n')) >= 0) { const line = buf.slice(0, i); buf = buf.slice(i + 2);
      const u = line.toUpperCase();
      if (u.startsWith('EHLO')) sock.write('250-sink\r\n250 8BITMIME\r\n');
      else if (u.startsWith('RCPT')) { console.log('nodemailer transmits :', line); sock.write('250 ok\r\n'); }
      else if (u.startsWith('DATA')) sock.write('354 go\r\n');
      else if (line === '.') sock.write('250 ok\r\n');
      else if (u.startsWith('QUIT')) { sock.write('221 bye\r\n'); sock.end(); }
      else sock.write('250 ok\r\n'); } });
});
server.listen(0, '127.0.0.1', async () => {
  const t = nodemailer.createTransport({ host: '127.0.0.1', port: server.address().port, secure: false });
  await t.sendMail({ from: 'app@good-corp.com', to: RECIPIENT, subject: 'hi', text: 'x' });
  t.close(); server.close();
});

Run:

npm init -y && npm install nodemailer@9.0.6
node poc-comment.js

Actual output (nodemailer 9.0.6):

nodemailer transmits : RCPT TO:<user@good-corp.comevil.com>

The application asked to mail user@good-corp.com(x)evil.com; Nodemailer delivers to good-corp.comevil.com — registrable domain comevil.com, which an attacker can register.

Verified against a real RFC 5321 server (containerized lab included with this report — docker compose up --build, case R8_comment_glue, receiver = aiosmtpd):

wire RCPT TO                     : RCPT TO:<user@good-corp.comevil.com>
real server                      : ACCEPTED (250)
recipient parsed by real server  : user@good-corp.comevil.com   (domain good-corp.comevil.com)
delivered To header              : x <user@good-corp.comevil.com>

Which parser sees what (the crux of exploitability):

Parser used by the application to gate/route Domain it reads from user@good-corp.com(x)evil.com Deceived?
Python email.policy.default (strict RFC 5322) good-corp.com (flags InvalidHeaderDefect) Yes, if defects are not checked
Naive prefix / substring allow‑list (startsWith/includes('@good-corp.com')) good-corp.com Yes
Nodemailer's own addressparser good-corp.comevil.com No
Python email.utils.getaddresses good-corp.comevil.com No
WHATWG url.domainToASCII good-corp.com(x)evil.com No
Impact
  • Who is impacted: applications that make a security or routing decision on the recipient domain using a parser that terminates the domain at the comment, while relying on Nodemailer for delivery — specifically those that validate with a strict RFC 5322 parser without inspecting parse defects, or with a prefix/substring/allow‑list check (e.g. "only send to @good-corp.com", employee‑only flows, "same‑tenant" routing). Applications that validate with Nodemailer's own addressparser, email.utils.getaddresses, or url.domainToASCII are not affected, which is why this is rated below the IDN/Punycode issue.
Patched in 9.1.0

Fixed in 902b63e.

Not propagating noBreak across the closing ) on its own breaks valid addresses, because CFWS is legal on either side of the @: user@(x)good-corp.com and user(x)@good-corp.com both come out mangled. A comment now joins what it separates only when one side carries the @, so those keep resolving while user@good-corp.com(x)evil.com terminates at good-corp.com.

Quoted-string and angle-address joining are unchanged. Nested comments are still not modelled, but the misroute is gone: user@x.com(a(b)c) now yields user@x.com.

Severity

  • CVSS Score: 6.5 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nodemailer: IDN/Punycode domain allow-list bypass leads to email delivery to an attacker-controlled domain

GHSA-wmmp-3585-3rmp

More information

Details

Summary

Nodemailer resolves an international (IDN / non-ASCII) recipient domain to a different Punycode xn-- label than every UTS‑46‑conformant parser (web browsers, the WHATWG URL Standard, Node's url.domainToASCII, Python's idna). Its address normalizer (_normalizeAddress in lib/mime-node/index.js) uses the bundled raw RFC‑3492 Punycode codec with no UTS‑46 mapping/normalization, so a domain that a standards‑compliant validator maps to a trusted domain is delivered by Nodemailer to a different, attacker‑registrable domain.

An application that applies a domain allow‑list / same‑domain check to a recipient using a normal IDN‑aware parser (or that shows the normalized recipient to a user for confirmation) and then relies on Nodemailer to deliver to that domain can be induced to send email to an unintended external domain. This is the same weakness class as CVE‑2025‑13033 (Interpretation Conflict, CWE‑436) but reached through IDN/Punycode rather than quoted local‑parts, and it is not addressed by the 7.0.7 fix.

Because the mismatch can be triggered with an invisible character (U+00AD SOFT HYPHEN) that UTS‑46 folds away to the exact trusted domain string, no visible look‑alike/homograph is required.

Details

lib/mime-node/index.js → _normalizeAddress(address) (around lines 1307–1346) splits the address at the last @ and normalizes the domain like this:

// lib/mime-node/index.js
try {
    if (/[\x80-�]/.test(user)) {
        encodedDomain = punycode.toUnicode(domain.toLowerCase());   // line ~1338
    } else {
        encodedDomain = punycode.toASCII(domain.toLowerCase());     // line ~1340
    }
} catch (_err) {
    // keep domain as supplied
}
return `${this._normalizeLocalPart(user)}@${encodedDomain}`;         // line ~1346

punycode here is the project’s bundled codec (lib/punycode/), which is a pure RFC 3492 (Punycode) implementation. The only normalization applied to the domain is .toLowerCase(). It performs none of the UTS‑46 “IDNA2008 + compatibility processing” steps that browsers and DNS‑facing resolvers apply before Punycode encoding, specifically:

  • removing Ignored code points such as U+00AD SOFT HYPHEN,
  • Mapping full‑width / compatibility characters to their canonical ASCII forms,
  • Unicode NFC normalization,
  • validity checks.

As a result, for any domain containing a UTS‑46‑mapped or ‑ignored character, Nodemailer’s punycode.toASCII(...) produces a different A‑label than url.domainToASCII(...) (Node ≥ 7 / WHATWG), new URL('http://'+domain), browsers, and Python’s idna (uts46=True). Nodemailer then uses its A‑label as:

  • the SMTP envelope recipient written to the wire as RCPT TO:<local@xn--…> (getEnvelope() → lib/smtp-connection/index.js _setEnvelope), and
  • the address emitted in the To: / From: headers (_convertAddresses).

So the domain a standards‑compliant validator computes and the domain Nodemailer actually delivers to disagree, on a syntactically valid, validator‑accepted address. Concrete divergences (verified on 9.0.6):

recipient (raw) UTS‑46 parser (url.domainToASCII) Nodemailer delivers to
victim@compa{U+00AD}ny.com (invisible soft hyphen) company.com xn--company-pka.com
victim@company.com (full‑width) company.com xn--mi7cd4afch9d.com
user@exámple.com (NFD a+U+0301) xn--exmple-qta.com xn--example-vge.com

This is the “Punycode / IDN parser discrepancy” technique documented in PortSwigger’s Splitting the email atom research (which produced e.g. Joomla CVE‑2024‑21725 and fixes in the PHP idna_convert library). The fix for CVE‑2025‑13033 (nodemailer 7.0.7) hardened the quoted‑local‑part path only; this IDN path is independent and still present in 9.0.6 (latest) and, given the long‑standing use of the bundled RFC‑3492 codec, earlier releases.

Suggested remediation: perform UTS‑46 processing before/at domain encoding so Nodemailer’s resolution matches browsers, validators, and DNS — e.g. use the runtime’s url.domainToASCII() (available since Node 7) instead of the raw punycode.toASCII, and decode with the matching UTS‑46 domainToUnicode. At minimum, reject a domain whose value changes under UTS‑46 mapping (i.e. punycode.toASCII(d) ≠ url.domainToASCII(d)).

PoC

Environment: Node.js ≥ 18, the published nodemailer@9.0.6. No special configuration; the discrepancy is in domain normalization itself.

poc-idn.js:

'use strict';
const net = require('net');
const url = require('url');
const nodemailer = require('nodemailer'); // 9.0.6

const TRUSTED   = 'company.com';                        // the only domain the app will mail
const RECIPIENT = 'victim@compa\u00ADny.com';           // attacker input: invisible U+00AD inside "company"

// The app's domain allow-list check, done the standard (UTS-46 / browser / WHATWG) way:
const seen = url.domainToASCII(RECIPIENT.split('@').pop());
console.log('validator (url.domainToASCII) sees:', JSON.stringify(seen),
            seen === TRUSTED ? '=> ALLOWED (equals trusted domain)' : '');

// A tiny SMTP sink that prints the literal RCPT TO Nodemailer transmits:
const server = net.createServer(sock => {
  let buf = ''; sock.write('220 sink\r\n');
  sock.on('data', d => { buf += d; let i;
    while ((i = buf.indexOf('\r\n')) >= 0) { const line = buf.slice(0, i); buf = buf.slice(i + 2);
      const u = line.toUpperCase();
      if (u.startsWith('EHLO')) sock.write('250-sink\r\n250 8BITMIME\r\n');
      else if (u.startsWith('RCPT')) { console.log('nodemailer transmits             :', line); sock.write('250 ok\r\n'); }
      else if (u.startsWith('DATA')) sock.write('354 go\r\n');
      else if (line === '.') sock.write('250 ok\r\n');
      else if (u.startsWith('QUIT')) { sock.write('221 bye\r\n'); sock.end(); }
      else sock.write('250 ok\r\n'); } });
});
server.listen(0, '127.0.0.1', async () => {
  const t = nodemailer.createTransport({ host: '127.0.0.1', port: server.address().port, secure: false });
  await t.sendMail({ from: 'app@company.com', to: RECIPIENT, subject: 'reset your password', text: 'secret link' });
  t.close(); server.close();
});

Run:

npm init -y && npm install nodemailer@9.0.6
node poc-idn.js

Actual output (Nodemailer 9.0.6):

validator (url.domainToASCII) sees: "company.com" => ALLOWED (equals trusted domain)
nodemailer transmits             : RCPT TO:<victim@xn--company-pka.com>

The application’s domain check approves company.com, but the message is sent to xn--company-pka.com — a different domain an attacker can register — carrying the To: header <victim@xn--company-pka.com> as well.

A containerized version that proves the same result against a real RFC 5321 SMTP server (aiosmtpd) is included alongside this report (docker compose up --build, cases R6/IDN); the receiving server accepts RCPT TO:<victim@xn--company-pka.com> and reports the recipient domain as xn--company-pka.com.

Impact

Any application that uses Nodemailer to send mail to a recipient whose domain is subjected to a security or trust decision made with a different (UTS‑46‑conformant) parser, and then trusts Nodemailer to deliver to that domain. This includes:

  • recipient allow‑list / block‑list / “same corporate domain” checks implemented with new URL(), url.domainToASCII, a browser‑side check, or an IDN library;
  • flows that display or log the normalized recipient domain for human confirmation (the shown company.com differs from the delivered xn--company-pka.com);
  • any domain‑gated feature (employee‑only registration, “send only to our tenant”, notification routing).
Patched in 9.1.0

Domain encoding now applies UTS-46 (259c32d), so victim@compa­ny.com resolves to company.com, matching url.domainToASCII and browsers.

One caveat on the suggested remediation, hardened in b212ac4: url.domainToASCII is a WHATWG host parser, not a pure UTS-46 mapper. It terminates the host at /, \\, ? and # and percent-decodes. Used unguarded it introduces a worse version of the same weakness, since user@attacker.example/mail.corp.example encodes to the deliverable user@attacker.example where the bundled Punycode codec left it intact and unroutable. Those characters are now kept away from the mapper.

On severity, "attacker-registrable" is doing significant work in the report: xn--company-pka.com decodes to a label containing U+00AD and xn--mi7cd4afch9d.com to full-width Latin, neither of which Verisign's IDN tables permit for a .com registration. The misdelivery and the confirmation-UI mismatch stand regardless, which is why this is rated level with the comment issue rather than above it.

Severity

  • CVSS Score: 6.5 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nodemailer: resolveContent() on a MailMessage bypasses disableFileAccess/disableUrlAccess when called with the legacy signature

[GHSA-8m3c-c648-2xjj](https://redirect.github.com/ad

❗ Important

✂ PR body was truncated to here.


Thanks for the PR!

Deployments, as required, will be available below:

Please create PRs in draft mode. Mark as ready to enable:

After merge, new images are deployed in:

@renovate renovate Bot added the security label Oct 5, 2026
@renovate

renovate Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

⚠️ Artifact update problem

Renovate failed to update artifacts related to this branch. You probably do not want to merge this PR as-is.

♻ Renovate will retry this branch, including artifacts, only when one of the following happens:

  • any of the package files in this branch needs updating, or
  • the branch becomes conflicted, or
  • you click the rebase/retry checkbox if found above, or
  • you rename this PR's title to start with "rebase!" to trigger it manually

The artifact failure details are included below:

File name: frontend/package-lock.json
npm warn Unknown env config "store". This will error in a future major version of npm. See `npm help npmrc` for supported config options.
npm warn ERESOLVE overriding peer dependency
npm warn While resolving: @typescript-eslint/eslint-plugin@8.57.0
npm warn Found: typescript@7.0.2
npm warn node_modules/typescript
npm warn   dev typescript@"^7.0.0" from the root project
npm warn   2 more (ts-api-utils, msw)
npm warn
npm warn Could not resolve dependency:
npm warn peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/eslint-plugin@8.57.0
npm warn node_modules/@typescript-eslint/eslint-plugin
npm warn   @typescript-eslint/eslint-plugin@"8.57.0" from typescript-eslint@8.57.0
npm warn   node_modules/typescript-eslint
npm warn
npm warn Conflicting peer dependency: typescript@5.9.3
npm warn node_modules/typescript
npm warn   peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/eslint-plugin@8.57.0
npm warn   node_modules/@typescript-eslint/eslint-plugin
npm warn     @typescript-eslint/eslint-plugin@"8.57.0" from typescript-eslint@8.57.0
npm warn     node_modules/typescript-eslint
npm warn ERESOLVE overriding peer dependency
npm warn While resolving: @typescript-eslint/parser@8.57.0
npm warn Found: typescript@7.0.2
npm warn node_modules/typescript
npm warn   dev typescript@"^7.0.0" from the root project
npm warn   2 more (ts-api-utils, msw)
npm warn
npm warn Could not resolve dependency:
npm warn peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/parser@8.57.0
npm warn node_modules/@typescript-eslint/parser
npm warn   peer @typescript-eslint/parser@"^8.57.0" from @typescript-eslint/eslint-plugin@8.57.0
npm warn   node_modules/@typescript-eslint/eslint-plugin
npm warn   1 more (typescript-eslint)
npm warn
npm warn Conflicting peer dependency: typescript@5.9.3
npm warn node_modules/typescript
npm warn   peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/parser@8.57.0
npm warn   node_modules/@typescript-eslint/parser
npm warn     peer @typescript-eslint/parser@"^8.57.0" from @typescript-eslint/eslint-plugin@8.57.0
npm warn     node_modules/@typescript-eslint/eslint-plugin
npm warn     1 more (typescript-eslint)
npm warn ERESOLVE overriding peer dependency
npm warn While resolving: @typescript-eslint/project-service@8.57.0
npm warn Found: typescript@7.0.2
npm warn node_modules/typescript
npm warn   dev typescript@"^7.0.0" from the root project
npm warn   2 more (ts-api-utils, msw)
npm warn
npm warn Could not resolve dependency:
npm warn peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/project-service@8.57.0
npm warn node_modules/@typescript-eslint/project-service
npm warn   @typescript-eslint/project-service@"8.57.0" from @typescript-eslint/typescript-estree@8.57.0
npm warn   node_modules/@typescript-eslint/typescript-estree
npm warn
npm warn Conflicting peer dependency: typescript@5.9.3
npm warn node_modules/typescript
npm warn   peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/project-service@8.57.0
npm warn   node_modules/@typescript-eslint/project-service
npm warn     @typescript-eslint/project-service@"8.57.0" from @typescript-eslint/typescript-estree@8.57.0
npm warn     node_modules/@typescript-eslint/typescript-estree
npm warn ERESOLVE overriding peer dependency
npm warn While resolving: @typescript-eslint/tsconfig-utils@8.57.0
npm warn Found: typescript@7.0.2
npm warn node_modules/typescript
npm warn   dev typescript@"^7.0.0" from the root project
npm warn   2 more (ts-api-utils, msw)
npm warn
npm warn Could not resolve dependency:
npm warn peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/tsconfig-utils@8.57.0
npm warn node_modules/@typescript-eslint/tsconfig-utils
npm warn   @typescript-eslint/tsconfig-utils@"^8.57.0" from @typescript-eslint/project-service@8.57.0
npm warn   node_modules/@typescript-eslint/project-service
npm warn   1 more (@typescript-eslint/typescript-estree)
npm warn
npm warn Conflicting peer dependency: typescript@5.9.3
npm warn node_modules/typescript
npm warn   peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/tsconfig-utils@8.57.0
npm warn   node_modules/@typescript-eslint/tsconfig-utils
npm warn     @typescript-eslint/tsconfig-utils@"^8.57.0" from @typescript-eslint/project-service@8.57.0
npm warn     node_modules/@typescript-eslint/project-service
npm warn     1 more (@typescript-eslint/typescript-estree)
npm warn ERESOLVE overriding peer dependency
npm warn While resolving: @typescript-eslint/type-utils@8.57.0
npm warn Found: typescript@7.0.2
npm warn node_modules/typescript
npm warn   dev typescript@"^7.0.0" from the root project
npm warn   2 more (ts-api-utils, msw)
npm warn
npm warn Could not resolve dependency:
npm warn peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/type-utils@8.57.0
npm warn node_modules/@typescript-eslint/type-utils
npm warn   @typescript-eslint/type-utils@"8.57.0" from @typescript-eslint/eslint-plugin@8.57.0
npm warn   node_modules/@typescript-eslint/eslint-plugin
npm warn
npm warn Conflicting peer dependency: typescript@5.9.3
npm warn node_modules/typescript
npm warn   peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/type-utils@8.57.0
npm warn   node_modules/@typescript-eslint/type-utils
npm warn     @typescript-eslint/type-utils@"8.57.0" from @typescript-eslint/eslint-plugin@8.57.0
npm warn     node_modules/@typescript-eslint/eslint-plugin
npm warn ERESOLVE overriding peer dependency
npm warn While resolving: @typescript-eslint/typescript-estree@8.57.0
npm warn Found: typescript@7.0.2
npm warn node_modules/typescript
npm warn   dev typescript@"^7.0.0" from the root project
npm warn   2 more (ts-api-utils, msw)
npm warn
npm warn Could not resolve dependency:
npm warn peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/typescript-estree@8.57.0
npm warn node_modules/@typescript-eslint/typescript-estree
npm warn   @typescript-eslint/typescript-estree@"8.57.0" from @typescript-eslint/parser@8.57.0
npm warn   node_modules/@typescript-eslint/parser
npm warn   3 more (@typescript-eslint/type-utils, ...)
npm warn
npm warn Conflicting peer dependency: typescript@5.9.3
npm warn node_modules/typescript
npm warn   peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/typescript-estree@8.57.0
npm warn   node_modules/@typescript-eslint/typescript-estree
npm warn     @typescript-eslint/typescript-estree@"8.57.0" from @typescript-eslint/parser@8.57.0
npm warn     node_modules/@typescript-eslint/parser
npm warn     3 more (@typescript-eslint/type-utils, ...)
npm warn ERESOLVE overriding peer dependency
npm warn While resolving: @typescript-eslint/utils@8.57.0
npm warn Found: typescript@7.0.2
npm warn node_modules/typescript
npm warn   dev typescript@"^7.0.0" from the root project
npm warn   2 more (ts-api-utils, msw)
npm warn
npm warn Could not resolve dependency:
npm warn peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/utils@8.57.0
npm warn node_modules/@typescript-eslint/utils
npm warn   @typescript-eslint/utils@"8.57.0" from @typescript-eslint/eslint-plugin@8.57.0
npm warn   node_modules/@typescript-eslint/eslint-plugin
npm warn   2 more (@typescript-eslint/type-utils, typescript-eslint)
npm warn
npm warn Conflicting peer dependency: typescript@5.9.3
npm warn node_modules/typescript
npm warn   peer typescript@">=4.8.4 <6.0.0" from @typescript-eslint/utils@8.57.0
npm warn   node_modules/@typescript-eslint/utils
npm warn     @typescript-eslint/utils@"8.57.0" from @typescript-eslint/eslint-plugin@8.57.0
npm warn     node_modules/@typescript-eslint/eslint-plugin
npm warn     2 more (@typescript-eslint/type-utils, typescript-eslint)
npm error code ERESOLVE
npm error ERESOLVE could not resolve
npm error
npm error While resolving: @vitest/coverage-v8@5.0.2
npm error Found: vitest@4.1.11
npm error node_modules/vitest
npm error   dev vitest@"^4.0.0" from the root project
npm error   peerOptional vitest@">= 0.32" from @testing-library/jest-dom@7.0.1
npm error   node_modules/@testing-library/jest-dom
npm error     dev @testing-library/jest-dom@"^7.0.0" from the root project
npm error
npm error Could not resolve dependency:
npm error peer vitest@"5.0.2" from @vitest/coverage-v8@5.0.2
npm error node_modules/@vitest/coverage-v8
npm error   dev @vitest/coverage-v8@"^5.0.0" from the root project
npm error
npm error Conflicting peer dependency: vitest@5.0.2
npm error node_modules/vitest
npm error   peer vitest@"5.0.2" from @vitest/coverage-v8@5.0.2
npm error   node_modules/@vitest/coverage-v8
npm error     dev @vitest/coverage-v8@"^5.0.0" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry this command with --force or --legacy-peer-deps to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error
npm error For a full report see:
npm error /runner/cache/others/npm/_logs/2026-10-06T20_35_43_670Z-eresolve-report.txt
npm error A complete log of this run can be found in: /runner/cache/others/npm/_logs/2026-10-06T20_35_43_670Z-debug-0.log

File name: backend/package-lock.json
npm warn Unknown env config "store". This will error in a future major version of npm. See `npm help npmrc` for supported config options.
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: undefined@undefined
npm error Found: @vitest/coverage-v8@5.0.2
npm error node_modules/@vitest/coverage-v8
npm error   dev @vitest/coverage-v8@"^5.0.0" from the root project
npm error
npm error Could not resolve dependency:
npm error peerOptional @vitest/coverage-v8@"4.1.11" from vitest@4.1.11
npm error node_modules/vitest
npm error   dev vitest@"^4.0.0" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry this command with --force or --legacy-peer-deps to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error
npm error For a full report see:
npm error /runner/cache/others/npm/_logs/2026-10-06T20_35_54_853Z-eresolve-report.txt
npm error A complete log of this run can be found in: /runner/cache/others/npm/_logs/2026-10-06T20_35_54_853Z-debug-0.log

@renovate
renovate Bot force-pushed the renovate/major-all-dependencies branch 7 times, most recently from 7440b0b to 3419855 Compare October 6, 2026 20:02
@renovate renovate Bot changed the title fix(deps): update all dependencies (major) fix(deps): update all dependencies (major) - autoclosed Oct 6, 2026
@renovate renovate Bot closed this Oct 6, 2026
@renovate
renovate Bot deleted the renovate/major-all-dependencies branch October 6, 2026 20:22
@renovate renovate Bot changed the title fix(deps): update all dependencies (major) - autoclosed fix(deps): update all dependencies (major) Oct 6, 2026
@renovate renovate Bot reopened this Oct 6, 2026
@renovate
renovate Bot force-pushed the renovate/major-all-dependencies branch from 3419855 to 8e91fae Compare October 6, 2026 20:36
Hold back the three majors whose ecosystems have not caught up, and fix the
code the rest of them break.

Held back:
- typescript 7 -> ~6.0.3 (backend): @nestjs/schematics@12 peers >=6.0.0 and
  typescript-eslint peers <6.1.0, so 6.0.x is the only version satisfying both.
  Frontend stays on 5.9 for the same typescript-eslint bound.
- ioredis 6 -> ^5.3.7: every typeorm release peers ioredis ^5.0.4.
- eslint 10 -> ^9.39.4 (frontend only): eslint-plugin-react has no ESLint 10
  release; its latest caps at ^9.7. The backend has no React plugin and runs 10.
- eslint-plugin-react-hooks pinned to ~7.0.1, the version already in use. 7.1
  adds rules that flag 11 pre-existing effects, which is not this PR's work.

Raised: vitest ^4 -> ^5, because @vitest/coverage-v8@5 pins peer vitest 5.0.3.

Code changes the upgrades require:
- typeorm 1.0 dropped the string-array `relations` form; use the object form.
- typescript 6 requires an explicit rootDir; set it to the ./src it inferred,
  so dist keeps its layout.
- eslint 10 (backend) flags two unused initial assignments and a rethrow with
  no `cause`.
- vitest 5 rejects hoisted vi.unmock beside a runtime doMock; use doUnmock.
- vite 8 builds with rolldown: manualChunks takes the function form, and the
  minifier is oxc ('esbuild' now needs esbuild installed separately).

Backend: 2016 unit tests, e2e boot against Postgres, lint, nest build all pass.
Frontend: 683 tests, lint, vite build all pass. Lockfiles written with npm 10;
npm ci verified under npm 10 and npm 11.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@renovate

renovate Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Edited/Blocked Notification

Renovate will not automatically rebase this PR, because it does not recognize the last commit author and assumes somebody else may have edited the PR.

You can manually request rebase by checking the rebase/retry box above.

⚠️ Warning: custom changes will be lost.

Bump `axios` in the backend and integration tests, update `markdown-it`, and refresh package overrides for several transitive dependencies. This pulls in newer upstream fixes and aligns the lockfiles with the updated backend and test dependency set.
Replace `tenantId: null` with `tenantId: IsNull()` in `FeatureFlagService.getByCodeAndTenant` so global (tenantless) feature flag queries are translated explicitly to SQL `IS NULL`. Update unit tests to assert the new repository call shape.
Refactors several backend and frontend string/parsing paths to avoid regex backtracking and improve determinism (new trim-char utility, safer masking/typography/template parsing, and helper extraction for CSV and header-title logic). It also standardizes runtime usage (`node:` imports, `Number.parseInt`, minor guard/lifecycle cleanups) and updates Sonar test inclusion for `*.spec.tsx`.

On the frontend, this adds broad API-layer unit coverage (`bulkNotifications`, `cstar`, `events`, `templates`, `user`) and improves accessibility/keyboard behavior in DataTable and Email Logo menus (button semantics, ARIA states, combobox active descendant handling), with related style and spec updates.
Refactors several UI fragments to use more appropriate HTML semantics: empty/loading messages now render with `output`, and the email header preview in settings is now a `figure` instead of a generic grouped `div`. Tests were updated to assert the new `figure` role, and styling resets default figure margin to preserve layout. It also tightens email header title typing to accept `null` values from persisted tenant settings (`customTitle` and `logoDisplayTitle`).

@mgtennant mgtennant left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tested locally and in dev, found no issues

@barrfalk
barrfalk merged commit 4780b25 into main Oct 9, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants