Skip to content
Back to Maintenance & LTS

Security Backports

Backport security fixes from the current release to LTS versions so staying on LTS doesn't mean being vulnerable.

Why

An LTS user who gets no security fixes is an LTS user who gets breached — the LTS promise is "you can stay on this version and remain secure," and backports are how you keep that promise. A backport takes a fix from v20 and adapts it to v18's codebase (which may have different file structure, APIs, or dependencies). The discipline is to backport only security + critical fixes (not features, not cosmetic bugs), to test each backport on the LTS branch, and to release it as a patch (v18.20.1) with a changelog that names the CVE. Without backports, LTS is a lie — a version labeled "supported" that's actually vulnerable to every CVE fixed in current.

When

For every security fix in current, backport to all active LTS versions. Automate the backport PR creation.

How

1. When a security fix lands in current (v20), identify all active LTS branches (v18, v16)
2. Use a backport bot (e.g., backport-action) or cherry-pick: git cherry-pick <fix-commit> onto the LTS branch
3. Resolve conflicts (the fix may touch code that differs in LTS) — adapt, don't copy blindly
4. Run the LTS branch's test suite; add a regression test for the CVE if not present
5. Release as a patch: v18.20.1, changelog "security: backport CVE-2024-1234 fix from v20"
6. Notify LTS users via the LTS release channel (separate from current's changelog)
7. Track a backport dashboard: CVE, fixed-in current, backported-to [v18, v16], status per branch

Example

# Backport a security fix from v20 to v18
# 1. The fix commit on main (v20)
git log --oneline  # abc1234 fix: CVE-2024-1234 prototype pollution in merge()

# 2. Cherry-pick onto the LTS branch
git checkout lts/v18
git cherry-pick abc1234
# CONFLICT in src/merge.ts (file differs in v18) — resolve:
#   adapt the fix to v18's merge() signature, don't copy v20's version

# 3. Test on the LTS branch
npm ci && npm test  # must pass; add a regression test for the CVE
# test/cve-2024-1234.test.ts:
it('is not vulnerable to CVE-2024-1234', () => {
  expect(() => merge({}, JSON.parse('{"__proto__":{"polluted":1}}'))).not.toThrow();
  expect({}.polluted).toBeUndefined();
});

# 4. Release as a patch
git tag v18.20.1
# changelog: "v18.20.1 (LTS): security — backport CVE-2024-1234 fix from v20.20.0"

# Backport dashboard
| CVE            | Fixed in | Backported to      | Status |
|----------------|----------|--------------------|--------|
| CVE-2024-1234  | v20.20.0 | v18.20.1, v16.20.5 | done   |
| CVE-2024-5678  | v20.20.1 | v18 (in progress)  | wip    |

Common Problems

  • ⚠Blindly cherry-picking without adapting to the LTS codebase — the fix references a function that doesn't exist in v18, and the build breaks.
  • ⚠Backporting features along with security fixes — now the LTS API changed, breaking users who chose LTS for stability.
  • ⚠No regression test for the backported CVE — the fix is in, but a future refactor silently reverts it because nothing catches it.

Best Practices

  • ✓Backport only security + critical fixes to LTS, never features — LTS users chose stability, and an API change breaks that promise.
  • ✓Add a regression test for every backported CVE on the LTS branch — the test proves the fix and prevents silent reversion.
  • ✓Automate backport PR creation (backport-action) so a fix on main opens PRs to all active LTS branches within minutes.