Back to skill

Security audit

Sd Stage 0821

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent Space Duck networking purpose, but it includes broad local control paths that deserve manual review before installation.

Install only if you are comfortable giving Space Duck persistent local listeners, access to a Beak Key, and potential workspace Markdown synchronization. Prefer strict consent, avoid Run all/24-hour approvals, disable or avoid broad workspace snapshots unless needed, do not use pipe-to-shell bootstrap commands, and review any Telegram/Mission Control action before allowing it to run shell commands.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
Findings (6)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/telegram_listener.py:140
Finding

Platform-Signed Action Messages Can Execute Arbitrary Bash Without Per-Command Consent

Content
View full analysis
Remediation
View remediation

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/telegram_listener.py:431
Finding

Twenty-Four-Hour Approval Memory Authorizes Future Unseen Shell Payloads

Content
View full analysis
Remediation
View remediation

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/install_byob.sh:12
Finding

Installation Instructions Execute a Mutable Remote Script Without Integrity Verification

Content
View full analysis
| bash # Production deployment should pin the script's SHA256 in the curl # command and document the source at a known location. ``` ```bash # Usage (interactive — preferred): # export SD_OWNER_EMAIL='you@example.com' # for Cognito sign-in # bash <(curl -fsSL https://spaceduckling.com/install/byob.sh) ``` ### Technical Analysis Although the commands are contained in usage comments rather than automatically executed by the script, they are explicit operator instructions for installation. Process substitution downloads a mutable remote response and executes it immediately with Bash. HTTPS authenticates the current server endpoint but does not guarantee artifact immutability, code-signing provenance, or protection against an official server compromise. The comments acknowledge that production deployment should pin a SHA-256 digest, but the preferred usage example performs no verification. The installer subsequently installs packages, writes configuration, creates listeners, and configures bridge components. A substituted installer therefore has a broad opportunity to compromise the account or host. ### Attack Path 1. An attacker compromises the hosted installer, its deployment pipeline, DNS/TLS trust path, or upstream server. 2. The operator follows the documented `bash <(curl ...)` command. 3. Bash executes the attacker-controlled response before the operator can inspect it. 4. The payload runs with the invoking user’s privileges and can imitate the expected installer output. 5. If the operator invokes the installer with elevated privileges, the impact increases accordingly. ### ...[truncated 388 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/workspace_bridge.py:527
Finding

Workspace Bridge Recursively Uploads All Markdown Files by Default

