Skip to content

terminal-security: package-manager rule flags installs but not typosquat targets (e.g. npm install lodahs) #12573

Open

Description

@AyoubTadlaoui

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/bunx aren'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 vue produce 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 (so npm install --prefix axio lodash does not flag axio, which is a --prefix value, not a package), quote stripping, and @scope/pkg@version normalization.

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

  1. added
    area:toolsRelates to tool usage
    javascriptPull requests that update Javascript code
    kind:enhancementIndicates a new feature request, imrovement, or extension
    on Jun 7, 2026
  2. Ram9199 commented on Jun 13, 2026

    @Ram9199

    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.

  3. mvanhorn commented on Jun 17, 2026

    @mvanhorn
    Contributor

    Implemented in #12727 - feat: Add typosquat-target detection to terminal-security package-manager rule.

  4. AyoubTadlaoui commented on Jun 20, 2026

    @AyoubTadlaoui
    Author

    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.

  5. AyoubTadlaoui commented on Jun 20, 2026

    @AyoubTadlaoui
    Author

    @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.

  6. AyoubTadlaoui commented on Jun 20, 2026

    @AyoubTadlaoui
    Author

    @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.

  7. mvanhorn commented on Oct 28, 2026

    @mvanhorn
    Contributor

    @AyoubTadlaoui good catch, you were right that the alias form slipped through. 38d29e5 on #12727 reads the target after @npm:, so npm install lodash@npm:lodahs now warns on lodahs (suspected lodash). lodahs@npm:lodash@4.17.21 stays 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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:toolsRelates to tool usagejavascriptPull requests that update Javascript codekind:enhancementIndicates a new feature request, imrovement, or extension

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions