Space Duck

Security checks across malware telemetry and agentic risk

Overview

This appears to be a real Space Duck client, but it enables persistent listeners, workspace file access, auto-replies, and remotely triggered shell actions that users should review carefully before installing.

Install only if you intend to give Space Duck a persistent local bridge with access to your agent workspace and Beak Key. Bind listeners behind trusted TLS/firewalling, avoid exposing the unauthenticated peck listener publicly, disable or restrict auto-reply and remembered approvals unless needed, and review any Telegram or Mission Control action before allowing shell execution.

SkillSpector

By NVIDIA
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (41)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if on_peck_cmd:
        try:
            argv = shlex.split(on_peck_cmd)
            subprocess.Popen(
                argv,
                stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
Confidence
91% confidence
Finding
subprocess.Popen( argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).communicate(input=json.dumps(ev

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# argv is parsed with shlex; subprocess runs without shell
                # interpretation. Gated upstream by --allow-shell-hook.
                argv = shlex.split(self.on_peck_cmd)
                subprocess.Popen(
                    argv,
                    stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
Confidence
92% confidence
Finding
subprocess.Popen( argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).communicate

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = ['claude', '--print', '--permission-mode', 'bypassPermissions',
           '--model', DEFAULT_MODEL]
    try:
        out = subprocess.run(cmd, input=prompt, capture_output=True,
                             text=True, timeout=DEFAULT_TIMEOUT)
        if out.returncode != 0:
            # 0.4.0 — surface BOTH stderr and stdout (claude CLI sometimes
Confidence
95% confidence
Finding
out = subprocess.run(cmd, input=prompt, capture_output=True, text=True, timeout=DEFAULT_TIMEOUT)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
'--message', handoff_text,
           '--purpose', 'handoff']
    try:
        out = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
        if out.returncode != 0:
            _log(f'handoff send_peck exit {out.returncode}: {out.stderr[:300]}')
            return False
Confidence
96% confidence
Finding
out = subprocess.run(cmd, capture_output=True, text=True, timeout=30)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
done downstream by the callback handler so the full text is
    available for upload to /beak/byob/workspace/action-log."""
    try:
        proc = subprocess.run(
            pending['bash'], shell=True, executable='/bin/bash',
            capture_output=True, timeout=60)
        out = (proc.stdout or b'').decode('utf-8', errors='replace')
Confidence
95% confidence
Finding
proc = subprocess.run( pending['bash'], shell=True, executable='/bin/bash', capture_output=True, timeout=60)

Tainted flow: 'req' from os.environ.get (line 167, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={'Content-Type': 'application/json'},
    )
    try:
        with urllib.request.urlopen(req, timeout=8) as r:
            return (True, 'sent') if 200 <= r.status < 300 else (False, f'http_{r.status}')
    except urllib.error.HTTPError as e:
        snippet = e.read()[:120].decode('utf-8', 'ignore')
Confidence
83% confidence
Finding
with urllib.request.urlopen(req, timeout=8) as r:

Tainted flow: 'req' from os.environ.get (line 167, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
url, method='GET',
                headers={'X-Beak-Key': beak_key, 'X-Spaceduck-ID': sdid},
            )
            with urllib.request.urlopen(req, timeout=10) as r:
                data = json.loads(r.read())
            content = data.get('content', '')
            (files_dir / fn).write_text(content)
Confidence
84% confidence
Finding
with urllib.request.urlopen(req, timeout=10) as r:

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

Medium
Category
Data Flow
Content
line = f'[{time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}] {msg}\n'
    try:
        LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
        with open(LOG_PATH, 'a') as f:
            f.write(line)
    except Exception:
        pass
Confidence
87% confidence
Finding
with open(LOG_PATH, 'a') as f:

Tainted flow: 'req' from os.environ.get (line 927, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
try:
        url = f'{api_base.rstrip("/")}/beak/duck/{sd_id}/connections'
        req = urllib.request.Request(url, headers={'X-Beak-Key': beak_key})
        with urllib.request.urlopen(req, timeout=10) as r:
            data = json.loads(r.read())
        conns = data.get('connections') or []
        lines = ['# Your Network (auto-synced from Spaceduckling)',
Confidence
90% confidence
Finding
with urllib.request.urlopen(req, timeout=10) as r:

Tainted flow: 'req' from os.environ.get (line 927, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={'Content-Type': 'application/json',
                     'Authorization': f'Bearer {beak_key}'},
            method='POST')
        with urllib.request.urlopen(req, timeout=10) as r:
            return r.status == 200
    except Exception:
        return False
Confidence
92% confidence
Finding
with urllib.request.urlopen(req, timeout=10) as r:

Tainted flow: 'req' from os.environ.get (line 927, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
'Authorization': f'Bearer {bk}'},
                method='POST',
            )
            with urllib.request.urlopen(req, timeout=10) as r:
                print(f'# reported: HTTP {r.status}', file=sys.stderr)
        except Exception as e:
            print(f'# warn: report failed: {e}', file=sys.stderr)
Confidence
89% confidence
Finding
with urllib.request.urlopen(req, timeout=10) as r:

Tainted flow: 'req' from os.environ.get (line 927, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={'Content-Type': 'application/json',
                         'Authorization': f'Bearer {beak_key}'},
                method='POST')
            urllib.request.urlopen(req, timeout=5).close()
        except Exception:
            pass  # silent — platform-side state still advances on next tick
        time.sleep(INTERVAL)
Confidence
90% confidence
Finding
urllib.request.urlopen(req, timeout=5).close()

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The skill explicitly tells the agent to execute bash delivered via Telegram immediately and without confirmation, even though it tries to narrow scope to a supposed server-side allowlist. That is still unsafe because the agent is being instructed to trust message content from an external channel and perform code execution based on natural-language policy rather than a locally enforced, cryptographically bound command schema.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The script explicitly claims its report is safe to share publicly, but it prints process command lines and log tails that can contain secrets such as tokens, API keys, webhook URLs, usernames, file paths, or sensitive message content. That mismatch increases the chance of accidental disclosure because users may paste the output into tickets or chats without reviewing it.

Intent-Code Divergence

High
Confidence
96% confidence
Finding
The docstring states the status mode performs a GET and only contacts the Space Duck backend, but the implementation actually performs a POST that includes the beak_key credential. This mismatch is dangerous because operators or downstream tools may treat --status as a harmless read-only action when it in fact transmits sensitive authentication material over the network.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The code sends the status POST to cfg.get("api_base", API_BASE), allowing a locally controlled config value to override the documented trusted backend and redirect requests to any host. Because the payload includes spaceduck_id and beak_key, this creates an SSRF/exfiltration path where a malicious or tampered config can leak credentials to an attacker-controlled server.

Intent-Code Divergence

Medium
Confidence
88% confidence
Finding
The module claims to eliminate chat-pasted secrets, but it explicitly accepts a Telegram bot token on the command line and persists it to disk. Command-line secrets are commonly exposed via shell history, process listings, agent logs, and orchestration telemetry, so this undermines the stated security model and can leak a long-lived credential.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The critic is intended to catch unsafe drafts, yet every failure mode of the local model invocation defaults to `PASS`. An attacker who can induce timeout, missing binary, nonzero exit, or malformed output can bypass the safety gate entirely, causing unsafe responses to be sent without review.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The file explicitly states there is no inbound HMAC, and the handler processes any POST /peck request without verifying the claimed event header or any shared secret. On the default 0.0.0.0 bind, this allows anyone who can reach the port to inject fake pecks, trigger forwarding, write files, and activate the optional local hook.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The design explicitly allows the responder to initiate new conversations with third parties via `<handoff .../>`, which expands capability far beyond replying to the original sender. In a system driven by untrusted inbound messages and model output, this increases blast radius by enabling unsolicited outreach and cross-party information propagation.

Intent-Code Divergence

Medium
Confidence
90% confidence
Finding
The module docstring claims the script can register a webhook, but the implementation itself states that the backend ignores the webhook field and only updates presence. This mismatch can mislead operators into believing inbound message delivery is configured when it is not, creating a security and operational blind spot around authentication, exposure, and expected network behavior.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The script sources a state file from /tmp, which executes arbitrary shell code in the current process. Because /tmp is commonly writable by other local users/processes and the filename is predictable from attacker-influenced input, an attacker who can create or modify that file can achieve arbitrary command execution when teardown runs.

Vague Triggers

Medium
Confidence
86% confidence
Finding
The trigger list is very broad and includes common phrases such as navigation, setup, and messaging requests, which increases the chance that the skill activates in contexts the user did not intend. In an agentic environment, overbroad activation can route benign conversation into a skill that performs networked actions, credential handling, or message forwarding.

Missing User Warnings

High
Confidence
98% confidence
Finding
The skill instructs the agent to execute Telegram-delivered bash immediately and explicitly says not to ask for confirmation. This removes an essential safety checkpoint at the exact moment untrusted external input is being translated into privileged local execution.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The API reference explicitly documents storage of long-lived credential material (`beak_key`, `spaceduck_id`, `duckling_id`) in a predictable local file path under the user's home directory without any warning about file permissions, encryption, or secret-handling practices. If that file is readable by other local users, included in backups, committed to source control, or exposed by malware, an attacker could use the `beak_key` to impersonate the agent and access or send authenticated API requests.

VirusTotal

64/64 vendors flagged this skill as clean.

View on VirusTotal