phi started as an npm-only behavioral scanner. It's becoming a supply-chain firewall that doesn't care which registry produced the bytes. This page is the overview; per-version release notes live in CHANGELOG.md.
shipped released, on GitHub
Releases, runnable today
in progress actively being worked
on
next up committed direction, queued
behind the in-progress item
horizon direction we're going, not
committed to dates
out of scope explicit boundary —
phi will not become this
phi audit now runs as a first-class CI step —
philtechs-org/phi-action
downloads the matching phi binary, scans your npm dependency
tree on every pull request, and fails the check when a
package comes back BLOCKED. Same detection engine
phi watch runs continuously against the public
registry, gated at the point a team is actually reviewing a
change.
phi watch now monitors Go module paths
(github.com/gin-gonic/gin,
golang.org/x/tools) alongside npm packages and
GitHub repos, running the same Go-specific detector set
phi view already used — init()
network and filesystem access, cgo plus process execution,
build-tag-gated payloads, //go:generate fetching
remote code. The scan stream gains an ecosystem filter.
The larger half of this release is scan accuracy. Every fix
came from scanning a real, widely-used package and working
out why the verdict looked wrong: next@16.3.0
went 65/BLOCKED → 25/REVIEW and three@0.185.1
went 80/BLOCKED → 45/REVIEW. Between them:
.npmrc existence probes read as credential
theft, vendored polyfills (vm-browserify, setimmediate) read
as arbitrary code execution, the V8
toFastProperties hack's deliberately-unreachable
eval, hex escapes in a regex character class
read as obfuscation, and a package-age signal that measured
the current release's publish date rather than the package's
first — so every release of every actively-maintained package
took a "brand new" penalty for a week afterward. AST-based
detection also now runs on ES modules and bundled output,
which two unrelated parser issues had been silently
downgrading to weaker regex matching. Both headline packages
are now in the regression corpus, which runs nightly.
phi prune removes packages phi.lock/
node_modules still hold but package.json
no longer needs, plus anything under node_modules
phi never tracked at all (a manual npm install
alongside phi, or leftovers from before phi managed the
project). phi explain <pkg> extends
phi why with the requested version range at every
hop in a dependency chain and which package.json
section pulled it in. phi doctor bundles
environment sanity checks — phi's version against the latest
release, registry reachability, lockfile/node_modules
consistency, cache size — into individual pass/warn/fail lines.
phi cache clean --force now works as the npm-parity
alias for --all.
An unrecognized command now falls back to running the matching
package.json script when one exists —
phi typecheck is the same as phi do
typecheck, matching npm <script>'s
shorthand. Falls through to the normal "unknown command" error
when no script by that name exists, so it never shadows a real
phi command or silently swallows a typo.
Every detection now carries a confidence tier —
ast-confirmed, regex-fallback, or
regex-only — surfacing a distinction the
Arbitrary Code Execution/Dynamic Code
Compilation detectors already computed internally but
previously threw away: a real AST-parsed eval(...)
reads differently from a regex match on a file goja couldn't
parse. Shown in phi view, --json
output, and on phi watch's package page.
phi watch now computes a sha256 file manifest for
every scanned tarball and diffs it against the package's prior
scan, rendered as a new "what changed" panel — added, removed,
and changed files between two versions, a sharper signal than a
bare score delta. When two consecutive scans are byte-identical
(a version bump that only touches package.json's
version field), the scan skips the analyze/advisory/reputation
pipeline and copies the exact prior verdict forward instead of
recomputing an equivalent one.
Package pages also show a real subscriber count (developers watching that package) instead of the earlier placeholder.
Scores now get a small, transparent adjustment (-10 to +15)
based on a package's publish age and maintainer count —
signals already in every npm packument. A brand-new,
single-maintainer package nudges a borderline score up; a
long-established one nudges it down. It's a third scoring
stage after detectors and advisories, never overrides a
confirmed-malicious verdict, and always explains itself via a
notices entry when it applies. Applies across
phi view, phi install/phi
audit, and phi watch.
phi watch's package page gained a historical score
sparkline, and users can now set a generic outbound webhook
(Slack/Discord/PagerDuty/anything) alongside or instead of
email alerts — shipped with SSRF protection (HTTPS-only,
private/loopback/link-local/cloud-metadata IPs rejected, no
redirect-following) since it's the first place phi watch makes
a request to a user-supplied URL.
An OSV MAL-* advisory (GitHub/OSV's confirmed-
malicious classification) now forces
Score=100/blocked outright instead of
contributing points like an ordinary CVE — "this is malware" is
a certainty, not a probability to weigh against detector noise.
phi view/phi audit --json gained a
dedicated malicious field, distinct from a
generic BLOCKED verdict, with a ☠ CONFIRMED
MALICIOUS banner in text output. Credential
Theft was also downgraded from CRITICAL to HIGH — a bare
credential-file read alone shouldn't carry the same weight as a
confirmed outbound exfil call.
A tarball npm scrubbed down to an empty placeholder (a
security-holding-package takedown) has nothing left for
detectors to find, so the whole verdict rode entirely on the
OSV advisory — but MAL-* IDs never carry a CVSS
severity, so they fell through to a generic 5-point "LOW"
weighting that wasn't enough to leave a package flagged as
safe. MAL-* advisories are now
weighted as CRITICAL regardless of reported severity,
everywhere advisory points are merged.
A run of detector-accuracy fixes against real packages —
skills, fastify, next,
zod, cowsay, nx,
create-react-app — tightening
Credential Exfil Flow/Credential
Theft's canonical-host checks, teaching
Arbitrary Code Execution/Dynamic Code
Compilation several well-known bundled-dependency
idioms (ajv's minified validator codegen, @vercel/nft,
depd's deprecation wrapper), stripping comments/prose
before regex fallback so remarks and doc strings stop matching
as if they were real calls, and excluding source-attribution
comments from Network Exfiltration. Every
detection also gained a line number, and evidence snippets now
show the real matched source (not a fixed template) at up to
240 characters instead of 80.
phi
watch gained support for GitHub-sourced packages (the same
owner/repo shorthand phi view already
accepts), authenticated GitHub API requests (raises the rate
cap from 60/hr to 5000/hr once polling multiple repos), and
captures each package's npm description for the dashboard.
phi audit now cross-checks each direct dependency
against phi
watch's public feed — if watch has seen a worse verdict
since your last local scan, it's called out by name.
--submit-watch opts a run into publishing its
scanned packages to that feed; off by default, nothing leaves
your machine unless you pass it. A new local-only drift check
also compares each package's verdict at last install
(phi.lock) against its current audit verdict, so
a package that's silently gone from SAFE to flagged gets
surfaced by name — directly answering "why didn't phi
update catch this" (a caret range can't reach a major
bump; drift tells you which package needs an explicit one).
Fixed: phi view <pkg>@<tag> now
resolves npm dist-tags like latest or
next, not just exact versions.
phi audit now recommends what to upgrade a flagged
package to, not just its score — advisory fixes use OSV's
known-good version, and scanner-flagged packages with no
advisory get a candidate rescanned and only recommended if the
score actually improves. Recommendations distinguish an
in-range bump (phi update reaches it) from a
breaking one, and phi audit fix can now apply
scanner-verified bumps too.
phi rebuild now runs lifecycle scripts with
node_modules/.bin on PATH, so a postinstall that
calls node-gyp, the prisma CLI, or any
dependency binary works. One failing script no longer aborts the
batch, successes read as status instead of warnings, and
packages run in the order named.
phi install/phi ci now extract a
git-sourced dependency into node_modules instead
of resolving it and stopping short.
phi now resolves and scans packages that live only on GitHub
instead of a registry — npm dependencies written as
github:owner/repo#ref, and phi view
owner/repo for one-off lookups. Fetched via the GitHub
API and codeload.github.com, scored through the
same analyzer pipeline as any registry package.
.phi-allow supports the resulting
owner/repo@ref identity.
phi rebuild <pkg…> lands as the explicit,
per-package opt-in to lifecycle scripts. A plain
phi install still runs none by default — and now
ends with an advisory listing any package whose
preinstall/install/postinstall
hooks it skipped, printing the exact phi rebuild
line to enable them.
phi view now inspects several packages at
once — phi view nodemailer express resend
renders a card each plus a per-package verdict summary, and
--json emits an array. Every flag across
phi view and the install/audit report cards now
shows the reason behind it — severity, the matched
evidence:, and the file: — so a
BLOCKED verdict explains itself instead of sending you back to
re-run a scan.
optionalDependencies are now resolved by default
with platform-aware filtering. Modern dev tooling that ships
native binaries this way — vite,
rollup, esbuild,
swc, lightningcss,
sharp — finally installs cleanly. The
resolver reads each package's os and cpu
fields and silently drops platform mismatches the same way
npm does; a packument failure on an optional entry is also
silent. --omit=optional becomes meaningful and
reinstates the previous strict behavior.
The install flow now runs in two phases:
safe packages extract immediately after the scan, then the
review prompt fires with the full per-detection breakdown
inline — severity badge, detector, the why:
description, the matched evidence:, and the
file: path. No more scrolling up to remember
what you're approving. Review-approved packages and any
safes that were deferred because their closure touched a
pending review then extract in a second pass.
The detector pipeline was tuned to clear false positives on
popular bundled CLIs and packages with embedded binary data.
prisma (score 100 → 35) and
pdfjs-dist (90 → 20) no longer land in the
BLOCKED tier: their hex-escape charset tables, embedded
OpenType / TrueType fonts, bundler
eval("__dirname") recovery shims, and
.npmrc reads by legitimate native-binary
installers all stopped tripping detectors without weakening
coverage of real attacks. Six new regression tests pin the
contract.
phi install gains
--strict-closure /
PHI_STRICT_CLOSURE: a CI gate that bails
before writing phi.lock when any
package would be pruned due to incomplete dep closure.
Without the flag, partial-prune installs now exit non-zero
so CI pipelines notice the incompleteness without re-reading
the per-package warnings.
phi install now iteratively prunes packages
whose transitive dependency closure is incomplete —
typically when a parent in the resolved tree has a dep that
itself failed scan, was excluded by --omit, or
has no available tarball. The previous behavior would
install the parent with dangling refs, leaving
node_modules in a state where requires could
fail at runtime.
A fixed-point algorithm walks until no further pruning is needed, records each skip reason (blocked transitive, missing tarball, removed from tree), and surfaces the list of affected direct deps in the install report so the user knows exactly what to address.
npm installs are now best-effort: phi installs resolved SAFE
packages first, reports unresolved packages after extraction, skips
BLOCKED packages unless --force is used, and queues
REVIEW packages for an explicit approval step.
Accepted REVIEW packages are remembered in phi.lock, so
repeated installs do not ask again for the same accepted package
version. CLI flags such as --yes and
--no-advisories are parsed as installer flags instead of
package names.
New phi check / phi verify validates
package.json, phi.lock, installed package
directories, cached tarball integrity, and installed files when a
cached tarball is available. Registry fetches also gained retries,
a longer default timeout plus PHI_REGISTRY_TIMEOUT,
PHI_REGISTRY_ATTEMPTS, and
PHI_REGISTRY_RETRY_DELAY for slow networks.
phi becomes a polyglot supply chain firewall. Auto-detects
Go modules (go.mod, go.work) and
routes to the new hybrid replace pipeline: shells
out to go mod download into a phi-controlled
staging dir, scans every fetched zip with the analyzer + OSV,
then atomically materializes approved bytes into
$GOMODCACHE. BLOCKED modules never reach Go's
cache without an explicit override.
Full command surface: phi install,
install <mod>@<v>,
update, remove,
audit, audit fix,
outdated, upgrade-interactive,
view, why, ls,
build / test / run /
vet / fmt, doc,
replace / unreplace,
init --go. Plus Go-toolchain aliases
(phi mod tidy = phi install,
phi get = phi install,
phi mod verify = phi audit, etc.)
and the universal phi go <anything>
escape hatch.
Go workspaces (go.work) — multi-module
monorepos walk every use directive with a
per-member phi.lock and an aggregate verdict
summary. Binary install (phi install
<tool>@latest outside a project) gates global
Go tools through the same scan pipeline before
go install writes to $GOBIN.
Vendor via phi mod vendor.
Cross-compile sugar
(phi build --cross linux/arm64).
Replace /unreplace wrappers with
cross-org-fork detection.
Six new Go-specific detectors: go-init-network,
go-init-fs-write, go-cgo-shellexec,
go-build-tag-gated,
go-go-generate-curl, go-unsafe-import.
Plus the project-level go-replace-with-fork.
Schema-v3 phi.lock with a tagged
source.type union covers npm and Go in one
file. Verdict cache (h1-keyed, content-addressed) cuts
warm-cache install time ~50%. .phi-allow
persistent project allowlist. Retraction + deprecation
warnings surfaced on install. sum.golang.org transparency
status in audit footer.
phi view scans by default; live spinners
2026-05-16
phi view <pkg> now runs the detector
pipeline + OSV check on the resolved tarball and prints a
security: block (score, verdict, detections,
advisories with fix versions) alongside the standard
metadata. Same engine phi install and
phi audit use — so you get the same verdict you
would get if you installed the package, before you add it.
--no-scan opts out for scripting; field-targeted
queries (phi view react license) auto-skip.
Spinners now cover phi view,
phi outdated, and
phi upgrade-interactive so long-running phases
never look stuck. phi help rewritten and grouped
by lifecycle.
The biggest single drop of CLI ergonomics so far. Tier 1
features: phi ls (npm-style ASCII trees),
workspace: protocol (workspace:*,
workspace:^1.0.0, path-relative form),
phi link / phi unlink (Windows
junctions, no admin), and phi do --filter
(pnpm-style selectors). Tier 2:
phi view packument inspector,
phi config with secret redaction,
phi pkg dot-path editing, and
phi upgrade-interactive. Plus
--omit=dev,optional,peer, env var equivalents
(PHI_OMIT, PHI_CI, …), and
phi cache clean --runs /
--all-caches for stage cache eviction.
Registry failures now surface with the right shape: a
non-existent package gets a clear
not found · check the spelling or whether it's
private instead of a raw 404 Not Found
string. phi audit no longer dies on the first
registry failure — transient or missing packages are
recorded in phi-report.json's new
skipped array and the rest of the tree is
scanned. Registry fetches retry up to 3× with exponential
backoff (500ms / 1s / 2s) + ±25% jitter on network errors,
429 rate-limits, and 5xx; Retry-After honored.
The spinner now shows
resolving... (retrying X · N total) so flaky
networks are visible instead of looking like a hang.
phi ci for production installs
2026-05-11
phi ci is the canonical one-line install for
non-interactive environments (Docker builds, GitHub Actions) — sugar
for phi install --frozen-lockfile --yes. The frozen
lockfile is the audit record: anything in phi.lock was
already reviewed and approved by the developer during their local
install, so prod can auto-approve review verdicts without a prompt.
Blocked verdicts still abort unless --force. Plus two
composable primitives: -y/--yes
(auto-approve reviews on any install/update) and
--omit=dev (skip devDependencies, npm
parity).
phi x, scan-and-run like npx
2026-05-10
phi x <pkg> now mirrors npx: if the
bin isn't already in node_modules/.bin, phi resolves
the providing package, runs the full scanner + advisory pipeline
over the transitive tree, then runs the bin from a per-version cache
without polluting your node_modules. A scan-passed
marker means repeat invocations skip straight to running. Pinned
versions, scoped packages, bin-name-≠-package-name (phi x -p typescript tsc), and the -- separator all work the npx way.
Lifecycle scripts stay off — phi-stricter than npx by design. Plus
an animated resolver spinner with the first frame drawn immediately
— no more silence between banner and progress.
Mid-install interruption can no longer corrupt your
package.json. Bad packages can no longer crash a scan
run. Friendlier first-run experience on Windows.
phi audit fix [--apply | --force] — proposes safe fixes
for typosquats, vulnerable versions, and deprecated packages.
Preview by default; you opt in to applying. Scan progress now
renders consistently across every shell we ship to.
Two new behavioral detectors targeting recent threat patterns (credential exfiltration flow + Linux system tampering), plus deprecation guidance for packages with safer successors.
phi self-update brings phi current with one command.
Verifies the new binary against published checksums before swapping.
phi create bootstraps React, Next, Express, Fastify, or
Nest projects — with the scaffolder itself audited first.
--force escape hatch for packages you know you trust
despite a flagged verdict.
The install-time interception pipeline: 19 detectors (npm + Go), OSV vulnerability layer, lifecycle scripts off by default, lockfile + audit report, cross-platform binaries.
Cleans up the install experience on first run. Same binary, signed.
Nothing queued right now — tell us what npm workflow still feels rough.
Directional, not committed to dates. Order is rough priority.
Live scan as you edit your manifest.
First-class GitHub Action, GitLab CI templates, drop-in for the major platforms.
Opt-in per package; results feed the same risk score.
Strict trust model — signed plugins only.
phi will not become these things. Listing them here so the boundary is explicit.
phi is install-time only. Once code is in
node_modules/ and your app runs it, that's the
application's concern — use container limits, gVisor, or an EDR for
that.
phi.lock is a complete, integrity-verified lockfile —
phi can be your only package manager, in CI, in Docker, in
production. But if you'd rather keep
package-lock.json / yarn.lock /
pnpm-lock.yaml alongside for tooling that expects them,
phi leaves them alone. Drop-in, not rip-out.
No daemon. No phone-home. Ever. Decisions happen on your machine, with your data, full stop.
phi stays free and open-source. Commercial offerings would be separate products built on top — never behind a paywall on the install path itself.