Back to skill

Security audit

Snapshot-Wipe Resilience

Security checks across malware telemetry and agentic risk

Overview

This recovery skill is mostly transparent about its purpose, but it gives agents broad authority to run repair shell commands, modify credentials/files, and sync manifests off-box, so it needs review before installation.

Install only if you intentionally want an agent-run workspace repair system. Review and sign manifests yourself, use `--dry-run` before restore/doctor, avoid broad or destructive recipes where possible, do not trust pulled manifests without verifying signatures/fingerprints, and treat any manifest containing restore commands or credential placeholders as sensitive.

SkillSpector

By NVIDIA
Vulnerability Patterns
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (26)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_smoke(cmd, cwd, timeout=60):
    try:
        r = subprocess.run(["bash", "-lc", cmd], cwd=cwd, timeout=timeout,
                           stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return r.returncode == 0
    except Exception:
Confidence
99% confidence
Finding
r = subprocess.run(["bash", "-lc", cmd], cwd=cwd, timeout=timeout, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def check_ldd(path):
    try:
        r = subprocess.run(["ldd", path], capture_output=True, text=True, timeout=30)
        return [l.split("=>")[0].strip() for l in r.stdout.splitlines()
                if "not found" in l]
    except Exception:
Confidence
83% confidence
Finding
r = subprocess.run(["ldd", path], capture_output=True, text=True, timeout=30)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
t0 = time.time()
    tmo = timeout or e.get("timeout_s", 1800)
    try:
        r = subprocess.run(["bash", "-lc", cmd], cwd=man.get("workspace", HOME),
                           timeout=tmo, start_new_session=True)
        rc = r.returncode
    except subprocess.TimeoutExpired:
Confidence
96% confidence
Finding
r = subprocess.run(["bash", "-lc", cmd], cwd=man.get("workspace", HOME), timeout=tmo, start_new_session=True)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def _run():
            try:
                return subprocess.run(
                    ["bash", "-lc", e["restore"]], cwd=man.get("workspace", HOME),
                    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                    timeout=e.get("timeout_s", 300),
Confidence
95% confidence
Finding
return subprocess.run( ["bash", "-lc", e["restore"]], cwd=man.get("workspace", HOME), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,

Tainted flow: 'cmd' from open (line 759, file read) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
t0 = time.time()
    tmo = timeout or e.get("timeout_s", 1800)
    try:
        r = subprocess.run(["bash", "-lc", cmd], cwd=man.get("workspace", HOME),
                           timeout=tmo, start_new_session=True)
        rc = r.returncode
    except subprocess.TimeoutExpired:
Confidence
98% confidence
Finding
r = subprocess.run(["bash", "-lc", cmd], cwd=man.get("workspace", HOME), timeout=tmo, start_new_session=True)

Tainted flow: 'tmp' from open (line 775, file read) → open (file write)

Medium
Category
Data Flow
Content
data = escrow_restore(e)          # validate BEFORE touching disk
            os.makedirs(os.path.dirname(p) or ".", exist_ok=True)
            tmp = p + ".swr-tmp"
            with open(tmp, "wb") as f:
                f.write(data)
            if e.get("mode"):
                os.chmod(tmp, int(e["mode"], 8))
Confidence
88% confidence
Finding
with open(tmp, "wb") as f:

Tainted flow: 'e' from open (line 612, file read) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
def _run():
            try:
                return subprocess.run(
                    ["bash", "-lc", e["restore"]], cwd=man.get("workspace", HOME),
                    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                    timeout=e.get("timeout_s", 300),
Confidence
95% confidence
Finding
return subprocess.run( ["bash", "-lc", e["restore"]], cwd=man.get("workspace", HOME), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,

Tainted flow: 'PEERS_DIR' from os.environ.get (line 591, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
die(f"FINGERPRINT MISMATCH: file says {peer['fingerprint']}, computed {fp}")
    peer["fingerprint"] = fp
    name = a.name or peer.get("name") or fp[:11]
    with open(os.path.join(PEERS_DIR, f"{name}.json"), "w") as f:
        json.dump(peer, f, indent=2)
    print(f"added peer '{name}'\n  fingerprint {fp}")
    print("  VERIFY this fingerprint with the peer over a second channel "
Confidence
90% confidence
Finding
with open(os.path.join(PEERS_DIR, f"{name}.json"), "w") as f:

Tainted flow: 'o' from open (line 284, file read) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
if not shutil.which("age"):
            raise RuntimeError("payload is age-encrypted but `age` is not installed")
        cmd = ["age", "-d"] + (["-i", identity] if identity else [])
        r = subprocess.run(cmd, input=o["body"].encode(),
                           capture_output=True, timeout=60)
        if r.returncode:
            raise RuntimeError(f"age decrypt failed: {r.stderr.decode()[:200]}")
Confidence
67% confidence
Finding
r = subprocess.run(cmd, input=o["body"].encode(), capture_output=True, timeout=60)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill advertises and documents capabilities to read environment variables, modify files, execute shell commands, and access the network, yet it declares no permissions metadata. That mismatch prevents callers and policy engines from applying least-privilege controls and is especially risky here because the documented behavior includes destructive restore actions and off-box uploads.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The skill metadata promises off-box encrypted sync to pastebin and secret redaction, but this file does not implement those guarantees and only references an external push workflow plus an unauthenticated HEAD request. This security-model mismatch is dangerous because users may rely on confidentiality or survivability properties that are not actually enforced here.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The manifest and `why --json` output expose restore commands verbatim, which can include secrets such as tokens, credentials, or private URLs. Because the skill description claims such secrets are redacted before upload, this discrepancy can cause accidental secret disclosure if users export or sync manifests assuming safe redaction.

Vague Triggers

Medium
Confidence
83% confidence
Finding
The invocation guidance is extremely broad ('run doctor as the first command of every turn' and use for missing files, credentials, builds, models, etc.), which increases the chance the skill will be auto-triggered in contexts where it should not run. In this skill, auto-triggering is dangerous because it can execute restore recipes, touch credentials, upload manifests off-box, and perform destructive filesystem operations.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The restore recipe deletes the existing model file before completing a fresh download, creating a destructive failure mode if the network fetch fails or the upstream artifact is unavailable. In a workspace-recovery skill, destructive replacement is especially risky because the tool may be invoked precisely when state is already fragile, turning partial corruption into total data loss.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The restore command recursively removes the entire source tree and reclones it without any user-facing warning or safety check. If the path is wrong, locally modified code exists, or the clone fails, the action can irreversibly destroy user work under the guise of repair.

Missing User Warnings

Medium
Confidence
81% confidence
Finding
The tool records telemetry history by default without an explicit consent prompt or conspicuous warning in the code path. In an agent sandbox, even local history files can capture workspace state over time and may surprise users who do not expect persistent operational logging.

Unvalidated Output Injection

High
Category
Output Handling
Content
ident = load_identity()
    if a.qr and shutil.which("qrencode"):
        blob = base64.b64encode(json.dumps(ident).encode()).decode()
        print(subprocess.run(["qrencode", "-t", "ANSIUTF8", blob],
                             capture_output=True, text=True).stdout)
    print(json.dumps(ident, indent=2))
    eprint(f"\nfingerprint: {ident['fingerprint']}  "
Confidence
88% confidence
Finding
subprocess.run(["qrencode", "-t", "ANSIUTF8", blob], capture_output

Unvalidated Output Injection

High
Category
Output Handling
Content
def qr(data):
    if shutil.which("qrencode"):
        try:
            return subprocess.run(["qrencode", "-t", "ANSIUTF8", data],
                                  capture_output=True, text=True,
                                  timeout=15).stdout
        except Exception:
Confidence
76% confidence
Finding
subprocess.run(["qrencode", "-t", "ANSIUTF8", data], capture_output

Session Persistence

Medium
Category
Rogue Agent
Content
"restore": "chmod +x ~/run_swarm.sh"
    },
    {
      "_kind_note": "tier 0 secret. The restore recipe re-authenticates, it does not just rewrite the file.",
      "id": "clawhub-token",
      "path": ".clawhub/TOKEN",
      "kind": "file",
Confidence
88% confidence
Finding
write the file.", "id": "clawhub-token", "path": ".clawhub/TOKEN", "kind": "file", "mode": "0600", "tier": 0, "restore": "mkdir -p ~/.clawhub && printf '%s\\n' \"$C

Tool Parameter Abuse

High
Category
Tool Misuse
Content
# tier 2 — source, sentineled on the dir that silently empties
swr add llama.cpp --id llama-src --kind tree --tier 2 \
  --sentinel ggml/src/ggml.c --sentinel ggml/cmake/ggml-config.cmake.in \
  --restore 'rm -rf ~/llama.cpp && git clone --depth 1 -q https://github.com/ggerganov/llama.cpp.git ~/llama.cpp'

# tier 3 — models verified by exact byte count, resumable fetch
swr add model.gguf --kind blob --tier 3 \
Confidence
91% confidence
Finding
rm -rf ~

Tool Parameter Abuse

High
Category
Tool Misuse
Content
# tier 2 — source, sentineled on the dir that silently empties
swr add llama.cpp --id llama-src --kind tree --tier 2 \
  --sentinel ggml/src/ggml.c --sentinel ggml/cmake/ggml-config.cmake.in \
  --restore 'rm -rf ~/llama.cpp && git clone --depth 1 -q https://github.com/ggerganov/llama.cpp.git ~/llama.cpp'

# tier 3 — models verified by exact byte count, resumable fetch
swr add model.gguf --kind blob --tier 3 \
Confidence
91% confidence
Finding
rm -rf ~/llama.cpp && git clone --depth 1 -q https://github.com/ggerganov/

Chaining Abuse

High
Category
Tool Misuse
Content
# tier 3 — models verified by exact byte count, resumable fetch
swr add model.gguf --kind blob --tier 3 \
  --restore "cd ~ && rm -f model.gguf && curl -sSL --retry 3 --retry-all-errors -C - -o model.gguf '$URL'"

# tier 4 — binaries, only after source is proven healthy
swr add llama.cpp/build/bin --id llama-bins --kind tree --tier 4 \
Confidence
88% confidence
Finding
&& rm -

Tool Parameter Abuse

High
Category
Tool Misuse
Content
"upstream": "https://huggingface.co/bartowski/Qwen_Qwen3-0.6B-GGUF/resolve/main/Qwen_Qwen3-0.6B-Q4_K_M.gguf",
      "tier": 3,
      "fragile": "large blob (484 MB) - likely exceeds snapshot size cap",
      "restore": "cd ~ && rm -f Qwen3-0.6B-Q4_K_M.gguf && curl -sSL --retry 3 --retry-all-errors -C - -o Qwen3-0.6B-Q4_K_M.gguf 'https://huggingface.co/bartowski/Qwen_Qwen3-0.6B-GGUF/resolve/main/Qwen_Qwen3-0.6B-Q4_K_M.gguf'"
    },
    {
      "_kind_note": "tree + sentinels = THE important one. Catches a directory that survives while its contents are emptied. Sentinels are only a SAMPLE, so pin files from risky subdirs (note ggml/src below) and set a tight min_files, or use `merkle` for exact detection.",
Confidence
87% confidence
Finding
rm -f Qwen3-0.6B-Q4_K_M.gguf && curl -sSL --retry 3 --retry-all-errors -C - -o Qwen3-0.6B-Q4_K_M.gguf 'https://huggingface.co/bartowski/Qwen_Qwen3-0.6B-GGUF/resolve/main/Qwen_Qwen3-0.6B-Q4_K_M.gguf'"

Tool Parameter Abuse

High
Category
Tool Misuse
Content
"min_files": 4986,
      "tier": 2,
      "fragile": "subdir 'src' can be swept by exclusion rules, leaving an empty shell",
      "restore": "rm -rf ~/llama.cpp && git clone --depth 1 -q https://github.com/ggerganov/llama.cpp.git ~/llama.cpp"
    },
    {
      "_kind_note": "tier 4 build output; 'build' is on every exclusion list. `needs` means a failed llama-src SKIPS this instead of running a doomed 6-min build.",
Confidence
90% confidence
Finding
rm -rf ~/llama.cpp && git clone --depth 1 -q https://github.com/ggerganov/

Chaining Abuse

High
Category
Tool Misuse
Content
"upstream": "https://huggingface.co/bartowski/Qwen_Qwen3-0.6B-GGUF/resolve/main/Qwen_Qwen3-0.6B-Q4_K_M.gguf",
      "tier": 3,
      "fragile": "large blob (484 MB) - likely exceeds snapshot size cap",
      "restore": "cd ~ && rm -f Qwen3-0.6B-Q4_K_M.gguf && curl -sSL --retry 3 --retry-all-errors -C - -o Qwen3-0.6B-Q4_K_M.gguf 'https://huggingface.co/bartowski/Qwen_Qwen3-0.6B-GGUF/resolve/main/Qwen_Qwen3-0.6B-Q4_K_M.gguf'"
    },
    {
      "_kind_note": "tree + sentinels = THE important one. Catches a directory that survives while its contents are emptied. Sentinels are only a SAMPLE, so pin files from risky subdirs (note ggml/src below) and set a tight min_files, or use `merkle` for exact detection.",
Confidence
84% confidence
Finding
&& rm -

VirusTotal

VirusTotal findings are pending for this skill version.

View on VirusTotal

Static analysis

No suspicious patterns detected.