Back to skill

Security audit

X-Scout

Security checks for vulnerabilities and agentic risk

Overview

This skill largely matches its X/Twitter research purpose, but it silently reports usage, stores API keys unsafely, and has a setup-script input handling flaw.

Before installing, review whether you are comfortable with usage telemetry to clawagents.dev, plaintext API keys in the project and home directory, and external processing by TwitterAPI.io, OpenRouter, Cerebras, and Deepgram. Use isolated credentials, avoid running setup with privileged accounts, and rotate keys if the generated config files may have been shared or committed.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
setup.sh:165
Finding
Python Code Injection Through Unsafely Interpolated Setup Inputs<![CDATA[ ## Vulnerability Details **File Location**: `setup.sh`, lines 165-178 and 190-200 **Vulnerability Type**: Unsanitized data embedded in executable Python source **Risk Level**: High ### Vulnerable Code ```bash # Save to ~/.x-scout/config.json python3 -c " import json config = { 'install_id': '$INSTALL_ID', 'twitterapi_key': '$TW_KEY', 'openrouter_key': '${OR_KEY:-}', 'cerebras_keys': '${CB_KEYS:-}', 'deepgram_key': '${DG_KEY:-}', } with open('$XS_CONFIG', 'w') as f: json.dump(config, f, indent=2) " ``` A second vulnerable interpolation occurs when constructing the registration payload: ```bash REGISTER_PAYLOAD=$(python3 -c " import json, platform print(json.dumps({ 'tool': 'x-scout', 'install_id': '$INSTALL_ID', 'email': '${USER_EMAIL:-}', 'platform': platform.system(), 'python': '$PY_VER', 'has_openrouter': bool('${OR_KEY:-}'), 'has_cerebras': bool('${CB_KEYS:-}'), 'has_deepgram': bool('${DG_KEY:-}'), })) ") ``` ### Technical Analysis Values collected from interactive prompts or inherited environment variables are inserted directly into source code passed to `python3 -c`. These values are treated as part of the Python program rather than as data. An attacker-controlled value containing a quote and valid Python statements can terminate the intended string literal and inject additional Python expressions. No escaping or syntactic validation is applied before interpolation. For example, a crafted key following this general structure can alter the generated Python program: ```text '; __import__("os").system("ATTACKER_COMMAND"); injected=' ``` The vulnerable inputs include the TwitterAPI.io, OpenRouter, Cerebras, and Deepgram keys, as well as the optional registration email. Environment-provided values are affected in addition to interactively entered values. ### Attack Path 1. An attacker persuades a user to run `setup.sh` with a malicious API-key environment variable, supplies a crafted v ...[truncated 1004 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Never insert external values into Python source code. - Pass values through environment variables, standard input, or positional arguments and read them as data inside Python. - A safe environment-based pattern is: ```bash export INSTALL_ID TW_KEY OR_KEY CB_KEYS DG_KEY XS_CONFIG python3 <<'PY' import json import os config = { "install_id": os.environ["INSTALL_ID"], "twitterapi_key": os.environ["TW_KEY"], "openrouter_key": os.environ.get("OR_KEY", ""), "cerebras_keys": os.environ.get("CB_KEYS", ""), "deepgram_key": os.environ.get("DG_KEY", ""), } with open(os.environ["XS_CONFIG"], "w", encoding="utf-8") as output: json.dump(config, output, indent=2) PY ``` - Construct the registration payload with the same data-only pattern. - Do not rely on ad hoc quote escaping; environment variables or structured standard input avoid source-code generation entirely. - Add regression tests using values containing single quotes, double quotes, newlines, semicolons, and Python syntax. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
setup.sh:153
Finding
API Credentials Persisted in Multiple Plaintext Files Without Enforced Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `setup.sh`, lines 153-178 **Vulnerability Type**: Insecure plaintext secret storage **Risk Level**: Medium ### Vulnerable Code ```bash cat > "$SCRIPT_DIR/.env" <<ENVEOF TWITTERAPI_KEY=$TW_KEY OPENROUTER_API_KEY=${OR_KEY:-} CEREBRAS_API_KEYS=${CB_KEYS:-} DEEPGRAM_API_KEY=${DG_KEY:-} XS_INSTALL_ID=$INSTALL_ID ENVEOF echo -e " ${GREEN}Saved .env to $SCRIPT_DIR/.env${NC}" # Save to ~/.x-scout/config.json python3 -c " import json config = { 'install_id': '$INSTALL_ID', 'twitterapi_key': '$TW_KEY', 'openrouter_key': '${OR_KEY:-}', 'cerebras_keys': '${CB_KEYS:-}', 'deepgram_key': '${DG_KEY:-}', } with open('$XS_CONFIG', 'w') as f: json.dump(config, f, indent=2) " ``` ### Technical Analysis The setup process writes every collected API credential to two plaintext locations: - `<project directory>/.env` - `~/.x-scout/config.json` The script does not establish a restrictive umask and does not explicitly apply mode `0600` to either file. Consequently, actual permissions depend on the user's current umask and pre-existing file permissions. Duplicating credentials also unnecessarily expands the secret exposure surface. Runtime code shown in the audit reads only `install_id` from `~/.x-scout/config.json`, while operational API keys are loaded from the project `.env` file or environment. Retaining duplicate API keys in the home-directory configuration therefore exceeds the minimum storage required by the demonstrated runtime behavior. The project-local `.env` is additionally vulnerable to accidental inclusion in source archives, backups, copied project folders, or version-control commits. ### Attack Path 1. A user runs `setup.sh` and supplies valid API credentials. 2. The script writes the credentials in plaintext to both storage locations. 3. If the user's umask or existing file modes permit access, another local account or process reads the files. 4. Alternatively, the project directory ...[truncated 691 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Prefer an operating-system credential manager or dedicated secrets service instead of plaintext files. - If file storage is unavoidable, set a restrictive umask before creating any secret-bearing file: ```bash umask 077 mkdir -p "$XS_DIR" ``` - Create files atomically and explicitly enforce permissions: ```bash chmod 600 "$SCRIPT_DIR/.env" "$XS_CONFIG" chmod 700 "$XS_DIR" ``` - Store credentials only once. Remove API keys from `~/.x-scout/config.json` if runtime code only needs `install_id`. - Add `.env` to `.gitignore` and provide a non-secret `.env.example`. - Warn users that the file contains secrets and must not be committed, shared, or included in support bundles. - Avoid shell-generated `.env` syntax for arbitrary secret values; use a serializer that safely handles newlines and special characters. - Document key rotation and revocation procedures for users whose files may already have been exposed. ]]>

other

Warning
Location
x_scout.py:97
Finding
Mandatory Silent Per-Run Telemetry Enables Persistent Usage Correlation<![CDATA[ ## Vulnerability Details **File Location**: `x_scout.py`, lines 97-119 and 1155-1223 **Vulnerability Type**: Privacy-impacting telemetry without a runtime opt-out **Risk Level**: Medium ### Vulnerable Code ```python # Analytics endpoint ANALYTICS_URL = "https://clawagents.dev/reddit-rank/v1/xs/usage" # --------------------------------------------------------------------------- # Analytics: phone-home on every run # --------------------------------------------------------------------------- def _report_usage(mode, query=None, results_count=0, error=None): """Silent phone-home to track CLI usage. Never blocks, never fails loudly.""" try: requests.post( ANALYTICS_URL, json={ "tool": "x-scout", "version": VERSION, "install_id": INSTALL_ID, "mode": mode, "query_hash": hashlib.sha256(query.encode()).hexdigest()[:12] if query else None, "results": results_count, "error": str(error)[:200] if error else None, "ts": int(time.time()), }, timeout=3, ) except Exception: pass ``` The reporting function is invoked after successful operations and on failures: ```python _report_usage(mode, query=args.search, results_count=len(results)) _report_usage(mode, query=args.profile, results_count=len(results)) _report_usage(mode, query=args.comments, results_count=len(results)) _report_usage(mode, query=args.intel, results_count=result["comment_count"]) ``` ```python except KeyboardInterrupt: print("\nInterrupted.", file=sys.stderr) _report_usage(mode, query=query_for_analytics, error="keyboard_interrupt") sys.exit(130) except Exception as e: print(f"ERROR: {e}", file=sys.stderr) _report_usage(mode, query=query_for_analytics, error=str(e)) sys.exit(1) ``` ### Technical Analysis Every operational mode automatically sends telemetry to `cl ...[truncated 2156 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Make analytics disabled by default and require explicit, informed opt-in. - Provide a persistent setting and a runtime switch such as `--no-telemetry`. - Clearly disclose the endpoint, exact fields, purpose, retention period, and deletion procedure before consent. - Do not send query-derived hashes. A truncated unsalted hash of predictable input is not a reliable anonymization mechanism. - Do not transmit raw exception strings. Use fixed local error codes if aggregate reliability metrics are necessary. - Avoid stable installation identifiers where session-level aggregate metrics are sufficient. - Display a visible notice when telemetry is transmitted rather than silently suppressing all reporting behavior. - Ensure scraping, classification, and transcription continue to function when analytics are disabled or unreachable. ]]>

T08 · Insecure Dependencies

Warning
Location
x_scout.py:34
Finding
Automatic Installation of Unpinned Dependencies From Mutable Package Sources<![CDATA[ ## Vulnerability Details **File Location**: `x_scout.py`, lines 34-43; `setup.sh`, lines 69-70; `requirements.txt`, lines 1-2 **Vulnerability Type**: Unsafe runtime dependency installation and non-reproducible dependency resolution **Risk Level**: Medium ### Vulnerable Code `x_scout.py` automatically installs a dependency when importing it fails: ```python try: import requests except ImportError: cmd = [sys.executable, "-m", "pip", "install", "-q", "requests"] try: subprocess.check_call(cmd + ["--break-system-packages"]) except subprocess.CalledProcessError: subprocess.check_call(cmd) import requests ``` The setup script upgrades pip and installs dependencies without a locked dependency set: ```bash pip install -q --upgrade pip 2>/dev/null || true pip install -q -r "$SCRIPT_DIR/requirements.txt" ``` The requirements file uses open-ended lower bounds: ```text requests>=2.28.0 python-dotenv>=1.0.0 ``` ### Technical Analysis Executing the CLI can trigger a package installation as a side effect of an import failure. The first installation attempt uses `--break-system-packages`, which can modify a system-managed Python environment and bypass distribution protections intended to prevent conflicts with operating-system packages. The package name is not version-pinned in the runtime installation. The requirements file similarly permits any future version above the stated minimums and provides no cryptographic hashes. This makes installation results dependent on the state of the configured package index at execution time. No malicious or typosquatted package name was identified in the audited files; `requests` and `python-dotenv` are legitimate packages. The risk arises from automatically trusting mutable future releases or an attacker-controlled/misconfigured package index, and from executing package build or installation logic without a reviewed lock set. ### Attack Path 1. `requests` is absent from the interpr ...[truncated 1199 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove package installation from application runtime. If a dependency is missing, terminate with a clear message instructing the user to install the project environment. - Never use `--break-system-packages` from application code. - Require execution from the virtual environment created by setup. - Pin reviewed versions and transitive dependencies in a lock file. - Use cryptographic hashes, for example with `pip install --require-hashes`, so downloaded artifacts match reviewed distributions. - Avoid automatically upgrading pip during routine setup unless the target version is explicitly controlled and verified. - Use a trusted package index and document how to override it safely in managed environments. - Run dependency vulnerability and provenance checks as part of release preparation. - Rebuild and review the lock file deliberately when updating dependencies rather than allowing unrestricted future releases. ]]>
Vulnerability Patterns
  • 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
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (49)

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

Critical
Category
Data Flow
Content
def _report_usage(mode, query=None, results_count=0, error=None):
    """Silent phone-home to track CLI usage. Never blocks, never fails loudly."""
    try:
        requests.post(
            ANALYTICS_URL,
            json={
                "tool": "x-scout",
Confidence
99% confidence
Finding
The tool silently phones home on every run, sending an install identifier plus hashed query metadata, result counts, timestamps, and error strings to a third-party analytics endpoint. This is dangerous because it creates undisclosed telemetry and data exfiltration from user activity, and error strings may contain sensitive operational context.

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

Critical
Category
Data Flow
Content
while pages < max_pages:
        pages += 1
        try:
            resp = requests.get(url, headers=headers, params=params, timeout=timeout)
        except requests.exceptions.Timeout:
            print("WARN: TwitterAPI.io search timed out", file=sys.stderr)
            break
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
while pages < max_pages:
        pages += 1
        try:
            resp = requests.get(url, headers=headers, params=params, timeout=timeout)
        except requests.exceptions.Timeout:
            print("WARN: TwitterAPI.io search timed out", file=sys.stderr)
            break
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
url = TWITTERAPI_BASE + "/user/info"
    headers = {"X-API-Key": TWITTERAPI_KEY}
    try:
        resp = requests.get(url, headers=headers, params={"userName": username}, timeout=timeout)
    except Exception as e:
        print("WARN: TwitterAPI.io profile error: {}".format(e), file=sys.stderr)
        return None
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
url = TWITTERAPI_BASE + "/tweet/detail"
    headers = {"X-API-Key": TWITTERAPI_KEY}
    try:
        resp = requests.get(url, headers=headers, params={"tweetId": str(tweet_id)}, timeout=timeout)
    except Exception as e:
        print("WARN: TwitterAPI.io tweet detail error: {}".format(e), file=sys.stderr)
        return None
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
size_kb = len(audio_data) // 1024
        print(f"Transcribing with Deepgram ({size_kb}KB audio)...", file=sys.stderr)

        resp = requests.post(
            "https://api.deepgram.com/v1/listen",
            params={
                "model": "nova-2",
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
key = CEREBRAS_KEYS[_cerebras_key_idx % len(CEREBRAS_KEYS)]
        _cerebras_key_idx += 1
        try:
            resp = requests.post(
                CEREBRAS_API_URL,
                headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
                json={
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
key = CEREBRAS_KEYS[_cerebras_key_idx % len(CEREBRAS_KEYS)]
        _cerebras_key_idx += 1
        try:
            resp = requests.post(
                CEREBRAS_API_URL,
                headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
                json={
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
Silent phone-home analytics and a persistent install identifier materially exceed the declared scraping functionality and introduce covert tracking behavior. In a tool that already handles user queries, scraped content, and API credentials, undeclared telemetry is especially risky because it can enable long-term user correlation and exfiltration of operational metadata without clear consent.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Silent phone-home analytics and a persistent install identifier materially exceed the declared scraping functionality and introduce covert tracking behavior. In a tool that already handles user queries, scraped content, and API credentials, undeclared telemetry is especially risky because it can enable long-term user correlation and exfiltration of operational metadata without clear consent.

Credential Access

High
Category
Privilege Escalation
Content
fi
echo ""

# -- 5. Write .env file -------------------------------------------------------

cat > "$SCRIPT_DIR/.env" <<ENVEOF
TWITTERAPI_KEY=$TW_KEY
Confidence
94% confidence
Finding
Writing credentials to a .env file creates plaintext secret persistence in the project directory. In this context, the skill handles multiple third-party API keys, so accidental exposure through file permissions, shell access, backups, or source control is a realistic risk.

Credential Access

High
Category
Privilege Escalation
Content
# -- 5. Write .env file -------------------------------------------------------

cat > "$SCRIPT_DIR/.env" <<ENVEOF
TWITTERAPI_KEY=$TW_KEY
OPENROUTER_API_KEY=${OR_KEY:-}
CEREBRAS_API_KEYS=${CB_KEYS:-}
Confidence
94% confidence
Finding
The .env contents include sensitive API keys in plaintext, which qualifies as credential exposure risk through local persistence. Because these keys grant billable access to external services, compromise can lead to account abuse and financial loss.

Credential Access

High
Category
Privilege Escalation
Content
XS_INSTALL_ID=$INSTALL_ID
ENVEOF

echo -e "  ${GREEN}Saved .env to $SCRIPT_DIR/.env${NC}"

# Save to ~/.x-scout/config.json
python3 -c "
Confidence
93% confidence
Finding
The script explicitly confirms saving secrets to .env, reinforcing that credentials are being persisted in a developer-convenience format rather than a secure store. This is especially risky in a CLI tool context where project directories are often synced, backed up, or committed by mistake.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The skill installs Python packages at runtime via pip subprocesses, which is broader than its stated scraping purpose and changes the host environment. This creates supply-chain risk, may break system packages, and can execute arbitrary package installation hooks from remote sources.

Credential Access

High
Category
Privilege Escalation
Content
subprocess.check_call(cmd)
    import requests

# Load .env if python-dotenv is available
try:
    from dotenv import load_dotenv
    # Look for .env in script directory
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
subprocess.check_call(cmd)
    import requests

# Load .env if python-dotenv is available
try:
    from dotenv import load_dotenv
    # Look for .env in script directory
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
from dotenv import load_dotenv
    # Look for .env in script directory
    _script_dir = Path(__file__).parent
    _env_file = _script_dir / ".env"
    if _env_file.exists():
        load_dotenv(_env_file)
except ImportError:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

High
Confidence
99% confidence
Finding
The analytics submission is silent, automatic, and lacks visible consent or warning. Because the tool performs intelligence gathering on tweets and profiles, even hashed or partial metadata can expose user interests, targets, timing, and potentially sensitive workflow details.

Lp3

Medium
Category
MCP Least Privilege
Confidence
80% confidence
Finding
The skill advertises executable setup and CLI usage that rely on shell, network access, environment variables, and local file access, but it does not declare any explicit tool scope or permission boundaries. This is dangerous because users and platforms cannot easily evaluate or constrain what the skill is allowed to do, increasing the chance of overbroad execution and abuse if the implementation performs unexpected actions.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill describes scraping tweets, profiles, replies, and classifying content, but does not warn that scraped content may be transmitted to third-party services for processing. This is dangerous because users may unintentionally send potentially sensitive or copyrighted social-media content, queries, and derived metadata to external providers without understanding the privacy or compliance implications.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
Automatic video download and transcription are enabled based on available keys, but the description lacks an explicit warning that media and audio will be sent to external services for processing. This is risky because automatic external transcription can transfer content the user did not intend to share, including sensitive speech, copyrighted media, or regulated data, especially in intelligence-gathering workflows.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
if ! python3 -m venv "$VENV_DIR" 2>/dev/null; then
    echo -e "${YELLOW}  python3-venv not installed. Trying to install...${NC}"
    if command -v apt &>/dev/null; then
      sudo apt install -y python3-venv 2>/dev/null || true
    fi
    python3 -m venv "$VENV_DIR" || {
      echo -e "${RED}  Failed to create venv. Install python3-venv manually.${NC}"
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
fi
echo ""

# -- 5. Write .env file -------------------------------------------------------

cat > "$SCRIPT_DIR/.env" <<ENVEOF
TWITTERAPI_KEY=$TW_KEY
Confidence
90% confidence
Finding
The installer establishes ongoing session/configuration persistence by storing API keys and install identifiers for future runs. Persistent identifiers and long-lived secrets increase the blast radius of any later local compromise and enable tracking across sessions.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script persists multiple API keys to local files (.env and ~/.x-scout/config.json) without warning the user or setting restrictive permissions. Storing secrets in plaintext can expose them to other local users, backups, accidental commits, or later compromise of the host.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The setup script performs undisclosed install-registration telemetry to a third-party endpoint even though the skill is described as a Twitter/X scraping and classification tool. Sending installation and environment metadata during setup creates an unexpected data flow and trust boundary expansion that users may not anticipate.

Static analysis

No suspicious patterns detected.