Repository navigation
terminal-security: package-manager rule flags installs but not typosquat targets (e.g. npm install lodahs) #12573
Description
Summary
@continuedev/terminal-security already gates package installs behind permission via isHighRiskPackageManager (it recognises npm/yarn/pnpm install/add/i). That rule answers "is this an install?" but not "what is being installed?". A command like npm install lodahs (one edit from lodash) is treated identically to npm install lodash: both land at allowedWithPermission, with no signal that the target name is a likely typosquat. Runner forms such as npx expres aren't covered by the package-manager list at all.
Typosquatting is one of the most common npm supply-chain attack vectors, and an AI agent driving the terminal is exactly the actor most likely to fat-finger or hallucinate a package name. The evaluator is the right seam, because it is the choke point that decides whether a terminal command auto-runs.
Gap (reproducible, offline)
npm install lodahs— no distinction from a legitimate install; a reviewer sees no "this looks like a typosquat of lodash" signal.npx expres—npx/bunxaren't in the package-manager list, so the typosquat target gets no dedicated treatment.pnpm dlx expres,yarn add lodahs,bun add lodahs— same gap across managers and runners.
Proposal
Add a sibling, pure/offline checker in the same seam that detects npm-ecosystem installs (and npx/bunx/dlx/exec runner targets) of a name exactly one edit away from a curated set of popular packages, and wire it into the existing isHighRiskCommand aggregator so a hit escalates the policy to allowedWithPermission (most-restrictive-wins). It never hard-denies; it only asks the human.
Design:
- Pure/offline/deterministic. No network, no filesystem, no new runtime dependencies.
- Gate: Optimal String Alignment (restricted Damerau-Levenshtein) distance == 1 and the matched popular name length > 4 (short names like
vueproduce too many false positives). - Hardening to avoid false positives: sub-command splitting on
;/&&/||/|/&/newlines, env-assignment + wrapper prefixes (sudo,env FOO=bar), path-prefix on the binary, leading global options before the subcommand, value-taking install flags (sonpm install --prefix axio lodashdoes not flagaxio, which is a--prefixvalue, not a package), quote stripping, and@scope/pkg@versionnormalization.
I have a working implementation + vitest suite ready (45 new cases including the --prefix axio false-positive guard; the package's full suite stays green at 269 passing; tsc --noEmit clean) and proposed the same check upstream in Goose: aaif-goose/goose#9642. Would the maintainers be open to it living next to the existing isHighRisk* checks in evaluateTerminalCommandSecurity.ts? Happy to open the PR.
Activity
Ram9199 commentedon Jun 13, 2026
I am collecting real-world incidents around AI agents with terminal/package-manager authority, and this proposal is relevant.
Was this driven by an actual incident or near miss where an agent installed/hallucinated a risky package, or is it pre-incident hardening? If there was a real case, what changed afterward - manual approval, separate credentials, Docker/VM, allowlists, audit logging, custom policy, or no change?
Not asking about any product. I am trying to determine whether agent-authority pain is already operationally felt.
mvanhorn commentedon Jun 17, 2026
Implemented in #12727 - feat: Add typosquat-target detection to terminal-security package-manager rule.
AyoubTadlaoui commentedon Jun 20, 2026
Thanks for picking this up @mvanhorn Matt, glad it found a home in the rule.
One thing worth folding in if it isn't already: a couple of forms that slip past a naive check. npm exec -w foo lodahs where the package is
buried after runner options, and the alias form pkg@npm:lodahs where the real install target is on the right side of the alias. Both produce a
real typosquat install but read clean if you only look at the first non-flag arg. Happy to share the test vectors I had for those if useful.
AyoubTadlaoui commentedon Jun 20, 2026
@Ram9199 Pre-incident hardening, not a postmortem. The thing I kept hitting is that an agent will confidently run npm install on a name i half-remembered or that a model just made up, and the package manager has no idea the name is one character off something real. There's no failure that triggers, the wrong thing just installs and runs its lifecycle scripts.
So the gap felt operationally real before any single incident: the agent has install authority, the registry resolves whatever string it's handed, and nothing in between is checking "is this name suspiciously close to a popular one." That's the seam this rule sits in. Manual approval helps but only if the human reviewing it happens to notice the typo, which is exactly the thing people miss.
Curious what your incident collection is turning up, if it's somewhere public I'd read it.
AyoubTadlaoui commentedon Jun 20, 2026
@mvanhorn Looks like the -w runner case is already covered by your skip list, nice. The one I think still slips through is the alias formlodash@npm:lodahs since the version-strip cuts at the first @, so the check sees lodash and the real install target lodahs on the right of npm goes unseen. Small vector if it's worth a test: npm install lodash@npm:lodahs.
mvanhorn commentedon Oct 28, 2026
@AyoubTadlaoui good catch, you were right that the alias form slipped through. 38d29e5 on #12727 reads the target after
@npm:, sonpm install lodash@npm:lodahsnow warns onlodahs(suspectedlodash).lodahs@npm:lodash@4.17.21stays quiet because the target is the real package. Both cases are in the test file and fail without the change. The terminal-security suite passes (234).