Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🦀
🦀 crates.io
Not in CISA KEV
CRITICAL severity

GHSA-5wp8-q9mx-8jx8 zeptoclaw

CRITICALFix: qhkm/zeptoclaw@68916c3

GHSA-5wp8-q9mx-8jx8 is a critical-severity (CVSS 10) remote code execution vulnerability in zeptoclaw. A fix is available for zeptoclaw — see the affected versions and patch details below.

zeptoclaw has Shell allowlist-blocklist bypass via command/argument injection and file name wildcards

Published
Mar 5, 2026
Updated
Mar 5, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Mar 5, 2026 · OSV.dev, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected
🦀zeptoclaw

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects crates.io packages — download data is not available via public APIs for these ecosystems.

Description

Summary

zeptoclaw implements a allowlist combined with a blocklist to prevent malicious shell commands in src/security/shell.rs. However, even in the Strict mode, attackers can completely bypass all the guards from allowlist and blocklist:

  • to bypass the allowlist, command injection is enough, such as ;, $() etc.
  • to bypass the REGEX_BLOCKED_PATTERNS, argument injection is enough, such as the python3 -P -c "..."
  • to bypass the LITERAL_BLOCKED_PATTERNS, file name wildcards can do the work, such as cat /etc/pass[w]d

Details

In code src/security/shell.rs#L218-L243, one can see the allowlist only checks the first token and thus makes command injection possible.

        // Allowlist check (runs after blocklist)
        if self.allowlist_mode != ShellAllowlistMode::Off && !self.allowlist.is_empty() {
            let first_token = command
                .split_whitespace()
                .next()
                .unwrap_or("")
                .to_lowercase();
            // Strip path prefix (e.g. /usr/bin/git -> git)
            let executable = first_token.rsplit('/').next().unwrap_or(&first_token);
            if !self.allowlist.iter().any(|a| a == executable) {
                match self.allowlist_mode {
                    ShellAllowlistMode::Strict => {
                        return Err(ZeptoError::SecurityViolation(format!(
                            "Command '{}' not in allowlist",
                            executable
                        )));
                    }
                    ShellAllowlistMode::Warn => {
                        tracing::warn!(
                            command = %command,
                            executable = %executable,
                            "Command not in allowlist"
                        );
                    }
                    ShellAllowlistMode::Off => {} // unreachable
                }

!self.allowlist.is_empty() makes the empty allowlist overlook the allowlist check, if it is in ShellAllowlistMode::Strict mode, empty allowlist should direct reject all the commands.

As the code in src/security/shell.rs#L18-L70, we can find the REGEX_BLOCKED_PATTERNS only apply \s+ in between the command and arguments, making argument injection possible, and the LITERAL_BLOCKED_PATTERNS just uses specific file name, totally overlooking the file name wildcards:

const REGEX_BLOCKED_PATTERNS: &[&str] = &[
    // Piped shell execution (curl/wget to sh/bash)
    r"curl\s+.*\|\s*(sh|bash|zsh)",
    r"wget\s+.*\|\s*(sh|bash|zsh)",
    r"\|\s*(sh|bash|zsh)\s*$",
    // Reverse shells
    r"bash\s+-i\s+>&\s*/dev/tcp",
    r"nc\s+.*-e\s+(sh|bash|/bin)",
    r"/dev/tcp/",
    r"/dev/udp/",
    // Destructive root operations (various flag orderings)
    r"rm\s+(-[rf]{1,2}\s+)*(-[rf]{1,2}\s+)*/\s*($|;|\||&)",
    r"rm\s+(-[rf]{1,2}\s+)*(-[rf]{1,2}\s+)*/\*\s*($|;|\||&)",
    // Format/overwrite disk
    r"mkfs(\.[a-z0-9]+)?\s",
    r"dd\s+.*if=/dev/(zero|random|urandom).*of=/dev/[sh]d",
    r">\s*/dev/[sh]d[a-z]",
    // System-wide permission changes
    r"chmod\s+(-R\s+)?777\s+/\s*$",
    r"chmod\s+(-R\s+)?777\s+/[a-z]",
    // Fork bombs
    r":\(\)\s*\{\s*:\|:&\s*\}\s*;:",
    r"fork\s*\(\s*\)",
    // Encoded/indirect execution (common blocklist bypasses)
    r"base64\s+(-d|--decode)",
    r"python[23]?\s+-c\s+",
    r"perl\s+-e\s+",
    r"ruby\s+-e\s+",
    r"node\s+-e\s+",
    r"\beval\s+",
    r"xargs\s+.*sh\b",
    r"xargs\s+.*bash\b",
    // Environment variable exfiltration
    r"\benv\b.*>\s*/",
    r"\bprintenv\b.*>\s*/",
];

/// Literal substring patterns (credentials, sensitive paths)
const LITERAL_BLOCKED_PATTERNS: &[&str] = &[
    "/etc/shadow",
    "/etc/passwd",
    "~/.ssh/",
    ".ssh/id_rsa",
    ".ssh/id_ed25519",
    ".ssh/id_ecdsa",
    ".ssh/id_dsa",
    ".ssh/authorized_keys",
    ".aws/credentials",
    ".kube/config",
    // ZeptoClaw's own config (contains API keys and channel tokens)
    ".zeptoclaw/config.json",
    ".zeptoclaw/config.yaml",
];

PoC

    #[test]
    fn test_allowlist_bypass() {
        let config =
            ShellSecurityConfig::new().with_allowlist(vec!["git"], ShellAllowlistMode::Strict);
        assert!(config.validate_command("/usr/bin/git status; python -P -c 'import os; os.system(\"rm -rf /\")'; cat /etc/pass[w]d").is_ok());
    }

Impact

Unauthorized command execution.

Credit

@zpbrent

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🦀crates.iozeptoclawall versions0.6.2cargo update -p zeptoclaw --precise 0.6.2

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for zeptoclaw, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update zeptoclaw to 0.6.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-5wp8-q9mx-8jx8 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-5wp8-q9mx-8jx8 can be triaged on real exposure rather than presence alone.

Tailored to GHSA-5wp8-q9mx-8jx8. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### Summary [zeptoclaw](https://github.com/qhkm/zeptoclaw) implements a allowlist combined with a blocklist to prevent malicious shell commands in [src/security/shell.rs](https://github.com/qhkm/zeptoclaw/blob/v0.5.8/src/security/shell.rs). However, even in the `Strict` mode, attackers can completely bypass all the guards from allowlist and blocklist: - to bypass the `allowlist`, command injection is enough, such as `;`, `$()` etc. - to bypass the `REGEX_BLOCKED_PATTERNS`, argument injection is enough, such as the `python3 -P -c "..."` - to bypass the `LITERAL_BLOCKED_PATTERNS`, file name wil
O3 Security · Impact-Aware SCA

Is GHSA-5wp8-q9mx-8jx8 in your dependencies?

O3 Security finds GHSA-5wp8-q9mx-8jx8 across crates.io dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

GHSA-5wp8-q9mx-8jx8: zeptoclaw (Critical 10) | O3 Security