Block Malicious npm Packages in CI with Socket.dev
Wire Socket's supply-chain scanner into GitHub Actions to stop typosquats and install scripts before merge.
What you'll build
A GitHub Actions job that runs Socket's supply-chain scanner on every push and pull request, posts a security report as a PR comment, and fails the check when a new dependency trips a blocking alert — known malware, typosquats, surprise install scripts, or packages published five minutes ago.
Prerequisites
- A GitHub repository with a
package.json(Socket also scans PyPI, Go, Maven, and other ecosystems from the same workflow — nothing below is npm-specific except the test package). - A Socket account — the free tier is enough. Sign up at socket.dev and create an organization if you don't have one.
- Admin access to the repo (you'll add a secret and a workflow file).
- Versions verified for this tutorial (July 2026):
socketsecurity2.5.6 on PyPI (requires Python ≥ 3.11 — the workflow installs 3.12 itself),actions/checkout@v4,actions/setup-python@v5,ubuntu-latestrunners.
Socket also ships a GitHub App that does PR comments with zero config. The CI route in this tutorial is what you want when you need a required status check that actually blocks the merge button, or when app installs are locked down by your org.
1. Create a Socket API token
In the Socket dashboard, go to Settings → API Tokens and click Create API Token. Name it something like github-actions-ci, and grant the scopes Socket documents for CI use:
full-scans:list,full-scans:create— the actual scanningrepo:list,repo:create,repo:update— lets the CLI register the repo on first runsecurity-policy:read— so the CLI knows which alerts your org blockstriage:alerts-list,triage:alerts-updateandpackages:list
Click Confirm, then Show key and copy the token. You won't see it again after leaving the page.
2. Store the token as a GitHub secret
From your repo checkout, with the GitHub CLI:
gh secret set SOCKET_SECURITY_API_TOKEN
# paste the token at the prompt
Or in the UI: Settings → Secrets and variables → Actions → New repository secret, name SOCKET_SECURITY_API_TOKEN. If you're rolling this out org-wide, set it once as an organization secret instead.
The CLI accepts the token from any of SOCKET_SECURITY_API_TOKEN, SOCKET_SECURITY_API_KEY, SOCKET_API_TOKEN, or SOCKET_API_KEY — pick one name and stay consistent.
3. Add the workflow
Create .github/workflows/socket.yml:
name: socket-security
run-name: Socket Security Scan
on:
push:
branches: ['**']
pull_request:
types: [opened, synchronize, reopened]
issue_comment:
types: [created]
concurrency:
group: socket-scan-${{ github.ref }}-${{ github.sha }}
cancel-in-progress: true
jobs:
socket-security:
permissions:
issues: write
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }}
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Socket CLI
run: pip install socketsecurity --upgrade
- name: Run Socket security scan
env:
SOCKET_SECURITY_API_TOKEN: ${{ secrets.SOCKET_SECURITY_API_TOKEN }}
GH_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=0
if [ "${{ github.event_name }}" == "pull_request" ]; then
PR_NUMBER=${{ github.event.pull_request.number }}
elif [ "${{ github.event_name }}" == "issue_comment" ]; then
PR_NUMBER=${{ github.event.issue.number }}
fi
socketcli --target-path "$GITHUB_WORKSPACE" --scm github --pr-number "$PR_NUMBER"
This is Socket's documented pattern (from the socket-python-cli repo), lightly renamed. The pieces that matter: pull-requests: write and issues: write let the CLI post its report comment via GH_API_TOKEN; fetch-depth: 2 on PRs gives it the parent commit so it can diff newly added dependencies instead of re-litigating your whole lockfile; the issue_comment trigger lets Socket react to triage commands in PR comments. On PRs, socketcli exits 1 when a blocked alert fires and 0 otherwise, which is what turns the check red.
Commit and push this to your default branch. The first push run does a full baseline scan and registers the repo in your Socket dashboard.
4. Tune the security policy
The exit code follows your org's security policy, and the defaults are softer than you probably want. Out of the box, Known Malware is Block, Possible Typosquat Attack is only Warn, and Install Scripts is Ignore.
In the dashboard, open your org's Security Policy page and set each alert to Block, Warn, Monitor, or Ignore. For the threats this pipeline is meant to stop:
- Possible Typosquat Attack (
didYouMean) → Block - Install Scripts (
installScripts) → Warn (Block if you're strict — expect noise from native modules) - Recently Published (
recentlyPublished) → Warn, to catch dependencies pushed to the registry hours ago
Only Block-level alerts fail the check; Warn shows up in the PR comment for a human to judge.
5. Open a PR that adds a risky dependency
Test the gate with a package that legitimately runs a postinstall script:
git checkout -b test-socket-gate
npm install puppeteer
git add package.json package-lock.json
git commit -m "test: add puppeteer to trip Socket install-script alert"
git push -u origin test-socket-gate
gh pr create --fill
Puppeteer's postinstall downloads a Chrome binary, so it reliably triggers the installScripts alert on the diff.
Verify it works
Within a couple of minutes the PR gets a comment from github-actions titled with a Socket security report: a table of the newly added packages, the alerts each one raised (you should see Install scripts on puppeteer), and a link to the full scan in your dashboard.
Check the status from the CLI:
gh pr checks
Expected output with Install Scripts at Warn:
socket-security pass 1m12s https://github.com/you/repo/actions/runs/...
Flip Install Scripts to Block in the policy, push an empty commit to re-trigger, and the same check reports fail — the job log ends with Process completed with exit code 1. That's your merge gate working. Finish by making it mandatory: Settings → Branches → Branch protection rules, add socket-security as a required status check.
Troubleshooting
Unauthorized/ HTTP 401 from the API. The token env var is empty or wrong. Check the secret name matches the workflow exactly, and remember that PRs from forks don't receive repository secrets — fork PRs will fail auth unless you use the GitHub App for external contributors.Resource not accessible by integrationwhen posting the PR comment. The defaultGITHUB_TOKENlacks write access. Make sure thepermissions:block from step 3 is present in the job, and that your repo/org settings don't force the token to read-only (Settings → Actions → General → Workflow permissions).ERROR: Could not find a version that satisfies the requirement socketsecurityduringpip install. You're on Python < 3.11 (currentsocketsecurityreleases require ≥ 3.11, so older interpreters see no installable version). Keep thesetup-pythonstep withpython-version: '3.12'rather than relying on the runner's system Python.- A package with install scripts sailed through unflagged. That's the default policy, not a broken scan — Install Scripts ships as Ignore. Set it to Warn or Block in the Security Policy page (step 4), then re-run the job.
Next steps
- Shift the same protection left onto laptops: the npm Socket CLI (
npm install -g socket) wraps your package manager, sosocket npm install <pkg>vets packages before they ever touchnode_modules, andsocket scan create --reportgives you the same policy gate in any CI system. - Export findings to GitHub code scanning with
socketcli --sarif-file results.sarifand upload viagithub/codeql-action/upload-sarif. - Enable reachability analysis (
--reach) to prioritize alerts in code paths you actually execute. - Read Socket's alert catalog to decide Block/Warn/Monitor for the other ~100 alert types — obfuscated code, telemetry, unstable ownership, and friends.
Sources & further reading
- Socket for GitHub Actions — docs.socket.dev
- Socket Security CLI for CI/CD (socket-python-cli) — github.com
- Create Socket API Key for CI/CD — docs.socket.dev
- Security Policy (Default Enabled Alerts) — docs.socket.dev
- Socket Alert Types — socket.dev
- socketsecurity on PyPI — pypi.org
Ji-ho covers the increasingly tangled overlap between cloud architecture and security, drawing on a background as a penetration tester to keep his reporting grounded in real-world attack paths. He never lets a vendor claim go unquestioned and insists that every buzzword come with a proof of concept.
Discussion 0
No comments yet
Be the first to weigh in.