X posts from EJS maintainer Matthew Eernisse showing npm's confirmation that his account was reinstated after being removed in error

Escalating an npm Account Lockout When Support Goes Dark

On July 23, 2026, Matthew Eernisse, the author and maintainer of EJS, posted that his npm account had been flagged by an automated process and that npm support’s recommendation was to create another account. He also noted that he was not sure his support tickets were being read by a human at all.

A week later, on July 31, he was still locked out. He said so publicly in a reply to a GitHub thread that had asked developers what feature they wanted next. His answer was not a feature request. It was a request that GitHub respond to urgent security tickets, including his, which had been open for over a week.

EJS renders every page of a production site I maintain, which made this less an abstract community problem than an operational one. The person responsible for shipping security patches to a template engine in that stack could not log in to publish them.

On August 1, nine days after the initial flag, his account was reinstated. npm’s message said the removal had been made in error.

What follows is the escalation path used in the intervening days: the channels that worked, the two published npm addresses that turned out to be dead ends, and the parts worth doing differently. Most of it transfers to any large platform whose support queue has stopped moving.

Why an account lockout is a supply chain problem

EJS is not a niche package. At the time of writing its npm page reports 38,310,652 weekly downloads and 15,548 dependent projects, across 79 published versions with zero runtime dependencies of its own. It carries roughly eight thousand GitHub stars, it is Apache-2.0 licensed, and it is the view engine that a large share of Express tutorials and starter templates reach for first.

When the sole maintainer of a package like that cannot publish, three things happen at once:

  1. Pending security patches stop shipping. Not “get delayed.” Stop. There is no second person with publish rights to fall back on.
  2. The window for a hostile fork widens. A package with that much download volume and a visibly absent maintainer is an attractive target for a typosquat or a “helpful” fork that quietly adds a postinstall hook.
  3. Downstream trust erodes. Every day the latest tag sits stale on a package this widely installed is a day that dependency scanners, corporate security reviews, and cautious engineering leads start asking whether the project is abandoned.

This is not hypothetical. The npm registry spent 2025 and 2026 getting hammered. In September 2025, a phishing campaign against one maintainer compromised eighteen packages representing over two billion weekly downloads, an incident significant enough to draw a CISA alert. The Shai-Hulud worm followed, the first wormable malware in npm’s history, propagating by stealing credentials during installs. On March 31, 2026, an attacker used stolen credentials to publish two backdoored versions of axios, a package at roughly 100 million weekly downloads, injecting a dependency that dropped a remote access trojan on every machine that ran an install.

npm responded to all of that by tightening automated detection, which is the correct call. The cost of tightening automated detection is false positives. Matthew was one.

The catch-22 that makes this so hard to escalate

Here is the part that turns a two-day fix into a two-week ordeal.

npm’s own security policy states that tickets opened through npmjs.com/support are escalated to the security point of contact, and calls this the best and fastest way to reach npm on any security matter. The same document then carves out an exception: issues specific to your account, such as lost credentials, should go to support instead.

That carve-out is where a lockout falls, and it is the wrong side of the line. An account removal that blocks a security release is both an account issue and a security issue, but the policy routes it to general support, where automated triage does not distinguish “maintainer of a top-tier package” from “spam account appealing a ban.” The canned reply Matthew received was to create a new account.

This is a known and long-standing failure mode, and the GitHub community discussions are full of it. One thread is titled simply Can’t log into npmjs.com, where the poster explains they cannot contact support because contacting support requires being logged in. Another, Cant login to lodge a support ticket, is a user with a suspended account hitting the same wall. A third is a developer whose ticket sat unanswered for days, where the community’s practical suggestion was to post publicly on X and reference the ticket number, on the reasoning that visibility speeds up responses.

That last piece of advice is worth sitting with. The recommended workaround for npm’s support process, offered by other developers rather than by npm, is to go around it in public.

So the actual problem is not that npm refuses to fix this. The actual problem is that no human at npm has seen the ticket yet. Every step below was aimed at that single objective: get one person to read the words “EJS” and “locked out” in the same sentence.

Step one: parallel email escalation

The support queue is not the target. The inboxes that route to humans are.

Framing matters more than volume here. A first attempt that ran three sentences and read like a complaint went nowhere. The revised version did four things differently:

  • Led with the risk, not the inconvenience. This is a supply chain issue, not a customer service issue. Those get routed to different teams with different response SLAs.
  • Put the number in the first paragraph. Download volume is the only piece of context that makes a stranger in a security inbox sit up.
  • Named the non-solution. Quoting “create another account” back at them does a lot of work. It signals that the ticket was handled by automation, which is exactly the thing a human needs to override.
  • Made the ask specific. Not “please help.” Escalate to npm trust and safety.

