Back to skill

Security audit

Signal messaging for standalone bots/accounts

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real Signal integration, but it needs Review because its advertised role-based protections are only advisory while incoming Signal text is written into agent-readable wake files.

Install only if you are prepared to run this as a sensitive messaging bridge: use a dedicated low-privilege account, verify signal-cli before installing, avoid sudo system-wide installation where possible, restrict ~/.signal-state permissions, add retention limits, and do not let unknown Signal messages flow directly into an agent context without code-enforced quarantine and approval.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
Findings (4)

T01 · Skill Instruction Hijacking

Error
Location
scripts/signal-poll.sh:90
Finding
Untrusted Signal messages are inserted into the Agent processing context without enforceable isolation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/signal-poll.sh:90-101`; related processing instructions in `SKILL.md:132-141` **Vulnerability Type**: Untrusted instruction injection into an Agent-controlled workflow **Risk Level**: High ### Vulnerable Code ```bash if [[ -n "$current_sender" && -n "$body" ]]; then has_message=true name=$(get_name "$current_sender") role=$(get_role "$current_sender") history_file="$HISTORY_DIR/${current_sender}.log" echo "[$current_timestamp] $name: $body" >> "$history_file" echo "[$(date)] Received TEXT from $name ($current_sender) [$role]: $body" >> "$STATE_DIR/monitor.log" echo "Signal from $name ($current_sender) [$role]: $body" >> "$WAKE_FILE" # Flag untrusted/pending contacts for triage if [[ "$role" == "untrusted" || "$role" == "pending" ]]; then echo "[$(date)] TRIAGE NEEDED: $role contact $name ($current_sender) messaged: $body" >> "$STATE_DIR/triage.log" echo "⚠️ NEW/PENDING CONTACT needs triage - $name ($current_sender) [$role]: $body" >> "$WAKE_FILE" fi fi ``` The documented Agent workflow then consumes the file: ```markdown ### Signal Messages (check first!) cat /path/to/.signal-state/pending_wakes 2>/dev/null ``` ### Technical Analysis The script obtains the sender's role, but writes the complete message body into `pending_wakes` before applying any enforceable authorization decision. The subsequent role check only appends a warning. It does not quarantine the message, redact its contents, or prevent the Agent from interpreting it. Consequently, the role field is merely an advisory natural-language label. If OpenClaw loads the wake file as contextual instructions, attacker-controlled Signal text shares the same processing channel as trusted content. A malicious message can contain prompt-injection instructions that attempt to override the documented triage procedure, solicit confidential information, or induce tool calls. The vulnerabili ...[truncated 1423 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Enforce authorization before placing any message content into an Agent-readable queue. - Store untrusted and pending messages in a separate quarantine file that the Agent does not automatically interpret. - For an untrusted sender, wake the owner using a fixed notification that excludes the message body. - Release quarantined content only after an authenticated owner approval changes the sender's role. - Use a structured format such as JSON with separate `sender`, `role`, `type`, and `content` fields. - Ensure downstream code treats `content` strictly as untrusted data rather than instructions. - Implement tool-level authorization independently of model behavior. Owner-only operations must require a verified sender role outside the language model. - Add automated tests proving that untrusted message bodies never enter the normal Agent context. ]]>

T08 · Insecure Dependencies