Content
View full analysis
bool: import base64 url = f'{api_base.rstrip("/")}/beak/byob/workspace/snapshot-file' payload = { 'filename': filename, 'content_b64': base64.b64encode(content_bytes).decode('ascii'), 'mtime': mtime, } try: req = urllib.request.Request( url, data=json.dumps(payload).encode(), 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 ``` ```python # scripts/workspace_bridge.py:557-579 def _push_snapshot_all(beak_key: str, sd_id: str, api_base: str, workspace_dir: pathlib.Path, verbose: bool = False) -> None: if not workspace_dir.exists(): return pushed = 0 skipped = 0 for p in workspace_dir.rglob('*.md'): try: fn = p.name if '/' in fn or '..' in fn or fn.startswith('.'): skipped += 1; continue content = p.read_bytes() if len(content) > 5 * 1024 * 1024: skipped += 1; continue ok = _push_snapshot_one( beak_key, sd_id, api_base, fn, content, int(p.stat().st_mtime)) if ok: pushed += 1 else: skipped += 1 except Exception: skipped += 1 ``` ```python # scripts/workspace_bridge.py ...[truncated 2729 chars]
Remediation
View remediation

T02 · Agent Memory Poisoning

Error
Location
scripts/telegram_listener.py:903
Finding

Remote Command Output Is Persisted into Agent Memory

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/telegram_listener.py:497
Finding

Potentially Sensitive Command Output Is Automatically Uploaded to the Backend

Content
View full analysis
1600: viewer_url = _upload_action_log( beak_key, sd_id, DEFAULT_API_BASE, action_id, action_kind, out, rc) ``` ```python # scripts/telegram_listener.py:744-750 rc, output = _exec_pending(pending) _release_claim(pending) _write_memory_breadcrumb(pending['action_kind'], action_id, rc, output) viewer_url = '' if len(output) > 1600: viewer_url = _upload_action_log( ...[truncated 1812 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (338)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The script makes an authenticated outbound request to an API base taken from local configuration, sending the X-Beak-Key header. If config is tampered with or insufficiently validated, this can exfiltrate the API key to an attacker-controlled endpoint. The context increases risk because the request includes a credential and is part of an auto-diagnostic workflow likely to run unattended.

Content

Scanner excerpt · scripts/doctor_fix.py (reported line 156)May include surrounding context.

python
req = urllib.request.Request(
            f"{api}/beak/spaceducks?duckling_id={cfg.get('duckling_id', '')}",
            headers={'Accept': 'application/json', 'X-Beak-Key': cfg.get('beak_key', '')})
        with urllib.request.urlopen(req, timeout=10) as r:
            rows = json.loads(r.read())
        rows = rows.get('agents', rows.get('spaceducks', []))
        mine = next((s for s in rows if s.get('spaceduck_id') == cfg.get('spaceduck_id')), None)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/mcp_server.py (reported line 507)May include surrounding context.

python
headers={"Content-Type": "application/json",
                                              "Authorization": "Bearer " + (s or "")},
                                     data=b'{"jsonrpc":"2.0","id":1,"method":"ping"}')
        with urllib.request.urlopen(req, timeout=3) as r:
            up = r.status == 200
    except Exception:
        up = False

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · scripts/peck_listener.py (reported line 38)May include surrounding context.

python
This rail bypasses the per-duck bot
             entirely — outages there don't break local delivery.
  slack      Slack incoming-webhook URL. SPACEDUCK_FWD_SLACK_WEBHOOK env, or
             {"slack":{"webhook_url":"https://hooks.slack.com/…"}} in
             forward.json.
  discord    Discord webhook URL. SPACEDUCK_FWD_DISCORD_WEBHOOK env, or
             {"discord":{"webhook_url":"https://discord.com/api/webhooks/…"}}
             in forward.json.
  email      SMTP. SPACEDUCK_FWD_SMTP_HOST/PORT/USER/PASS + EMAIL_FROM/TO env,
             or {"email":{"smtp_host":"…","smtp_port":587,"smtp_user":"…",
             "smtp_pass":"…","from_addr":"…","to_addr":"…","use_tls":true}}
             in forward.json.

Lambda variant (drop-in handler) — sketch at the bottom of this file.

Usage:
  # DEFAULT onboarding path (0.4.19+) — zero tunnel/domain/webhook setup.
  # Polls the platform mailbox with adaptive cadence: fast (--interval, 3s)
  # while a peck session is active

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/peck_listener.py (reported line 231)May include surrounding context.

python
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)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/peck_listener.py (reported line 293)May include surrounding context.

python
headers={'Content-Type': 'application/x-www-form-urlencoded'},
    )
    try:
        with urllib.request.urlopen(req, timeout=8) as r:
            return (True, 'sent') if r.status == 200 else (False, f'http_{r.status}')
    except urllib.error.HTTPError as e:
        snippet = e.read()[:120].decode('utf-8', 'ignore')

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

_post_json will POST inbound peck content to arbitrary webhook URLs loaded from environment or config for Slack/Discord forwarding. This is intended functionality, but it creates an easy exfiltration channel for all received message content if configuration is malicious, mistaken, or compromised.

Content

Scanner excerpt · scripts/peck_listener.py (reported line 310)May include surrounding context.

python
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')

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/peck_listener.py (reported line 597)May include surrounding context.

python
def _http_get_json(url, headers, timeout=15):
    req = urllib.request.Request(url, method='GET', headers=headers)
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return r.status, json.loads(r.read()), dict(r.headers)
    except urllib.error.HTTPError as e:
        try:

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/peck_listener.py (reported line 615)May include surrounding context.