The body, lightly cleaned up:

Subject: Urgent: npm maintainer lockout blocking security fix, EJS (38M weekly downloads)

I’m reporting a maintainer account lockout that poses a supply chain risk to a widely used package.

Matthew Eernisse (@mde), maintainer of EJS, has been locked out of his npm account for over a week due to an apparent automated flag. npm support has reportedly told him to “create another account,” which isn’t a workable fix for a package of this scale, and it may be blocking him from shipping updates, including security patches.

Package: https://www.npmjs.com/package/ejs Repository: https://github.com/mde/ejs

His posts describing the issue: [links]

Given EJS’s download volume and the number of downstream projects that depend on it, I’d appreciate this being escalated to the npm trust and safety or security team for prompt review.

That went to six addresses, each as a separate message rather than one mass send:

AddressRationaleOutcome
security@npmjs.comnpm’s published security contactBounced. Delivery blocked by policy
support@npmjs.comGeneral queue, and a paper trailAuto-replied. Redirected to the login-gated support site
security@github.comGitHub owns npm, and the security framing fitsDelivered
abuse@github.comAccount flagging is the flip side of abuse detectionDelivered
opensource@github.comGitHub’s open source program office, which cares about ecosystem healthDelivered
press@github.comReputational framing, and press inboxes get read dailyDelivered

Separate messages, not a mass BCC. A single email with six company addresses in the header reads as scattershot and invites every recipient to assume someone else owns it. Six individually addressed emails sharing one subject line let each team triage independently while staying searchable if someone later wants to connect the threads.

One thing worth skipping: the high priority flag. It sets an X-Priority header that most modern clients ignore, ticketing systems strip, and security teams have long since learned to discount. The subject line does that job.

Neither npm address accepts email

That outcome column is the most reusable finding here, so it gets its own heading.

security@npmjs.com did not go to a queue. It did not go to a spam folder. It was rejected at the SMTP layer with a 5.7.1 status and a diagnostic explaining that the recipient domain has a policy prohibiting the message. That address appears in npm’s own historical security announcements as the place to report malicious code. It does not accept mail.

support@npmjs.com did deliver, and produced an automated reply stating that new support requests must be created through npmjs.com/support, followed by an instruction not to reply because nothing sent to that address will be seen by the team.

Read those two results next to the catch-22 above and the shape of the problem becomes clear. npm publishes a support URL as the correct channel. That URL assumes a working account. The two email addresses that would let you route around a broken account are, respectively, blocked at the mail server and configured as a one-way redirect back to the URL you cannot use.

That is not a queue that is slow. That is a closed loop. And it means that for the entire nine days Matthew was locked out, there was no functioning path from a locked-out maintainer to npm by email at all. Every message that had any chance of landing went to a github.com address instead.

The operational lesson: skip the npm addresses entirely and write to GitHub. npm has been GitHub property since 2020 and Microsoft property by extension. The staffed inboxes are on the parent company’s domain.

Step two: replying in public

The second channel was simple enough that it barely counts as a step. On July 31, a public reply to Matthew’s post restated the problem in the same terms as the emails: a package at this download volume, a sole maintainer who cannot publish, a security patch that cannot ship.

This is worth doing even when the reply reaches no one in particular. A maintainer posting about their own lockout is one person complaining. The same complaint with a downstream user attached to it is an ecosystem problem, and it gives anyone inside the company who stumbles across the thread a reason to forward it without having to adjudicate the original dispute.

Step three: LinkedIn, prepared and never sent

The plan and the execution diverged here, and the gap is worth documenting.

Connection requests went out to GitHub engineers, weighted toward senior individual contributors rather than managers, on the theory that a senior engineer with internal Slack access can ping the right team in thirty seconds whereas a manager routes you back to support. A couple were accepted. The message was drafted, then held, because firing off a request within minutes of someone accepting a connection is the exact pattern that reads as an automated outreach campaign. A day or two of daylight between the two seemed worth the delay.

The account was restored before that window closed. The messages were never sent.

The draft is included anyway, because the reasoning behind it holds even untested:

Apologies for the cold message. I’m trying to help resolve an npm account lockout affecting a widely used package, and I’m hoping you might know who to point me to.

Matthew Eernisse (@mde), maintainer of EJS, has been locked out of his account for over a week due to an apparent automated flag. Support told him to “create another account,” which isn’t workable given the package’s scale. It’s currently blocking a pending security patch.

If you know anyone on the npm trust and safety or security team, I’d really appreciate an introduction. Happy to share more context.