Error
Location
SKILL.md:41
Finding
Downloaded signal-cli executable is installed without checksum or signature verification<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:41-51` **Vulnerability Type**: Unverified third-party executable installation **Risk Level**: High ### Vulnerable Code ```bash # Download latest release SIGNAL_CLI_VERSION="0.13.12" curl -L "https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-Linux.tar.gz" | tar xz sudo mv signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/local/bin/ sudo mv signal-cli-${SIGNAL_CLI_VERSION}/lib /usr/local/lib/signal-cli # Or install to user directory mv signal-cli-${SIGNAL_CLI_VERSION} ~/.local/share/signal-cli-install ln -s ~/.local/share/signal-cli-install/bin/signal-cli ~/.local/bin/signal-cli ``` ### Technical Analysis The dependency version is pinned and the URL points to the declared upstream GitHub repository, which reduces but does not eliminate supply-chain risk. The archive is streamed directly from `curl` into `tar`, and no cryptographic checksum or trusted release signature is verified before extraction and installation. The use of `curl -L` follows redirects without restricting the final destination. A compromised upstream release, repository account, release artifact, or redirected endpoint could therefore supply altered content. The extracted executable and libraries are subsequently placed in system-wide paths using `sudo`, or in user executable paths. Streaming directly into `tar` also prevents validation of the complete archive before extraction. The documentation does not use archive hardening controls to reject unsafe paths or links. ### Attack Path 1. An attacker compromises the upstream release process, release account, artifact storage, or a redirect destination. 2. The configured URL returns a modified archive for the expected version. 3. The user executes the documented installation command. 4. `curl` streams the archive directly into `tar` without integrity verification. 5. The modified executable and libraries are install ...[truncated 729 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Download the archive to a non-executable temporary file before extraction. - Publish and pin an expected SHA-256 or stronger digest for the exact release archive. - Prefer verification with an upstream signing key whose fingerprint is documented and independently validated. - Use `curl --fail --show-error --location` and restrict accepted protocols and redirect destinations. - Verify the digest or signature before invoking `tar`. - Inspect archive entries and reject absolute paths, `..` traversal components, and unsafe symbolic or hard links. - Prefer a user-local installation unless system-wide installation is strictly required. - Avoid invoking broad `sudo mv` operations on unverified extracted content. - Document a controlled upgrade process in which each new dependency version requires review and a new pinned digest. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/signal-poll.sh:22
Finding
Signal conversations and authorization metadata are created without restrictive filesystem permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/signal-poll.sh:22-28, 96-100, 130-134`; `scripts/signal-send.sh:20, 35-36` **Vulnerability Type**: Insecure storage of sensitive plaintext data **Risk Level**: Medium ### Vulnerable Code ```bash mkdir -p "$HISTORY_DIR" # Initialize permissions file if it doesn't exist if [[ ! -f "$PERMISSIONS_FILE" ]]; then echo '{}' > "$PERMISSIONS_FILE" echo "[$(date)] Created empty permissions file at $PERMISSIONS_FILE" >> "$STATE_DIR/monitor.log" fi ``` Message bodies and authorization details are then written to files created under the process's inherited umask: ```bash echo "[$current_timestamp] $name: $body" >> "$history_file" echo "[$(date)] Received TEXT from $name ($current_sender) [$role]: $body" >> "$STATE_DIR/monitor.log" echo "Signal from $name ($current_sender) [$role]: $body" >> "$WAKE_FILE" if [[ "$role" == "untrusted" || "$role" == "pending" ]]; then echo "[$(date)] TRIAGE NEEDED: $role contact $name ($current_sender) messaged: $body" >> "$STATE_DIR/triage.log" echo "⚠️ NEW/PENDING CONTACT needs triage - $name ($current_sender) [$role]: $body" >> "$WAKE_FILE" fi ``` The send script has the same behavior: ```bash mkdir -p "$HISTORY_DIR" history_file="$HISTORY_DIR/${recipient}.log" echo "[$(date '+%Y-%m-%d %H:%M:%S')] Bot: $message" >> "$history_file" ``` ### Technical Analysis Neither script sets a restrictive umask nor explicitly assigns secure modes to the state directory and files. Their effective permissions therefore depend on the invoking environment. Under a common `022` umask, newly created directories are generally mode `0755` and files mode `0644`, allowing other local users to enumerate directories and read plaintext data. The stored information includes incoming and outgoing message contents, phone numbers or UUIDs, role assignments, attachment paths, triage records, and processing state. The permissions file is security-sensitive because it determines which se ...[truncated 1207 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Set `umask 077` at the beginning of both scripts before creating any directory or file. - Create the state and history directories with explicit mode `0700`, for example: ```bash install -d -m 0700 "$STATE_DIR" "$HISTORY_DIR" ``` - Create `permissions.json`, `pending_wakes`, and log files with mode `0600`. - Verify that the state directory and files are owned by the expected service account before reading or writing them. - Refuse to operate when sensitive files are symbolic links or have unexpected ownership. - Correct existing installations with: ```bash chmod 700 "$STATE_DIR" "$HISTORY_DIR" find "$STATE_DIR" -type f -exec chmod 600 {} + ``` - Consider limiting message retention and redacting message bodies from debug and monitor logs. - Run the polling process under a dedicated, unprivileged operating-system account. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/signal-send.sh:13
Finding
Unvalidated recipient identifier permits conversation-log path traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/signal-send.sh:13-14, 27-36` **Vulnerability Type**: Path traversal and unauthorized file append **Risk Level**: Medium ### Vulnerable Code ```bash recipient="$1" message="$2" if [[ -z "$recipient" || -z "$message" ]]; then echo "Usage: signal-send.sh <recipient> <message>" exit 1 fi ``` The unsanitized value is later incorporated into a filesystem path: ```bash # Send the message $SIGNAL_CLI -a "$SIGNAL_NUMBER" send -m "$message" "$recipient" # Stop typing indicator $SIGNAL_CLI -a "$SIGNAL_NUMBER" sendTyping -s "$recipient" 2>/dev/null # Log to conversation history history_file="$HISTORY_DIR/${recipient}.log" echo "[$(date '+%Y-%m-%d %H:%M:%S')] Bot: $message" >> "$history_file" ``` ### Technical Analysis The script documents the recipient as an E.164 phone number or UUID, but it only verifies that the argument is nonempty. It does not validate the recipient against either permitted format. Because `recipient` is concatenated directly into `history_file`, a value containing slash and `..` path components can resolve outside `HISTORY_DIR`. Quoting the path prevents shell word splitting but does not prevent filesystem traversal. The script also does not enable `set -e` or test whether the Signal send operation succeeded. Therefore, the log append still occurs when an invalid traversal recipient causes `signal-cli` to fail. The generated path always receives a `.log` suffix, which limits direct targeting but still permits writes to attacker-selected paths with that suffix. Existing symbolic links can further affect the final destination. ### Attack Path 1. An attacker obtains the ability to invoke `signal-send.sh`, directly or through an Agent tool that passes untrusted arguments. 2. The attacker supplies a recipient such as `../../target`. 3. `signal-cli` attempts to send to the invalid recipient and may fail. 4. The script continues because it does not check the command's exit st ...[truncated 763 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Validate recipients using strict allowlisted formats before any command or filesystem operation: - E.164 number: `^\+[1-9][0-9]{1,14}$` - Canonical UUID: `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$` - Reject recipients containing `/`, `\`, `..`, control characters, or newlines. - Derive log filenames from an encoded or hashed canonical identifier rather than using raw input. - Resolve the canonical parent and final path and verify that it remains under `HISTORY_DIR`. - Reject symbolic-link destinations and verify file ownership before appending. - Check the exit status of `signal-cli send` and do not log a successful outgoing message when sending fails. - Add regression tests covering traversal inputs, absolute paths, malformed UUIDs, newlines, and symbolic-link targets. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill claims security-sensitive features such as role-based permissions, contact triage, voice processing, wake hooks, and sending behavior, but the documented/observed behavior does not fully implement those controls. In security tooling, this mismatch is dangerous because operators may rely on protections that do not actually exist, leading to unauthorized message handling, data exposure, or unsafe automation.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
signal-cli uses file locking. If cron polling and manual sends overlap:
- The second instance waits briefly then proceeds
- This is normal and self-resolving
- If stuck: `rm ~/.local/share/signal-cli/data/*.lock`