python
def _http_get_json(url, headers, timeout=15):
    req = urllib.request.Request(url, method='GET', headers=headers)
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return r.status, json.loads(r.read()), dict(r.headers)
    except urllib.error.HTTPError as e:
        try:

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

Critical
Category
Data Flow
Confidence
98% confidence
Finding

The script sends owner notifications directly to Telegram using bot credentials from config/environment, including a snippet of inbound peer content and the reason for suppression. This is an external transmission not disclosed in the stated call surface, and it can leak sensitive conversation data to a third-party service and to any party with access to that Telegram chat.

Content

Scanner excerpt · scripts/peck_responder.py (reported line 225)May include surrounding context.

python
req = urllib.request.Request(api, data=payload,
            headers={'Content-Type': 'application/json'}, method='POST')
        try:
            urllib.request.urlopen(req, timeout=8)
            _log(f'silent-skip TG notify ok: reason={reason!r} sender={sender!r}')
        except Exception as _se:
            _log(f'silent-skip TG notify failed: {_se}')

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/peck_responder.py (reported line 264)May include surrounding context.

python
url = f'{api}/beak/connection/permissions?sender_spaceduck_id={sender_sd}&target_spaceduck_id={my_sd}'
    req = urllib.request.Request(url, headers={'X-Beak-Key': beak_key})
    try:
        with urllib.request.urlopen(req, timeout=10) as r:
            data = json.loads(r.read())
    except urllib.error.HTTPError as e:
        return False, f'http_{e.code}', {}

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/workspace_bridge.py (reported line 480)May include surrounding context.

python
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)',

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/workspace_bridge.py (reported line 551)May include surrounding context.

python
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)',

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/workspace_bridge.py (reported line 998)May include surrounding context.

python
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)',

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/workspace_bridge.py (reported line 634)May include surrounding context.

python
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)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/workspace_bridge.py (reported line 933)May include surrounding context.

python
'X-Beak-Signature': sig,
                },
            )
            with urllib.request.urlopen(req, timeout=3) as r:
                _ = json.loads(r.read())
                state = 'up'
                detail['http_status'] = 200

YARA rule 'offensive_tool_references': References to well-known offensive security tools [hacktools]

High
Category
YARA Match
Confidence
70% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

Scanner excerpt · CHANGELOG.md (reported line 14)May include surrounding context.

md
Z and
  the fix landed at 13:50:19Z, five minutes late, so the guard shipped in no
  file. When the process's working directory is deleted out from under it,
  `os.getcwd()` raises and every auto-reply dies silently. `safe_cwd` /
  `ensure_cwd` guards are now present in all five affected entry points:
  `scripts/_claudebin.py`, `scripts/peck_critic.py`, `scripts/peck_listener.py`,
  `scripts/peck_responder.py`, `scripts/reply_with_claude.sh`.
- **`scripts/goal_store.py` — restored.** Dropped from 0.8.20, which silently
  no-op'd the D1 goal loop on every peck: `peck_responder.py` imports it under
  a try/except fallback, so the absence was invisible in logs.

No behaviour change beyond restoring the above. D5 anti-echo short-reply
handling from 0.8.20 is carried forward unchanged.

## 0.8.14 [BYOB-FILES]

Mission Control Files panel now reflects the live BYOB workspace, not a stale
S3 snapshot. Requires the paired Lambda + MC deploy (same release).