Four things worth keeping in it:

Connect first, message second. A cold message attached to a connection request gets filed with the recruiter spam. A message to someone who already accepted you is a message from a contact.

Give them an easy out. “If you know anyone” beats a request that assumes obligation. Someone who cannot help will often forward it to someone who can, precisely because you did not make it awkward to decline.

No wall of links. The URLs are stripped out of this version. In an email they add credibility. In a DM they look like phishing.

Do not blast. Spacing messages out over a couple of days matters. Ten identical DMs sent in one hour to people at the same company is a pattern that gets noticed, and not in the way you want.

That last point is the reason this step contributed nothing, and the tradeoff still favors the delay. Had the lockout run another week, warm connections already in place would have beaten starting from zero, and ten DMs blasted out on day one would have burned those contacts permanently.

The resolution

On August 1, Matthew posted a screenshot of npm’s reply confirming his account had been reinstated after they identified that the removal had been made in error, along with a note that they were taking the opportunity to improve their processes. His follow-up post was pointed and fair: GitHub has the resources of Microsoft behind it, and a human in the loop plus timely responses on security issues should not be a heavy lift.

He also posted a public thanks for the outside escalation, which is the only direct evidence any of it registered.

Which specific message got through is unknowable, and worth stating plainly rather than glossing. The field is at least narrow. Neither npm address received anything, so none of it reached npm directly. The LinkedIn messages were never sent. That leaves two candidates: four emails to GitHub addresses, and a public reply on X. One of those did it, or neither did and the ticket simply reached the front of a queue on its own schedule.

That is usually how these go. Which pebble started the slide is not recoverable, and it is not the useful part. The useful part is that it took two channels and roughly two hours, that the framing was a security risk rather than a complaint, and that the channel npm itself documents played no role at all.

What to do differently

Start with the security framing, not the complaint framing. The first attempt here was a complaint, and it cost a couple of days.

Ask the maintainer first. No one checked with Matthew before escalating on his behalf. It worked out, but some maintainers would rather handle this quietly, and a stranger amplifying their account problems is not universally welcome. A reply asking whether escalation is wanted costs nothing.

Keep records. Log the send time for each message and save any auto-reply ticket numbers. On day four, referencing a ticket number is the difference between a fresh email and an escalation.

Follow up before widening. Three to four business days is a reasonable interval for a short follow-up on the same thread. Do not open a new front until the existing ones have had time to land.

The part that should actually change

The specific bug that flagged Matthew’s account is not the interesting problem. Automated detection will always have false positives, and given what the registry has been through in the last eighteen months, aggressive detection is the right posture.

The interesting problem is that the fast lane exists, and account restoration is not on it.

The axios compromise is the useful comparison. Reading the maintainer’s post-mortem, a collaborator noticed the malicious publishes and contacted npm directly at 01:38 UTC. The bad versions were gone by 03:15 and the malicious dependency by 03:29. Under two hours, overnight, on a weekend, from first contact to takedown.

So npm can move quickly. It moves quickly when the request is to remove something. It does not appear to move at all when the request is to restore access, even though both are the same category of problem, and even though the second one is what blocks a patch from shipping. The registry already knows exactly how many weekly downloads every package receives, because that number sits on the package page next to the install command. Using it to triage account appeals is not a hard engineering problem. Its absence is what turned a mistaken flag into a nine-day outage on a piece of critical infrastructure.

There is a smaller and even cheaper fix available, though. Two email addresses that npm has published as security and support contacts currently reject or deflect everything sent to them. Making security@npmjs.com accept mail again, or publishing a correct alternative, would cost an afternoon. Until that happens, npm’s documented security contact process has a gap precisely where a compromised or mistakenly removed maintainer would need it most.

npm’s reply said the incident was being used as an opportunity to improve their processes. Fixing the contact addresses would be a visible place to start.

If you are the one locked out

The uncomfortable truth is that being locked out makes you the worst-positioned person to fix it. You cannot open a ticket, your public posts reach your own follower count, and every hour you spend on it is an hour of unpaid work on someone else’s bug.

So the practical move is to say it publicly, be specific about the impact, and lead with the download count rather than the frustration. Then let someone downstream carry it.

None of what worked here required access, standing, or a contact inside GitHub. It was four working email addresses, one public reply, a subject line that named a security risk instead of a complaint, and roughly two hours spread over three days. That last ingredient is the only scarce one, and it is not scarce among the people who read posts like this. Anyone who depends on a package and notices its maintainer stuck in a queue that has stopped moving is already qualified to be the one who gets them out.

RSS Feed Newsletter
Contact us

Latest Blog Posts