Skip to content

fix: handle Infinity/NaN maxAge in res.cookie() without crashing#7287

Open
Mohammad-Faiz-Cloud-Engineer wants to merge 2 commits into
expressjs:masterfrom
Mohammad-Faiz-Cloud-Engineer:master
Open

fix: handle Infinity/NaN maxAge in res.cookie() without crashing#7287
Mohammad-Faiz-Cloud-Engineer wants to merge 2 commits into
expressjs:masterfrom
Mohammad-Faiz-Cloud-Engineer:master

Conversation

@Mohammad-Faiz-Cloud-Engineer

Copy link
Copy Markdown

Noticed that passing { maxAge: Infinity } to res.cookie() throws an unhandled TypeError: option maxAge is invalid from the cookie package. The old !isNaN() check passes for Infinity (since it's not NaN), so the code tries to compute Math.floor(Infinity / 1000) and passes Infinity straight to cookie.serialize(), which rightfully rejects it.

Same issue with NaN and -Infinity.

The fix adds an early guard: if maxAge is a number and non-finite (Infinity, -Infinity, NaN), just delete it from opts so the cookie falls back to a session cookie (no Max-Age). Non-numeric garbage like 'foobar' still throws as before that path is unchanged.

Checked all edge cases manually and ran the suite a few times 1249 passing, 0 failing.

@Ap-0007 Ap-0007 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the contribution! Handling non-finite values like Infinity, -Infinity, and NaN in res.cookie is a great safety enhancement.

However, checking typeof opts.maxAge === 'number' introduces a regression for string representations of non-finite numbers (like 'Infinity').

The Bug

Since maxAge = opts.maxAge - 0 is coerced to a number before the check:

  1. If a user passes res.cookie('name', 'val', { maxAge: 'Infinity' }), typeof opts.maxAge is 'string'.
  2. This bypasses the new guard block (typeof opts.maxAge === 'number' && !isFinite(maxAge)).
  3. The code then falls into the else if (!isNaN(maxAge)) block because !isNaN(Infinity) is true.
  4. It sets opts.expires = new Date(Date.now() + Infinity) (which is Invalid Date) and opts.maxAge = Infinity.
  5. This is then passed to the cookie package, causing it to throw a TypeError: option maxAge is invalid or produce an invalid date—exactly what the PR seeks to prevent.

Suggested Fix

We can check maxAge (which has already been coerced to a number) instead of typeof opts.maxAge:

    if (maxAge === Infinity || maxAge === -Infinity || (typeof opts.maxAge === 'number' && isNaN(maxAge))) {
      // strip non-finite numeric maxAge (Infinity, -Infinity, NaN)
      delete opts.maxAge
    } else if (!isNaN(maxAge)) {
      opts.expires = new Date(Date.now() + maxAge)
      opts.maxAge = Math.floor(maxAge / 1000)
    }

This correctly handles both numeric and string representations of Infinity/-Infinity, correctly ignores the number NaN, and still allows invalid string garbage like 'foobar' to flow through to the cookie package and throw as expected.

Infinity, -Infinity, and NaN maxAge values now gracefully produce a session cookie instead of throwing an unhandled TypeError from cookie.serialize(). Non-numeric invalid maxAge values (e.g. 'foobar') still throw as before.
@Mohammad-Faiz-Cloud-Engineer

Copy link
Copy Markdown
Author

Done Brother, Now Check

@notadev99 notadev99 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice catch on the root cause — !isNaN() passing for Infinity is a real gap, and the fix is appropriately minimal. Two things before this is ready:

Tests. This changes observable behavior (Infinity/-Infinity/NaN now produce a session cookie instead of throwing), but nothing covers it. Could you add cases to the res.cookie tests asserting that maxAge: Infinity, -Infinity, and NaN set a session cookie (no Max-Age) without throwing — and one pinning that maxAge: 'foobar' still throws, so the intentional "leave non-numeric strings alone" behavior is locked in?

String 'Infinity' vs 'foobar'. Since maxAge is already coerced (opts.maxAge - 0), the string 'Infinity' coerces to Infinity and gets silently dropped to a session cookie, while 'foobar' coerces to NaN, fails the typeof guard, and throws. Two non-numeric strings, two outcomes. If that's intended, a one-line comment would save the next maintainer some confusion; if not, worth deciding whether 'Infinity' should throw too.

Logic looks correct otherwise.

Add tests for Infinity, -Infinity, and NaN maxAge values producing
session cookies (no Max-Age) as requested in PR review feedback.

Expand the inline comment to explain why the typeof guard is needed:
it distinguishes numeric NaN (stripped to session cookie) from
non-numeric strings like 'foobar' (which pass through and throw).
Also documents that string 'Infinity' coerces to numeric Infinity
and is intentionally handled the same way.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants