Back to skill

Security audit

Intros

Security checks for vulnerabilities and agentic risk

Overview

The social-networking behavior is mostly disclosed, but the skill teaches unsafe shell command formatting that can turn profile or message text into local command execution.

Install only if you are comfortable sending Intros profile data, searches, connection actions, Telegram linkage data, and messages to api.openbreeze.ai. Use extra caution with any profile, search, or message text containing quotes or shell metacharacters until the command-formatting guidance is fixed to avoid shell-string interpolation. Treat the local Intros API key as sensitive and delete ~/.openclaw/data/intros if you no longer want the local credential stored.

Vulnerability Patterns
  • 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
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:196
Finding
Shell Command Injection Through Unsafe Quoting of User-Controlled Arguments## Vulnerability Details **File Location**: `SKILL.md:196-204` **Vulnerability Type**: Shell command injection caused by unsafe command construction **Risk Level**: High The Skill explicitly instructs the agent to place user-provided values inside single quotes when constructing shell commands: ```markdown ## Command Formatting IMPORTANT: Always use single quotes around user-provided values when running commands. ```bash python3 ~/.openclaw/skills/intros/scripts/intros.py register --bot-id 'chosen_username' python3 ~/.openclaw/skills/intros/scripts/intros.py connect 'some_user' python3 ~/.openclaw/skills/intros/scripts/intros.py message send 'bob' 'Hello there!' python3 ~/.openclaw/skills/intros/scripts/intros.py profile create --name 'Alice' --interests 'AI, startups' ``` ``` ### Technical Analysis Single-quote wrapping does not safely escape arbitrary data for a shell command. If a user-controlled profile field, message, search query, or similar argument contains a single quote, it can terminate the quoted argument. The remaining input can then be interpreted by the shell as command syntax. This is particularly relevant to profile fields and message content because `scripts/intros.py` accepts arbitrary text for these arguments. Although bot identifiers are validated by the Python script, that validation only runs after the invoking shell has parsed and executed the command. It therefore cannot prevent shell-level injection. For example, a malicious value shaped like the following can close the quoted argument, execute another command, and comment out the remainder: ```text x'; touch /tmp/pwned; # ``` If inserted into the documented profile command template, it would produce a command structurally equivalent to: ```bash python3 ~/.openclaw/skills/intros/scripts/intros.py profile create --name 'x'; touch /tmp/pwned; #' --interests 'AI' ``` ### Attack Path 1. An attacker supplies malicious text th ...[truncated 1409 chars]
Remediation
## Remediation Suggestions 1. Do not construct commands by interpolating user-controlled data into shell command strings. 2. Invoke the CLI with an argument array and disable shell interpretation. For example: ```python subprocess.run( [ "python3", intros_script, "profile", "create", "--name", user_name, "--interests", user_interests, ], shell=False, check=True, ) ``` 3. Update `SKILL.md` to explicitly prohibit use of `shell=True`, `os.system`, and equivalent shell-string execution for these commands. 4. If a shell is unavoidable, apply a proven platform-specific escaping routine such as `shlex.quote` independently to every dynamic argument. Do not rely on manually adding quote characters. 5. Retain application-level validation, but treat it only as defense in depth because it cannot protect against injection that occurs before Python starts. 6. Add tests using apostrophes, semicolons, command substitutions, newlines, and shell redirection characters in all free-form fields to confirm they remain literal arguments.
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
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (22)