### Messages from new contacts not appearing
New contacts without phone numbers show as UUIDs. The poll script handles both formats. If messages still don't appear:
Confidence
97% confidence
Finding
The command uses a wildcard rm against lock files in an application data directory as a routine recovery action. In an agent or operator context, this normalizes destructive filesystem modification without validation, and can disable locking guarantees that protect against concurrent access, leading to state corruption, failed sends, or unsafe parallel execution.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill documents shell commands and operational behavior that clearly require shell execution, but it does not declare an explicit tool scope such as permissions or allowed-tools. That gap can cause an agent platform to expose broader execution capability than intended or prevent reviewers from understanding the true trust boundary of the skill.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill does not clearly warn that incoming messages may trigger automatic transmission to the OpenClaw wake hook API. Even if the endpoint is local or expected, message-derived metadata being forwarded automatically changes the data-flow and trust model, and users may not realize external or inter-process propagation is occurring.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill states that conversation history and auto-logging exist, but it does not present a clear upfront warning that all Signal conversations are written to local files. This can expose sensitive personal or operational content to unintended local users, backups, logs, or later compromise if operators do not realize retention is happening.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# Download latest release
SIGNAL_CLI_VERSION="0.13.12"
curl -L "https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-Linux.tar.gz" | tar xz
sudo mv signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/local/bin/
sudo mv signal-cli-${SIGNAL_CLI_VERSION}/lib /usr/local/lib/signal-cli

