On This Page
Agent Plugin Security: What a Plugin Can Reach in an npm Agent Runtime
Agent plugin security is a permission-model problem, not a scanner problem. Here is the install-decision rule for npm agent runtimes like DeepSeek Harness.

You are one command away from running third-party code you have never read against your own filesystem, your own credentials, and your own shell:
npx @deepseek-ai/dsh webThat is the documented install path for the DeepSeek Harness (dsh), open-sourced on GitHub on 2026-08-13 under MIT as a v0.1 Developer Preview. It fetches packages from npm and executes them locally, then serves a web UI at http://127.0.0.1:3080. Every capability in the runtime, models, tools, skills, sessions, sandboxes, filesystems, loops, orchestration, and the UI itself, is a plugin. And within a day of launch, hundreds of community plugin repositories were appearing under the dsh-plugin GitHub topic, a marketplace with no central review step.
This is the core of agent plugin security, and it is not a new problem so much as the same problem moved to a worse layer. The agent skill marketplaces already taught us how bad third-party agent supply chains are. An npm-native agent runtime reproduces that problem at the layer that holds your shell. The question this piece answers is not "will a scanner catch a bad plugin." It is the harder one: once a plugin is loaded, what can it reach? That is a question about the runtime's permission model, and in this design the answer is uncomfortable, because the sandbox is itself a swappable plugin.
If you want the architecture and the case for the runtime on its own terms, I covered that in the open agent runtime. This piece is the threat model.
The problem is measured, not hypothetical
Before reaching for any defense, get the size of the threat right, and get it from data rather than intuition. Two findings set the floor: what the marketplaces are already serving, and what happens the moment you reach for the obvious tool.
The honest baseline: marketplace supply chains are already bad
Start with the best data we have, because it sets the floor for what to expect.
NVIDIA studied 42,447 agent skills pulled from major marketplaces. They reported that 26.1% carried vulnerabilities and 5.2% showed likely malicious intent. Read those two numbers slowly. Roughly one in four skills in the wild had a security defect, and one in twenty looked deliberately hostile. This was not a red-team exercise on a curated sample. It was a census of what real marketplaces were actually serving to real users.
The load-bearing sub-finding is the one to tattoo on the inside of your eyelids: skills that bundle executable scripts were 2.12x more likely to be vulnerable than instruction-only skills. That multiplier is the whole argument in a single number. A skill that is only natural-language instructions is a relatively contained risk. The moment a skill carries code that runs, the risk more than doubles.
Now apply that to a plugin runtime. An npm plugin is not instruction-only. It is executable code by definition. There is no "documentation-only" mode for a Node package that contributes a service to your runtime. So a plugin ecosystem is not somewhere in the middle of that 2.12x spread. It is the worst case of it, structurally, for every single entry. You do not get to hope your plugin is on the safe side of the executable/non-executable line, because there is no non-executable side.
NVIDIA released a scanner alongside that study, SkillSpector (Apache 2.0), which encodes roughly 64 vulnerability patterns across 16 categories. It is a genuinely useful tool and you should run it. But it is a detector, and detectors set up the next question, which is where most of the industry's thinking stops one step too early.
Why "just scan it" is not the defense
The reflex, once you accept the baseline, is to reach for a scanner and call it a control. That reflex is wrong, and it is wrong in two independent ways.
The first is that scanners lose the adversarial game. An independent test in 2026 (an Adversa exercise written up as a Cloud Security Alliance research note) walked a single malicious skill past eight open-source scanners, including the current leaderboard leader. The bypasses were not exotic: encoding, homoglyph substitution, paraphrase, and bundled-code obfuscation. These are the standard evasion moves, and the standard evasion moves worked against the standard tools. If your control is "we scan on install," you have a control that a motivated author already knows how to defeat, and the effort required to defeat it is low.
The second reason is deeper and it is the actual subject of this article. Even a perfect scanner answers the wrong question. Byte-matching tells you whether code looks malicious. It does not tell you what the code is permitted to touch when it runs. Those are different problems, and only the second one bounds your blast radius. A plugin that is completely benign today, authored in good faith, becomes malicious the day its maintainer's npm account is compromised or a transitive dependency three levels down is quietly republished. No scan you ran at install time constrains what that code does at run time. The permission model does.
So the useful question is not "is this plugin bad." It is "if this plugin, or anything it depends on, turns out to be bad, what does it get to reach." That reframing is the entire move. It shifts you from detection, which is a losing arms race, to containment, which is an engineering property you can actually verify.
The real question is containment, not detection
Detection is where most of the industry's thinking stops. The move that matters is to stop asking whether a plugin is bad and start asking what it can reach once it runs, and this runtime makes that question unusually hard to answer.
The runtime makes the containment question hard
Here is where the DeepSeek Harness design becomes genuinely interesting as a case study, and where it earns its "everything is a plugin" tagline as a warning rather than a feature.
The Harness is built on Cordis (v4), a mature plugin framework that predates it, originally from the Koishi chatbot project. Cordis is a well-designed piece of software. Plugins register into a shared context, contribute typed services and events, and apply reversible effects. As a plugin architecture it is clean. That cleanliness is exactly the problem when you think about it as a security boundary, because the design principle is that nothing is privileged. Everything composes.
Read the capability list again with a security eye. The sandbox is a plugin. The filesystem is a plugin. Storage is a plugin. These are not the walls of the house. They are furniture, and any other plugin can rearrange the furniture. If the component that is supposed to contain untrusted code sits at the same privilege level as the untrusted code, then in principle a plugin can be swapped for the very component meant to contain it. A sandbox you can replace from inside the thing you are sandboxing is not a security boundary. It is a suggestion.
This is not a claim that the Harness has a specific exploitable bug. v0.1 Developer Preview is honest labeling, and I am not reporting a CVE. It is a claim about where the trust boundary sits in this class of design. When containment is implemented as a peer plugin rather than as a property enforced by a layer the plugins cannot reach (the OS, a real VM, a container the runtime does not control from inside), you cannot reason cleanly about what any plugin can reach, because the answer depends on what every other loaded plugin has already done to the context.
This is the same stance the industry converged on for the Model Context Protocol. I have argued before that MCP servers are untrusted code you run on your machine, and that the protocol's design does not change that fact. An npm agent runtime is MCP's problem with a bigger surface, because it is not just servers you connect to, it is the sandbox, the storage, and the loop, all installed the same way, all peers.
The transitive dependency reality kills "read the source first"
The most common piece of advice you will hear, and the one that sounds most responsible, is "read the source before you install it." It does not survive contact with npm.
The plugin you install is the top of a tree. When you npm install or npx a plugin, you are not installing one package. You are installing its dependencies, and their dependencies, and so on down a graph that routinely runs to hundreds of packages for a modestly featured Node project. You cannot read that tree. Nobody reads that tree. And even if you heroically read all of it today, the lockfile is a moving target: a caret range on a single sub-dependency means tomorrow's install can pull a version you never saw, published by an account you never evaluated, after your review was already "done."
This is why "read the source first" is not a control for a plugin runtime. It is a control for a single vendored file. The instant executable code arrives through a transitive npm dependency graph, source review stops scaling and version pinning becomes the only honest form of "I know what I ran." You are not defending against the plugin author you chose. You are defending against every account that can publish anything anywhere in that plugin's dependency closure, now and at every future install.
Combine this with the earlier point. Scanning does not bound reach. Reading does not scale. The only things left that actually constrain a compromised dependency are the permission model at run time and the version pin at install time. Everything else is theater.
What to actually do about it
Containment is an engineering property, not a hope, so here is how to get it, followed by an honest accounting of what it costs you to keep.
The install-decision rule the ecosystem does not ship
Here is the operational core, the rule the marketplace conspicuously does not hand you along with the one-command install. Treat every third-party plugin as hostile-capable code and decide accordingly. This is a checklist you run before, not after.
- Scope it to a throwaway profile, never your primary one. Run the runtime under a dedicated OS user or profile with its own home directory, its own shell config, and its own credential store. The default
npxinstall runs as you, which means it inherits your SSH keys, your cloud CLI tokens, your.envfiles, and your git credentials. A separate profile is the single highest-leverage control on this list, and it costs almost nothing. - Give it an ephemeral, disposable workspace. The plugin should operate inside a directory you can delete, mounted or bound so it cannot walk up into your real home. Assume anything written outside that workspace is a compromise. The cost here is real: an ephemeral workspace does not keep state between runs, so you pay in convenience for every bit of persistence you deny it. That trade is the point.
- Least-privilege filesystem and network egress by default. Deny first, then allow the specific paths and hosts the plugin genuinely needs. Most plugins do not need to reach arbitrary internet hosts. Egress control is what turns a credential-theft plugin into a plugin that stole credentials it cannot exfiltrate. If your runtime cannot restrict egress, the container or the OS around it must.
- Pin the plugin and its transitive tree. Commit a lockfile. Install with the equivalent of
npm ciagainst that lockfile, not a fresh resolve. Pinning the top-level plugin while leaving its dependencies on floating ranges pins nothing that matters. If you cannot reproduce the exact tree you reviewed, you did not review what you ran. - Verify what the sandbox plugin actually enforces, not what it is named. In an "everything is a plugin" runtime, "sandbox" is a package name, not a guarantee. Read what isolation it actually provides and, critically, whether another loaded plugin can replace or defang it. If the containment lives at the same privilege level as the code it contains, treat it as zero containment and provide the real boundary yourself with an OS-level control: a container, a VM, or a locked-down user.
- Never point an untrusted plugin at real credentials. Not your production cloud keys, not your primary GitHub token, not your password manager. If a plugin needs to authenticate to something to be evaluated, give it a scoped, revocable, low-privilege credential minted for that evaluation and revoked after. The blast radius of a compromised plugin is exactly the set of credentials it can read.
- Isolate at the OS layer, not inside the runtime. Because the runtime's own sandbox is a peer plugin, the boundary you can trust is the one the runtime cannot reach from inside. Run the whole thing in a container or a disposable VM. This is the control that holds even when every assumption above is wrong, which is why it is the one to reach for first when the stakes are real.
None of this is exotic. It is the standard containment discipline you would apply to any untrusted binary. The reason it is worth spelling out is that the one-command install path actively invites you to skip all of it, because npx @deepseek-ai/dsh web running as you, in your home directory, against your real credentials, is the path of least resistance and it is the worst possible configuration.
The tradeoffs are real, so name them
I am not going to pretend containment is free. It is not, and a piece that pretends otherwise is selling something.
Isolation costs latency and developer experience. A plugin that runs in a locked-down container with restricted egress is slower to start, more annoying to debug, and more likely to fail in ways that look like the plugin being broken when it is really the sandbox doing its job. Every boundary you add is friction, and friction is why people run npx as themselves in the first place. The DX gap between "one command, full access" and "properly contained" is the entire reason this problem persists.
Ephemeral workspaces cost you state. An agent runtime is often more useful the more context it accumulates, and a disposable workspace throws that context away on purpose. You will re-set-up things. You will lose caches. That is the price of not letting accumulated state become accumulated attack surface, and it is a price worth paying for anything you have not fully vetted, which for a transitive npm tree is everything.
And OS-level isolation costs you the frictionless experience the whole ecosystem is optimized to deliver. The marketplace wants installs to be one command because one-command installs are how marketplaces grow. Your interests and the marketplace's interests diverge exactly at the boundary you need to add. That divergence is worth being honest about, because it explains why the safe path will never be the default path. The default optimizes for adoption. You have to optimize for blast radius yourself.
This is the same lesson that shows up whenever an eval harness becomes attack surface: the tool you run to evaluate other things is itself code you are trusting, and the harness that orchestrates untrusted plugins is the highest-value target in the whole setup. And it is the same discipline that separates a demo from AI agents in production, where "it runs on my machine as me" is not a deployment model, it is an incident waiting for a date.
Key Takeaways
npx @deepseek-ai/dsh webis remote code execution by design: it fetches and runs unreviewed third-party npm code locally, as you, with access to your filesystem, credentials, and shell. The convenience of the one-command install is identical to its risk.- The honest baseline is bad and measured, not hypothetical: NVIDIA's census of 42,447 agent skills found 26.1% vulnerable and 5.2% likely malicious, and skills bundling executable scripts were 2.12x more likely to be vulnerable. An npm plugin is always executable code, so a plugin runtime is the worst case of that multiplier for every entry.
- Scanning is not a defense. Eight open-source scanners, including the leaderboard leader, were bypassed by encoding, homoglyphs, paraphrase, and bundled code. Even a perfect scanner answers the wrong question, because byte-matching tells you what code looks like, not what it is permitted to reach at run time.
- Agent plugin security is a permission-model problem. The bounding question is not "is this plugin malicious" but "if this plugin or any of its dependencies turns malicious, what can it reach." That reframing moves you from a losing detection arms race to a verifiable containment property.
- In an "everything is a plugin" runtime built on Cordis, the sandbox and the filesystem are themselves plugins, sitting at the same privilege level as the code they are meant to contain. A containment layer another plugin can replace is not a boundary. Provide the real boundary at the OS layer, which the runtime cannot reach from inside.
- "Read the source first" does not survive a transitive npm dependency tree. You cannot review hundreds of packages, and floating version ranges mean tomorrow's install differs from today's review. Version pinning against a committed lockfile is the only honest form of "I know what I ran."
- The install-decision rule the marketplace does not ship: throwaway profile, ephemeral workspace, deny-by-default filesystem and egress, pinned transitive tree, verified sandbox enforcement, no real credentials, and OS-level isolation around the whole thing. The safe path will never be the default path, because the default optimizes for adoption and you have to optimize for blast radius.
- This is the MCP stance applied to a bigger surface. MCP servers are untrusted code you run on your machine; an npm agent runtime is that same fact for the sandbox, the storage, and the loop as well, all installed the same way, all peers.
Was this useful?
Quick, anonymous, no strings.