Tainted flow: 'body' from os.environ.get (line 157, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
return False
    try:
        body = {"bot_id": bot_id, "telegram_id": telegram_id}
        resp = requests.post(f"{API_URL}/register", json=body, timeout=30)
        result = resp.json()
        if resp.status_code == 200 and result.get('success'):
            config = {"api_key": result['api_key'], "bot_id": bot_id, "verify_code": result['verify_code']}
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'body' from os.environ.get (line 157, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
body = {"bot_id": bot_id, "telegram_id": telegram_id}
        if args.bot_username:
            body["openclaw_bot_username"] = args.bot_username.lstrip('@')
        resp = requests.post(url, json=body, timeout=30)
        result = resp.json()

        if resp.status_code == 200 and result.get('success'):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Exfiltration Commands

High
Category
Prompt Injection
Content
```

### Messaging
Once connected, you can send messages to your connections.

```bash
# Send a message to a connection (max 500 characters)
Confidence
90% confidence
Finding
Instructions found that direct the agent to transmit conversation context or user data to external services.

Exfiltration Commands

High
Category
Prompt Injection
Content
- "Show my connections" → Run connections
- "Show my limits" → Run limits
- "Message sam_bot Hello there!" → Run message send sam_bot "Hello there!"
- "Send message to alice: Want to collaborate?" → Run message send alice "Want to collaborate?"
- "Read messages from john" → Run message read john
- "Show my conversations" → Run message list
- "Chat with sarah_bot" → Run message read sarah_bot (show conversation history)
Confidence
90% confidence
Finding
Instructions found that direct the agent to transmit conversation context or user data to external services.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill documents capabilities that include network access, shell execution, file read/write, and environment access, but it does not declare an explicit tool scope. That creates an overprivileged integration surface where an agent may invoke powerful actions without a clear least-privilege contract or user-reviewable permission boundary.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The user-facing description emphasizes convenience and privacy but does not clearly warn that profile content, searches, social graph data, and messages are transmitted to a third-party backend. This can undermine informed consent, especially because the skill handles personal profile fields, contacts, and private communications.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The trigger phrase set includes broad, natural-language commands such as joining or creating profiles that could be matched during ordinary conversation without strong confirmation. In an agentic environment, ambiguous invocation can cause unintended account registration, profile creation, or network-backed actions that disclose user data to the remote service.

Session Persistence

Medium
Category
Rogue Agent
Content
### Step 2: Verify
Send the verification code to @Intros_verify_bot on Telegram. This also enables automatic notifications — you'll receive Telegram messages for new connections, messages, and daily match suggestions.

### Step 3: Create Profile
```bash
python3 ~/.openclaw/skills/intros/scripts/intros.py profile create --name "Your Name" --interests "AI, startups" --looking-for "Co-founders" --location "Mumbai" --bio "Your bio here"
```
Confidence
76% confidence
Finding
The skill intentionally persists API keys and identity information so sessions survive reinstalls and can be auto-recovered. While this is product behavior rather than overtly malicious persistence, storing plaintext credentials and automatically re-establishing access increases the blast radius of local compromise and may preserve authorization longer than users expect.

Vague Triggers

Medium
Confidence
92% confidence
Finding
Phrases like 'Show my conversations' and 'Chat with sarah_bot' are generic and likely to overlap with unrelated assistant tasks. Because the skill can access remote messages and social-graph data, ambiguous dispatch increases the risk of accidental invocation and unintended retrieval or disclosure of sensitive conversation history.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
- **API Server**: All data is stored on the Intros backend at `https://api.openbreeze.ai` (source: [github.com/sam201401/intros](https://github.com/sam201401/intros))
- **Registration**: During `register`, you provide your bot's Telegram username via `--bot-username`. This is used solely to add an "Open Bot" deep link button on notification messages. No local config files are read.
- **Persistent storage**: The skill saves your API key and identity to `~/.openclaw/data/intros/` (JSON, chmod 600 owner-only) so credentials survive skill reinstalls. Delete this directory to revoke stored credentials.
- **Auto-recovery**: If config is lost (e.g. after reinstall), the skill re-registers using your saved identity file. This is idempotent and returns existing credentials.
- **Notifications**: Sent via @Intros_verify_bot on Telegram (server-side, no cron needed).
- **Environment variables**: `OPENCLAW_STATE_DIR` (optional) overrides the OpenClaw state directory for multi-instance setups. `TELEGRAM_USER_ID` (optional) is read as a fallback during registration if `--telegram-id` is not provided.
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

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

Medium
Category
Data Flow
Content
def save_config(config):
    """Save configuration with restrictive file permissions (owner-only)"""
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    with open(CONFIG_PATH, 'w') as f:
        json.dump(config, f, indent=2)
    os.chmod(CONFIG_PATH, 0o600)
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

External Transmission

Medium
Category
Data Exfiltration
Content
if method == 'GET':
            resp = requests.get(url, headers=headers, params=params, timeout=30)
        elif method == 'POST':
            resp = requests.post(url, headers=headers, json=data, timeout=30)
        elif method == 'DELETE':
            resp = requests.delete(url, headers=headers, timeout=30)
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

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

Medium
Category
Data Flow
Content
"""Save minimal identity to DATA_DIR for auto-recovery after reinstall."""
    identity_file = DATA_DIR / "identity.json"
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    with open(identity_file, 'w') as f:
        json.dump({"bot_id": bot_id, "telegram_id": telegram_id}, f)
    os.chmod(identity_file, 0o600)
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

External Transmission

Medium
Category
Data Exfiltration
Content
return False
    try:
        body = {"bot_id": bot_id, "telegram_id": telegram_id}
        resp = requests.post(f"{API_URL}/register", json=body, timeout=30)
        result = resp.json()
        if resp.status_code == 200 and result.get('success'):
            config = {"api_key": result['api_key'], "bot_id": bot_id, "verify_code": result['verify_code']}
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Description-Behavior Mismatch

Medium
Confidence
87% confidence
Finding
The skill collects Telegram identifiers and directs users to interact with an external Telegram bot for verification and notifications, expanding data sharing beyond the manifest's core description of finding people, managing connections, and chatting. This creates a privacy and scope-creep risk because users may not expect cross-service identity linking or understand that their identifiers are being sent to external infrastructure.

External Transmission

Medium
Category
Data Exfiltration
Content
body = {"bot_id": bot_id, "telegram_id": telegram_id}
        if args.bot_username:
            body["openclaw_bot_username"] = args.bot_username.lstrip('@')
        resp = requests.post(url, json=body, timeout=30)
        result = resp.json()

        if resp.status_code == 200 and result.get('success'):
Confidence
80% 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
93% confidence
Finding
Profile creation and messaging transmit personal data such as name, interests, location, bio, Telegram handle, and message content to a remote API without any explicit privacy notice in this file. In a social-networking context this transmission is expected functionally, but the absence of clear disclosure and consent increases privacy risk and can lead to unintended sharing of sensitive information.

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

Medium
Category
Data Flow
Content
# Save current IDs as seen (only current, old ones cleared when read)
        if current_msg_ids:
            with open(seen_msg_file, 'w') as f:
                json.dump(list(current_msg_ids), f)

        # Notify about new messages
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

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

Medium
Category
Data Flow
Content
# Save current IDs as seen
        if current_ids or seen_ids:
            with open(seen_file, 'w') as f:
                json.dump(list(current_ids), f)

        # Notify about new requests
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

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

Medium
Category
Data Flow
Content
# Save current IDs as seen
        if current_accepted_ids or seen_accepted_ids:
            with open(seen_accepted_file, 'w') as f:
                json.dump(list(current_accepted_ids), f)

        # Notify about accepted connections
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Intent-Code Divergence

Low
Confidence
81% confidence
Finding
The 'How It Works' section states 'No local config files are read' in the registration description, presenting registration as only using explicit CLI input. Just a few lines later, the documentation says TELEGRAM_USER_ID is read as a fallback during registration, which contradicts the earlier claim of registration input sources and creates intent-level inconsistency in the docs.

Description-Behavior Mismatch

Low
Confidence
76% confidence
Finding
The skill generates a private web URL containing a token derived from bot_id and the first eight characters of the API key. Even if intended as convenience functionality, this extends behavior into web-based access and uses a weak, partially key-derived token in a query parameter, which is prone to leakage via logs, browser history, and referrers.

Static analysis

No suspicious patterns detected.