# Or install to user directory
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# Download latest release
SIGNAL_CLI_VERSION="0.13.12"
curl -L "https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-Linux.tar.gz" | tar xz
sudo mv signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/local/bin/
sudo mv signal-cli-${SIGNAL_CLI_VERSION}/lib /usr/local/lib/signal-cli

# Or install to user directory
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
ln -s ~/.local/share/signal-cli-install/bin/signal-cli ~/.local/bin/signal-cli
```

Requires Java 21+: `sudo apt install openjdk-21-jre-headless`

### 2. Register a number
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Session Persistence

Medium
Category
Rogue Agent
Content
```bash
# Poll every minute
crontab -e
# Add: * * * * * /path/to/scripts/signal-poll.sh
```
Confidence
85% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

External Transmission

Medium
Category
Data Exfiltration
Content
ffmpeg -i /path/to/attachment.m4a -ar 16000 -ac 1 -c:a pcm_s16le /tmp/audio.wav -y

# Transcribe (whisper.cpp server example)
curl -s http://127.0.0.1:8080/inference -F "file=@/tmp/audio.wav" -F "language=en"

# Or use OpenAI Whisper, faster-whisper, etc.
```
Confidence
78% confidence
Finding
The skill sends transcribed audio to an HTTP inference service and also suggests use of third-party transcription providers, creating a clear data egress path for message content. Because voice messages may contain sensitive personal or operational information, forwarding them to another service without strong disclosure, minimization, and transport assurances can leak confidential data.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation explicitly grants the owner role the ability to access other contacts' conversations, but it provides no privacy notice, consent model, audit requirement, or limitation on when that access is appropriate. In a messaging integration, cross-conversation access is highly sensitive because a compromised owner account, misconfigured role assignment, or overbroad agent behavior could expose private communications across users.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The troubleshooting guidance recommends manually deleting signal-cli lock files without any warning to first ensure no process is still using them. Removing lock files unsafely can corrupt state, bypass concurrency protections, and cause overlapping Signal operations that may damage message stores or create inconsistent behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
### Wake API not triggering
- Verify OpenClaw hooks config: check `openclaw.json` has `hooks.wake.enabled: true`
- Test manually: `curl -X POST http://127.0.0.1:18789/hooks/wake -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" -d '{"text":"test"}'`
- Check `monitor.log` for wake trigger entries

### Typing indicators not showing
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script persistently stores Signal message bodies in per-contact history files and monitor logs without any minimization, redaction, retention control, or consent mechanism. In a messaging integration, this creates unnecessary local exposure of potentially sensitive communications, and compromise of the host or user account would reveal full plaintext conversation history.

Ssd 3

Medium
Confidence
95% confidence
Finding
Incoming message text is copied verbatim into history, monitor, wake, and triage files, multiplying the number of plaintext locations containing user-supplied sensitive data. This broadens exposure and increases the chance of accidental disclosure to other local processes, backups, support tooling, or operators reviewing logs.

Ssd 3

Medium
Confidence
89% confidence
Finding
Attachment paths and related metadata are recorded in several files, which can reveal filenames, media types, sender identities, and storage locations. Even if the attachment contents are not retransmitted, this metadata can still expose sensitive user activity and internal filesystem layout.

Missing User Warnings

Medium
Confidence
81% confidence
Finding
The script sends a network request to an external wake endpoint after processing Signal messages, authenticated with a bearer token. Even though the payload is minimal, the script behavior is network-triggering based on message activity and the file does not include a clear user warning or disclosure about this outbound integration beyond a configuration label.

External Transmission

Medium
Category
Data Exfiltration
Content
# Trigger OpenClaw wake API if there are pending messages
if [[ -s "$WAKE_FILE" && -n "$WAKE_URL" && -n "$WAKE_TOKEN" ]]; then
    curl -s -X POST "$WAKE_URL" \
      -H "Authorization: Bearer $WAKE_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"text": "Signal message received", "mode": "now"}' > /dev/null 2>&1
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script writes full outbound message contents to a local conversation log in $HOME/.signal-state/conversations without any access-control checks, encryption, retention policy, or user consent mechanism. In a Signal integration, this weakens the privacy guarantees users may expect from end-to-end encrypted messaging because sensitive message content is persisted in plaintext on disk and could be exposed to other local users, backups, or later compromise of the host.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
The example commands hard-code `language=en` for both Whisper transcription and TTS generation. This can violate language/locale policy because it defaults the skill to English behavior without user opt-in or explanation that the setting is merely an example and can be changed.

Static analysis

No suspicious patterns detected.