Open Source
dotenv

Published: October 5, 2026 · by Srinu Desetti · motdotla/dotenv#1049 (opens in a new tab) · merged September 16, 2026

Fixing the typeof null Trap in dotenv's populate()

dotenv is how Node.js apps load .env files — roughly 50 million downloads a week. My contribution — merged by the creator of dotenv — fixes a bug where its public populate() function crashed with a raw TypeError when passed null, instead of throwing the clear OBJECT_REQUIRED error dotenv promises.

dotenv contribution

The Problem

populate() copies parsed key/value pairs into process.env. It has a safety check that should reject bad input with dotenv's own friendly error:

lib/main.js — the guard (before)
if (typeof parsed !== 'object') {
  throw new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate')
}

JavaScript has an old trap:

typeof null === 'object'  // true — a quirk as old as the language

So calling populate(process.env, null) walked right past the check, hit Object.keys(null) below, and crashed:

require('dotenv').populate(process.env, null)
// ❌ TypeError: Cannot convert undefined or null to object
//    — a raw crash from deep inside, not dotenv's error
 
// expected:
// ✅ Error: OBJECT_REQUIRED: Please check the processEnv argument...

And there was a second half: the first argument, processEnv, was never checked at all. So populate(null, parsed) also crashed with a raw TypeError — ironically, the error message text literally says "Please check the processEnv argument", but the code never checked it.

Why null sneaks in: config loaders, JSON parsers, and lookup helpers commonly return null when something is missing. Pipe that into populate() and — before this fix — you got a cryptic crash from inside dotenv's internals instead of an error that names the actual problem.

The Fix: One Line

lib/main.js — the guard (after)
// before
if (typeof parsed !== 'object') {

// after
if (processEnv === null || typeof processEnv !== 'object' || parsed === null || typeof parsed !== 'object') {

Four conditions, covering both arguments:

  1. processEnv === null — the explicit null check typeof can't do.
  2. typeof processEnv !== 'object' — rejects strings, numbers, undefined.
  3. parsed === null — same null check for the second argument.
  4. typeof parsed !== 'object' — the original check, kept as-is.

I deliberately kept the existing error message and error code unchanged — the message was already correct; now the code finally does what the message says. No existing behavior or test broke.

The Tests

Two regression tests in tests/test-populate.js lock the fix in:

tests/test-populate.js — regression tests
// 1. null as the parsed argument → dotenv's error, not a TypeError
t.throws(
  () => dotenv.populate(process.env, null),
  { message: /OBJECT_REQUIRED/ }
)

// 2. null as the processEnv argument → same friendly error
t.throws(
  () => dotenv.populate(null, { test: 'foo' }),
  { message: /OBJECT_REQUIRED/ }
)

Without these, one refactor could silently reintroduce the crash — the tests make sure nobody can quietly re-break it.

How I Found and Verified It

No issue was filed — I found the bug myself by auditing the code:

  1. Read lib/main.js end to end and spotted the typeof null trap in the guard.
  2. Proved it with Node — ran both calls; both crashed with raw TypeErrors.
  3. Checked GitHub first — no open or closed issue reported it, and no competing PR existed. The lane was completely free.
  4. Ran the full test suite before and after — the only failures were three pre-existing Windows path quirks that fail identically on clean master. All 4 CI checks passed on the PR.

A community reviewer verified the bug independently and approved; motdotla, the creator of dotenv, merged it four days after I opened it.

Impact

  • A real crash in a public API of a package with ~50M weekly downloads and ~20.5k stars is gone.
  • Developers get the error dotenv promises: when null sneaks in, the message says exactly what's wrong instead of a cryptic TypeError they have to debug.
  • Minimal, surgical diff: one changed line plus 20 lines of tests — no parsers touched, no messages changed, no behavior altered for valid input.

What I Contributed

  • Audited lib/main.js and identified the typeof null trap plus the completely unchecked processEnv argument.
  • Implemented the four-condition guard covering both arguments while preserving the existing error message and code.
  • Added two regression tests proving both null paths throw OBJECT_REQUIRED instead of crashing.
  • Verified the fix against the full test suite and shepherded the PR through review to merge.

View the pull request → motdotla/dotenv#1049 (opens in a new tab)


← Previous: Multer filename decoding · All contributions →


Frequently Asked Questions

Why does typeof null return 'object' in JavaScript?

It's a bug from the very first JavaScript implementation in 1995: values carried a type tag, and the tag for objects was 0 — the same representation null used. Fixing it would break the web, so it was standardized as-is. Any validation using only typeof x === 'object' silently accepts null.

What happened before this fix when you passed null to populate()?

The guard passed (because typeof null === 'object'), execution reached Object.keys(null), and Node threw a raw TypeError: Cannot convert undefined or null to object — a confusing crash from dotenv's internals instead of the documented OBJECT_REQUIRED error.

Why check the processEnv argument too?

It was never validated at all, so populate(null, parsed) crashed the same way. The irony: the error message text already said "Please check the processEnv argument" — the code just never did. The fix makes the code match its own message.

Did the fix change dotenv's error message or behavior for valid input?

No. The error message and error code are unchanged, no other function was touched, and every existing test still passes. The diff is one changed line plus two regression tests — valid input behaves exactly as before.