
Cloud Vulnerability DB
A community-led vulnerabilities database
The never-allowed rules in the shipped classifier are regular expressions matched against literal command text. Two consequences follow, and both are observable by running the published npm artifact's own classifyCommand() and PolicyEngine:
rm rule does not tolerate flag reordering, separated flags, or long-flag spellings. The shipped pattern is /rm\s+-rf?\s+\/(\s|$)/, which matches only the exact spelling rm -rf / (or rm -r /). The same operation spelled any other way is not covered.safe, and safe is the one class that the ask-destructive approval policy never prompts on.
The result is that under the shipped default approval policy, a set of destructive commands executes with no approval prompt at all, while the equivalent command in the canonical spelling is refused outright.
This is not a new class for this project: the maintainers' own published advisory record documents this same class repeatedly (for example GHSA-qvx5-rxrj-9vfh, Critical, CVSS 9.9, and GHSA-cm4c-4q74-mgmj). This report is one more instance of it, found by executing the current release.Environment: ssh-mcp@2.20.1 from the public npm registry, Node 22. Profile: role = "operator", group = "prod", approvalPolicy = "ask-destructive" (the shipped default). No SSH connection is needed — the shipped build/policy/classifier.js and build/policy/engine.js decide locally.
import { classifyCommand } from './build/policy/classifier.js';
import { PolicyEngine } from './build/policy/engine.js';
const engine = new PolicyEngine();
const profile = {
name: 'p', role: 'operator', hostGroup: 'prod', group: 'prod',
approvalPolicy: 'ask-destructive',
};
for (const cmd of [
'rm -rf /', 'rm -fr /', 'rm -r -f /', 'rm --recursive --force /', 'rm -R -f /',
'mkfs.ext4 /dev/sda1', 'mkfs -t ext4 /dev/sda1',
]) {
const r = engine.evaluate(cmd, profile);
console.log(cmd.padEnd(28), r.commandClass.padEnd(12), r.decision, r.ruleId);
}Observed output (2.20.1; identical on 2.20.0, whose build/policy/ is byte-for-byte the same):
rm -rf / destructive deny denylist
rm -fr / safe allow default
rm -r -f / safe allow default
rm --recursive --force / safe allow default
rm -R -f / safe allow default
mkfs.ext4 /dev/sda1 destructive deny denylist
mkfs -t ext4 /dev/sda1 safe allow defaultThe allow / default rows are the finding: allow with ruleId: "default" means the command passed every gate — the denylist did not match, the role permitted its class, and the approval policy did not consider it approval-worthy. ask-destructive prompts only on destructive and privileged, so a safe classification is silent.
Switching only the approval policy to ask-all returns require-approval for every one of the respelled rows, because ask-all gates every class. That is the mitigation, and it is the same mitigation the project's own SECURITY.md already recommends for any profile that matters.
Observed on ssh-mcp@2.20.1 and ssh-mcp@2.20.0. The /rm\s+-rf?\s+\/(\s|$)/ never-allowed pattern is byte-identical in 2.19.2 and 2.15.0 as well, and ask-destructive has shipped as the default approvalMode throughout, so the affected range is at least < 2.20.2 — I did not bisect further back, and am leaving the lower bound to the maintainer.
Under the default policy, an agent that proposes one of these spellings executes it on the target host with no prompt, so the operator's approval veto is never invoked for an operation the tool's own never-allowed list exists to prevent. The blast radius is bounded by the privileges of the profile's remote account, which is the same reason the project has said in its threat model that this class of gap "is not fixable by classifying harder" — a bypassed classifier only yields what the account can already do. Two further observations that I believe are more consequential than the spelling itself, and which I would ask the maintainer to weigh:
safe class is broader than the name suggests. Executing classifyCommand() shows systemctl stop nginx, systemctl restart nginx, apt-get install -y nginx, docker restart myapp, rm -f /etc/passwd, useradd evil, usermod -aG sudo evil, chmod 777 /etc/shadow, and a NOPASSWD:ALL sudoers append all classify as safe rather than destructive. Under ask-destructive these are also silent. This is documented in SECURITY.md as intended behaviour ("ask-destructive ... does not buy one on writes, deletions spelled another way, service control, or signals"), so by the project's own stated standard in .oss-scanner/threat_model.md — "behaviour matching that description is not a finding; behaviour that is worse than it describes is" — the service-control and privilege-escalation rows are the ones that are worse than described, because the README presents rm -rf / and mkfs as never allowed "whatever the role or approval policy", which a reader would reasonably take to cover the operation rather than one spelling.rm -R -f / (uppercase R) and rm -Rf / are also uncovered, and -R is a valid recursive flag for rm.rm rule tolerant of flag order, separated flags, long-flag spellings, and the uppercase -R form — i.e. match the operation rather than one spelling of it.destructive even when the spelling is not recognised, so that ask-destructive prompts rather than staying silent. Failing that, the pattern-matching gap is a strong additional argument for ask-all on any profile that points at a machine that matters — which the documentation already says.useradd, usermod -aG sudo, writes to /etc/sudoers, chmod 777 /etc/shadow) as privileged rather than safe, so elevation is gated under every approval policy rather than only under ask-all.
I am happy to share the exact reproduction script, and to test a fix against it once one is available. I have not published anything about this publicly, and will not do so before the 90-day window in SECURITY.md expires or a fixed release is published, whichever comes first. Please credit me as you prefer.
One note on the class of this report: I am not claiming a CVE identifier, and I have not filed this anywhere other than this private advisory.Source: NVD
Free Vulnerability Assessment
Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.
Get a personalized demo
"Best User Experience I have ever seen, provides full visibility to cloud workloads."
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
"We know that if Wiz identifies something as critical, it actually is."