
Security News
Ruby's Bundler 4.0.18 Extends Cooldown to bundle lock and bundle cache
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.
Zero-dep local CLI and MCP server that scans npm packages for supply-chain risk. OSV vuln pre-check, sandboxed quarantine, tarball-integrity verification, calibrated static heuristics, GitHub provenance cross-check.
Analyze packages before you install them.
Local supply-chain security for AI agents & npm packages. Zero-dependency Node, runs entirely on your machine, never executes untrusted code.
npm install -g pkgxray
pkgxray guard npm:some-package@1.2.3
Point it at a package, get a SAFE / REVIEW / BLOCK verdict with cited
evidence — before a single line of that package runs.
AI coding assistants increasingly install packages automatically, often without a human ever reading the code. Traditional antivirus inspects what executes; pkgxray inspects what gets installed — evidence-based static analysis on a package's metadata, source, provenance, and published artifact before it reaches your machine.
It's intentionally conservative: it only reports evidence it can cite, and stages everything in a sandboxed quarantine that never runs install scripts or package code. Triage takes ~1 s/package with no execution risk.
Supply-chain intelligence — known CVEs (OSV, blocks before download), sigstore/SLSA provenance, npm↔GitHub artifact divergence, registry metadata.
Static code analysis — credential/secret access (.ssh, .aws, .npmrc,
.env, keychains, wallets), persistence writes (shell rc, cron, launch agents),
obfuscation + execution (a packed blob decoded into eval/new Function/vm,
split-string paths), Trojan Source (bidi/zero-width Unicode), and tiered
prompt-injection detection in docs, code comments, and package.json
metadata (description/keywords/author) — reworded steering, chat/role
scaffolding (<|im_start|>, <<SYS>>, [INST]), and identity reassignment, not
just verbatim phrases.
Concealment & encoding — injection is unsolvable by matching the attacker's wording (paraphrase defeats it), but it has to be delivered, and the delivery tells are high-signal and low-FP. pkgxray detects instructions smuggled in invisible characters (the Unicode tag block — "ASCII smuggling") and base64-encoded prompts hidden in docs/comments that a human can't read but an agent decodes. It detects the envelope, not the message — so it generalizes past rewording. (Emoji subdivision flags and benign/binary base64 are excluded.) See solving prompt injection.
Behavioral correlation — cross-file exfiltration, stage-2 loaders, download→
execute (curl | sh), process.env harvesting near a network sink, on-chain
command channels (EtherHiding — a chain-read like eth_getTransactionByHash /
TronGrid / Aptos co-located with a code executor, so the payload lives on a
blockchain the repo never has to change), and hidden self-node -e execution
(a detached, windowsHide, stdio:'ignore' inline-eval subprocess — stage-2
that outlives and escapes the process being scanned).
Every signal resolves to one verdict:
| Verdict | Meaning |
|---|---|
🟢 safe | no high- or medium-risk indicators |
🟡 review | incomplete evidence or a privileged capability needing a human |
🔴 block | high-severity (prompt injection, credential access, persistence, obfuscation + execution, likely exfiltration) |
INPUT ADAPTERS npm: · lockfile · folder · evidence JSON
│
ACQUISITION ENGINE registry meta · GitHub meta · provenance · OSV
│
QUARANTINE ENGINE stage tarball in a private sandbox (no exec)
│
STATIC ANALYSIS credentials · persistence · prompt-injection
+ CORRELATION obfuscation · unicode · dynamic load · cross-file
│
POLICY ENGINE → SAFE · REVIEW · BLOCK
│
CLI · JSON · MCP server · browser extension
Design principles: never execute untrusted code · report only citable evidence · explainability over black-box scoring · minimize false positives · operate offline whenever possible · zero runtime dependencies.
Malicious npm packages · compromised maintainer accounts · typosquatting & dependency confusion · credential theft · malicious lifecycle scripts · supply-chain tampering (npm artifact ≠ tagged source) · provenance spoofing · AI prompt injection in package docs.
Known blind spot: pkgxray reasons about bytes in the tarball. A package that downloads and runs its real payload after install can ship a clean tree. pkgxray flags the capability when its shape is unambiguous, but pair it with runtime/install-time sandboxing when that risk matters.
Why few false positives: validated against the 47 most-installed npm
packages with 0 false blocks. READMEs run only the prompt-injection check
(never read as code); test/fixture/example files downgrade to review;
npm↔GitHub divergence is review, not auto-block (can't tell a build step from
tampering); URL shorteners count only when co-located with a capability. And
minification is not obfuscation — eval/new Function on a string
literal (a bundler's eval-source-map module wrapper, a new Function("return this") globalThis probe) is recorded as info, not flagged; only eval on a
computed argument (eval(atob(blob))) gates. That keeps heavily-bundled
frontend packages out of the review pile.
Prompt injection isn't "solved" by a scanner, and pkgxray doesn't claim to. The durable defense is architectural, and pkgxray's design reflects three honest layers:
review, never a false block.# Guard an npm package before it reaches your machine
pkgxray guard npm:some-package@1.2.3
pkgxray guard npm:some-mcp-server@1.2.3 --format json
# Guard a local extension and promote it only if policy allows
pkgxray guard ./ext --promote-to ./approved/ext
# Audit a whole project's lockfile (batch OSV query)
pkgxray audit package-lock.json # also: yarn.lock, pnpm-lock.yaml, package.json
pkgxray audit package-lock.json --deep # full static/GitHub layer on each blocked dep
# Audit supplied evidence directly
pkgxray --file examples/evidence.json --format json
# Re-check already-installed deps against *current* intelligence (monitoring)
pkgxray recheck package-lock.json # diff verdicts vs. stored baseline
pkgxray recheck package-lock.json --format json # machine-readable, for CI cron
The guard flow stages the extension in a private quarantine, audits the staged
copy, and only promotes it when policy allows — it never runs npm install,
lifecycle scripts, build steps, or extension code. For npm references: resolve
metadata → query OSV → block before download if vulnerable → otherwise extract
into quarantine and run the static audit.
Decisions: allow (promotion ok), review (inspect quarantine first), block
(do not install). Only safe promotes by default; --policy allow-review also
promotes review-grade. Exit codes: 0 safe/allow, 2 block, 3 review.
.pkgxray.jsonEvery surface — the CLI, the MCP server, and the proxy — reads one optional
policy file, .pkgxray.json, through the same loader, so your policy can never
drift between them. Zero config is fully safe: an absent file means maximum
strictness. You never have to write one.
The governing rule is tighten freely, loosen loudly. You may make the policy stricter without limit; you may loosen it (allow a package, mute a check) only explicitly, and every loosening is printed in the report — never silent. Two rules are enforced in code, not by convention:
allow entry must be pinned to name@version and a sha256. A
bare name would blanket-trust every future version — exactly how a trojaned
update gets in — so un-pinned allows are dropped with a warning, and a pin
only applies when the scanned artifact's digest matches.known-vulnerability findings always surface. You can vouch for a package's
code; you cannot vouch away a CVE.{
"policy": "safe-only", // or "allow-review" (a loosening — warns)
"failOn": "review", // CI exit threshold: fail at review (exit 3) / block (exit 2)
"scanErrorPolicy": "fail-closed", // a scan that errors → review, never silently safe
// loosen ONLY explicitly — each is shown in every report
"allow": [
{ "pkg": "left-pad@1.3.0", "sha256": "e0b0…",
"reason": "reviewed 2026-07", "expires": "2026-10-01" }
],
"mute": [
{ "check": "lonely-maintainer", "scope": "@myorg/*", "reason": "internal registry" }
],
// the MCP server reads the same file, but the agent deployment starts stricter
"mcp": { "tools": ["audit", "recheck"], "packageScanFirst": true, "timeoutMs": 15000 }
}
Precedence (lowest → highest): built-in safe defaults → project .pkgxray.json
→ local .pkgxray.local.json (gitignored, personal) → PKGXRAY_* env vars →
CLI flags. allow/mute lists concatenate across layers. See
.pkgxray.example.json and docs/config.md
for the full schema.
pkgxray recheckguard and audit give a point-in-time verdict at install. recheck answers
the follow-up they can't: has anything I already depend on become unsafe since
I installed it? — the maintainer-takeover / trojaned-update case.
It walks a lockfile, re-runs the guard evaluation (OSV / provenance / divergence)
for each pinned name@version, and diffs the fresh verdict against the baseline
stored in .pkgxray.lock (written by triage/guard). It reports a diff, not
a full report:
checkedAt (allow/safe → review/block).
The actionable signal: you may already be exposed.--verbose.pkgxray recheck package-lock.json # human diff
pkgxray recheck package-lock.json --verbose # also list unchanged deps
pkgxray recheck package-lock.json --no-write # don't update stored baselines
pkgxray recheck package-lock.json --format json # machine-readable diff
Exit codes key off the worst regression, so CI cron jobs consume recheck
exactly as they do guard: 0 nothing regressed, 2 a dep regressed to
block, 3 a dep regressed to review. A dep that was block at install
and is still block is not a new regression and does not fail the run.
Available newer-version updates never affect the exit code on their own (see
version drift below) unless you pass --fail-on-available-updates.
Set PKGXRAY_CACHE_URL so a large tree shares guard's warm cache instead of
re-fetching everything cold.
Alongside verdict drift, recheck also asks the registry whether a newer
version exists for each dep and guards it, so you see the security verdict
before upgrading — the trojaned-update catch:
review/block.
Don't blind-upgrade into it.To keep registry/OSV cost sane, at most two candidates are vetted per dep: the latest published stable version and the latest within the pinned major (an approximation of your install range), when they differ. Prereleases are skipped unless the pinned version is itself a prerelease.
Version drift is informational — an available flagged update you haven't
installed isn't an active exposure, so it never changes the exit code on its
own. Pass --fail-on-available-updates to make a flagged update count, or
--no-version-drift to skip the registry pass entirely.
Run recheck against the committed lockfile on a schedule; the job fails the
moment a dependency you already ship regresses:
# .github/workflows/pkgxray-recheck.yml
name: pkgxray recheck
on:
schedule:
- cron: "0 6 * * *" # daily 06:00 UTC
workflow_dispatch:
jobs:
recheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npx pkgxray recheck package-lock.json --format json
# exit 2 (regressed→block) or 3 (regressed→review) fails the build;
# commit the updated .pkgxray.lock back if you want the baseline to move.
Use the stdio server from any MCP-capable agent:
{ "mcpServers": { "pkgxray": { "command": "pkgxray-mcp" } } }
Tools: audit_agent_extension_supply_chain (static heuristics on supplied
evidence), guard_agent_extension_install (stage + vuln-check + audit a real
package, auto-fetches provenance), audit_lockfile_supply_chain (batch OSV scan
a lockfile), triage_lockfile_supply_chain (record each flagged dep as
allow/block into a sibling .pkgxray.lock).
pkgxray mcpAn agent pulls untrusted things in from outside two ways — packages it installs
(covered by guard/hook/proxy) and MCP servers it connects to. pkgxray mcp
covers the second with the same engine: it connects to a server (stdio or
streamable HTTP), performs the read-only handshake, enumerates the tool
manifest via tools/list — and never calls a tool, reads a resource, or
invokes a prompt.
# Vet the server package statically FIRST, then connect and audit the manifest
pkgxray mcp --package npm:some-mcp-server@1.4.2 npx some-mcp-server
# An HTTP server
pkgxray mcp https://mcp.example.com/mcp
# Approve what you just reviewed (pins per-tool fingerprints into .pkgxray.lock)
pkgxray mcp --pin --package npm:some-mcp-server@1.4.2 npx some-mcp-server
# Later / in CI: catch the rug-pull — descriptions, tools, or schemas that
# changed since approval. Exits 3 on unapproved drift, 2 on a verdict regression.
pkgxray mcp --recheck npx some-mcp-server
What the manifest audit looks for, all with the existing engine: prompt
injection in tool descriptions and the server's instructions blurb (the same
tiered matcher used on READMEs), instructions concealed in invisible Unicode
tag characters or base64 envelopes, and one MCP-specific check —
capability-surface mismatch, a tool whose stated purpose is narrow but
whose input schema takes a general-execution parameter (a get_weather that
accepts a command). Calibrated like the rest of pkgxray: a file reader
taking a path, an HTTP tool taking a url, a DB tool taking a query, or
an honest execute_shell tool are not findings.
The one caveat, stated plainly: everything else pkgxray does is static —
it never executes what it inspects. Enumerating an MCP server is not. There
is no manifest without a connection, and for a stdio server that means
spawning and running it. pkgxray mcp narrows the risk the way guard
isolates a tarball — the child gets an allowlist-scrubbed environment (no
inherited secrets), a hard timeout, bounded output, and its process group is
killed after the listing — but the safe order is package-scan first: pass
--package <ref> so the static, no-execution scan clears the server before
anything connects to it. A block halts the connect step (--force to
override); skipping the scan entirely requires the explicit
--no-package-scan.
pkgxray mcp-proxypkgxray mcp is connect-time: it answers "should this server be registered?"
and then gets out of the request path. Two attacks only exist inside a live
session, where a connect-time check can never see them: a manifest that
changes after approval (notifications/tools/list_changed — the rug-pull
moving in real time), and poisoned tool output steering the model. And a
separate probe can't watch a running stdio server either — it would spawn a
different instance than the one the agent is talking to. The only seam that
sees the actual session is the wire itself, so mcp-proxy sits on it: point
the host's server config at the proxy and it launches the real server as its
child, relaying every JSON-RPC frame through the gate.
// .mcp.json — wrap the real launcher
{
"mcpServers": {
"some-server": {
"command": "pkgxray",
"args": ["mcp-proxy", "--", "npx", "some-mcp-server"]
}
}
}
What the gate does, and what each piece costs:
| Moment | Check | Cost |
|---|---|---|
first tools/list | full static manifest audit (same engine as pkgxray mcp), tools that would be denied are stripped from the listing so the model never reads their descriptions | ~1 ms per 30 tools, no network |
every tools/call | in-memory verdict lookup against the last verified manifest; unknown / blocked tools denied | ~0.05 µs (p95 ~0.1 µs) |
tools/list_changed | immediate re-list + re-audit through the same session; calls arriving mid-verification are held, then decided against the fresh manifest (denied if the server won't answer — fail closed) | one manifest audit |
every tools/call result | doc-typed injection scan of the result text (tiered prompt-injection, unicode-tag smuggling, base64 envelopes), capped at 512 KiB | ~0.06 ms for a 2 KB result, ~13 ms worst-case at the cap; --no-scan-results to disable |
after --pin | fresh manifest diffed against the pinned per-tool fingerprints; drifted tools are denied under strict/balanced until re-approved with pkgxray mcp --pin | one lock-file read per verification |
Policies mirror the hookshot gate: block denies everywhere; review denies
under --policy strict, passes with a logged warning under balanced
(default) and permissive. A denied call never reaches the server — the
agent gets an isError tool result naming the reason, so the model can
explain instead of hanging. The proxy's own diagnostics go to stderr only;
stdout stays protocol-clean. On session close it prints a summary
(N calls gated, M denied, per-call gate p50/p95).
One deliberate difference from pkgxray mcp: the proxy is the production
conduit, not an enumerator — the child inherits the full environment the host
configured for it. Trust decisions here are about frames, not the child's env.
HTTP servers aren't wrapped (the host connects to them directly); vet those
with connect-time pkgxray mcp <url> + --pin/--recheck.
hookshot — a hookshot hook
binary that guards installs across Claude Code, Cursor, Windsurf Cascade, Factory
Droid, and OpenAI Codex: it intercepts an agent's shell command, runs
pkgxray guard on every package about to be installed, and denies on a BLOCK
verdict with pkgxray's cited evidence returned to the agent. See
examples/hookshot/.
Detailed reference lives in docs/reference.md:
block / review / info.guard timings and mcp-proxy gate overhead.Other docs: compatibility & stability tiers · JSON schema · configuration schema · canary threat model · design notes · adoption playbook.
npm test # zero-dep node --test suite
npm run benchmark # calibration corpus: precision/recall + 0-false-block gate
npm run build:browser
npm run audit:evidence -- --file examples/evidence.json
The calibration benchmark runs a labelled corpus of malicious and
benign fixtures through the real engine and fails on a false block or a missed
detection — the reproducible form of the "0 false blocks" claim. See
benchmark/README.md.
src/ analysis engines bin/ CLI entrypoints browser-extension/ MV3 ext
docs/ architecture examples/ sample evidence test/ node --test suites
benchmark/ calibration corpus + runner
FAQs
pkgxray — pre-install security for npm packages, MCP servers, and AI agents. Zero-dependency local static analysis with cited SAFE, REVIEW, or BLOCK verdicts.
The npm package pkgxray receives a total of 248 weekly downloads. As such, pkgxray popularity was classified as not popular.
We found that pkgxray demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.

Company News
Socket is now in the AWS Security Hub Extended plan. Adopt it through AWS, apply committed spend, and block malicious open source packages.