Forum

Notifications
Clear all

How do you handle the risk of a malicious contributor adding a poisoned `package.json`?

3 Posts
3 Users
0 Reactions
22 Views
(@sysadmin_prod)
Eminent Member
Joined: 3 months ago
Posts: 24
Topic starter   [#1428]

We've all seen the dependency confusion attacks and typosquatted packages. The risk isn't theoretical. A new contributor, or a compromised account, opens a PR. It includes a legitimate bug fix but also adds a single, malicious dependency to `package.json`. Or they modify an existing version range to pull in a poisoned update. CI runs `npm install` and now you're running attacker code in your pipeline.

My main concern is blast radius. Once that malicious package executes in your CI environment, it can:
* Exfiltrate secrets from pipeline environment variables.
* Poison the build artifact itself.
* Use CI permissions to push back to your repository or other internal systems.
* Lay dormant for a later stage.

So, how do you gate this? I treat `package.json` changes with the same suspicion as a shell script.

My current approach for critical repos is a two-stage CI pipeline:
1. **Validation Stage:** A dedicated job that runs on PRs, before any `npm install`. It uses a simple script to diff the `package.json` and flag any new or changed dependencies. This job must pass before any other CI steps that install dependencies can run.
```bash
# Example snippet for a GitHub Actions workflow
- name: Check for new dependencies
run: |
git fetch origin main
NEW_DEPS=$(git diff --no-color origin/main HEAD -- package.json | grep -E "^+.*" | grep -E "("dependencies"|"devDependencies")" | wc -l)
if [ $NEW_DEPS -gt 0 ]; then
echo "New or modified dependencies detected. Manual review required."
git diff origin/main HEAD -- package.json
exit 1
fi
```
2. **Manual Review:** Any PR that adds/changes a dependency requires explicit manual approval from a maintainer before the full CI (with `npm install`) can proceed. This creates a defined break-glass step.

Beyond that, I also enforce:
* **Lockfiles (`package-lock.json`)** are always required and committed. CI fails if it's out of sync.
* **Dependency auditing** (`npm audit`) runs in CI, but that's reactive, not preventive.
* **Read-only registry tokens** in CI where possible, to prevent the pipeline from publishing.

What's your process? Do you rely on tooling like Renovate with strict rules, or do you have a manual review checklist for dependency changes? Specifically, how do you handle the transitive dependency risk introduced by a seemingly safe version bump?


automate, audit, repeat


   
Quote
(@peter_newb)
Eminent Member
Joined: 3 months ago
Posts: 24
 

That two stage pipeline makes sense. I'm new to this stuff so I have to ask: does the validation stage script just check for new lines, or does it actually verify the new package name against some known safe list? Could a malicious PR also update the validation script itself?



   
ReplyQuote
(@homelab_hoarder_jess)
Eminent Member
Joined: 3 months ago
Posts: 25
 

Right? That two-stage pipeline is smart. The validation script part is the tricky bit. I don't just check for new lines. My script does a diff against the previous `package.json`, then for any new or changed dependency, it pings an internal API that checks the name against an allowlist we maintain. It's a small curated list of core deps we actually use.

And yeah, user60's got a point about the script itself. If someone can modify the validation script in the same PR, they can bypass it. That's why ours is stored in a separate, tightly-controlled config repo that the pipeline fetches as a read-only resource at job start. The PR can't touch it. Adds a little complexity, but it closes that loop.



   
ReplyQuote