- **Recursive .md listing.** `work

Self-Modification

High
Category
Rogue Agent
Confidence
95% confidence
Finding

This duplicate finding points to the same self-update mechanism. Even with official-sender gating, automatic code replacement on a running host is a powerful primitive that should be treated as high risk because compromise of signing keys, registry, or update transport becomes full host compromise.

Content

Scanner excerpt · CHANGELOG.md (reported line 355)May include surrounding context.

md
- `[AUTOUP-075]` **Consent-based auto-update** (Josh, post-0.7.4 rollout: "if your skill worked, they would update on your command"). New `config.json` key `auto_update`:
  - `"ask"` (default) — unchanged behavior: version nudges + update pecks are notifications only; the owner runs `update.sh`.
  - `"auto"` — standing owner consent: the duck self-updates via its own `update.sh` when (a) the daily `version_check_daemon.sh` sees a genuine upgrade, or (b) a peck containing the `[SPACE-DUCK-UPDATE]` marker arrives. Result is reported to the owner via `/beak/tg/notify` (daemon path) and `~/.space-duck/logs/self_update.log`.
- Consent capture: `pair.py` now asks "Approve automatic skill updates from Spaceduckling? [y/N]" at pair time (TTY only; `SPACEDUCK_AUTO_UPDATE=auto|ask` env override for headless installs; default stays `ask`).
- Safety: trigger pecks are signals, never commands — the only action is `update.sh` (official registry latest, no-ops when current). Optional `update_senders` allowlist gates who can trigger; 1-hour debounce; updater spawned `start_new_session` so the listener bounce doesn't kill it. Lane A doctrine intact: consent lives only in the duck's local config; the platform never executes on the box.

Self-Modification

High
Category
Rogue Agent
Confidence
95% confidence
Finding

This duplicate finding points to the same self-update mechanism. Even with official-sender gating, automatic code replacement on a running host is a powerful primitive that should be treated as high risk because compromise of signing keys, registry, or update transport becomes full host compromise.

Content

Scanner excerpt · CHANGELOG.md (reported line 355)May include surrounding context.

md
- `[AUTOUP-075]` **Consent-based auto-update** (Josh, post-0.7.4 rollout: "if your skill worked, they would update on your command"). New `config.json` key `auto_update`:
  - `"ask"` (default) — unchanged behavior: version nudges + update pecks are notifications only; the owner runs `update.sh`.
  - `"auto"` — standing owner consent: the duck self-updates via its own `update.sh` when (a) the daily `version_check_daemon.sh` sees a genuine upgrade, or (b) a peck containing the `[SPACE-DUCK-UPDATE]` marker arrives. Result is reported to the owner via `/beak/tg/notify` (daemon path) and `~/.space-duck/logs/self_update.log`.
- Consent capture: `pair.py` now asks "Approve automatic skill updates from Spaceduckling? [y/N]" at pair time (TTY only; `SPACEDUCK_AUTO_UPDATE=auto|ask` env override for headless installs; default stays `ask`).
- Safety: trigger pecks are signals, never commands — the only action is `update.sh` (official registry latest, no-ops when current). Optional `update_senders` allowlist gates who can trigger; 1-hour debounce; updater spawned `start_new_session` so the listener bounce doesn't kill it. Lane A doctrine intact: consent lives only in the duck's local config; the platform never executes on the box.

Context Leakage

High
Category
Data Exfiltration
Confidence
85% confidence
Finding

Code or instructions that leak agent conversation context to external services, potentially exposing sensitive user interactions.

Content

Scanner excerpt · CHANGELOG.md (reported line 1318)May include surrounding context.

md
on the wire; one of the two will survive any reasonable
  intermediary processing.

### Bilateral verification record

Session `2b4394bc-f16f-4556-aaea-3147d1108650`, 2026-06-12 06:12-06:14 UTC:

External Script Fetching

High
Category
Supply Chain
Confidence
98% confidence
Finding

The changelog recommends bootstrap via curl -fsSL <URL> | bash, which executes remote content directly without an integrity boundary at execution time. This is a classic supply-chain risk: compromise of the URL, TLS trust, hosting account, or transit path can lead to immediate arbitrary code execution on the host.

Content

Scanner excerpt · CHANGELOG.md (reported line 1357)May include surrounding context.

md
Sensitive inputs come ONLY via env vars (never CLI args, never
  prompts at high entropy) to avoid `ps aux` leak. Has `--reset` for
  clean re-provisioning. Intended to be hosted at a stable HTTPS URL
  + run via `curl -fsSL <URL> | bash`, with operators encouraged to
  SHA-pin the script before piping. Quick-tunnel default is flagged
  as DEV ONLY; production gets a clear warning to set up a named
  cloudflared tunnel.

Ssd 3

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The changelog explicitly documents a contract that bash received over Telegram with an owner-labeled sentinel must execute immediately with no confirmation or summary. Even if guarded by a marker, this creates a remote code execution path whose safety depends entirely on perfect signing, delivery integrity, and parser correctness; any signature bypass, key compromise, or message confusion becomes instant command execution.

Content

No source excerpt is available for this finding.

YARA rule 'offensive_tool_references': References to well-known offensive security tools [hacktools]

High
Category
YARA Match
Confidence
70% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

Scanner excerpt · SECURITY-MANIFEST.md (reported line 37)May include surrounding context.

md
he explicit `allow_custom_api` opt-out (with stderr warning). Fail-closed: an attacker-supplied `fetch_url` never receives the beak key.

## Handoff target gating (0.8.3 [SDI-2])
- `peck_responder._send_handoff` fires a fresh peck at a duck named in the model's reply text (`HANDOFF_RE`). As of 0.8.3 the handoff target is gated client-side through `_check_permissions(cfg, my_sd, handoff_to)` (peck_responder.py) before sending — fail-closed if there is no permitted/active connection to that target. Owner is notified on block.

## Critic runs with no tools (0.8.4 [CRB-1])
- `peck_critic.py` reviews a draft reply (text in, JSON verdict out) using the local `claude` CLI. Its input embeds attacker-influenced inbound peck content.
- As of 0.8.4 it invokes `claude --print --tools "" --model <MODEL>` (peck_critic.py:93) — the **entire toolset is disabled**, replacing the earlier `--permission-mode bypassPermissions`. A prompt-injected critic cannot reach any tool; `--print` still runs non-i

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
86% confidence
Finding

Using subprocess.run(..., shell=True, executable='/bin/bash') creates a high-risk command-execution surface because shell parsing allows metacharacter expansion and turns any upstream input handling mistake into arbitrary command execution. The described owner-approval gate reduces exposure, but if attacker-influenced content can reach the pending command or if approval is socially engineered, the shell invocation materially increases impact.

Content

Scanner excerpt · SECURITY-MANIFEST.md (reported line 44)May include surrounding context.

md
- As of 0.8.4 it invokes `claude --print --tools "" --model <MODEL>` (peck_critic.py:93) — the **entire toolset is disabled**, replacing the earlier `--permission-mode bypassPermissions`. A prompt-injected critic cannot reach any tool; `--print` still runs non-interactively (nothing to approve).

## Command execution (disclosed — owner-in-the-loop)
- `telegram_listener.py:457` runs bash via `subprocess.run(..., shell=True, executable='/bin/bash')` in `_exec_pending()`.
- **Gate:** reachable only through `_handle_owner_approval_callback()` (telegram_listener.py:661) — an inline Telegram `callback_query` from the **owner** with data `sda:a:` (approve-run) or `sda:r:` (approve + 24h remember). A remote peck cannot reach this path directly.
- **Residual (disclosed):** `sda:r:` ("Run all") records the `action_kind` in `~/.space-duck/auto-approved.json` for 24h (telegram_listener.py:437-441); during that window same-kind actions run without a fresh tap. Owner-initiated, time-boxed, per-kind, 0600 file. Read-only kinds (show_beak_key, show_tunnel) auto-approve.
- No `os.system`, no `eval()`/`exec()` of remote input. The two `__import__('re')` uses (peck_responder.py:57,595) are lazy stdlib imports, not dynamic code execution.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 105)May include surrounding context.

md
- **Doctor output is redacted (`[HARDEN-074]`).** `doctor.sh` truncates

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 426)May include surrounding context.

md
- **Doctor output is redacted (`[HARDEN-074]`).** `doctor.sh` truncates

Static analysis

No suspicious patterns detected.