Back to skill

Security audit

FreeSmsGateway

Security checks for vulnerabilities and agentic risk

Overview

The skill has a plausible SMS-gateway purpose, but it handles SMS content and credentials with weak network and webhook safeguards.

Review carefully before installing. Use only a trusted HTTPS or protected local/VPN gateway, avoid public ngrok exposure unless you add authentication, disable or tightly control OPENCLAW_WEBHOOK_URL forwarding, protect .env and .token.json permissions, and remember this skill can send SMS, read message data, and change webhook configuration.

Vulnerability Patterns
  • 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
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/auth.py:75
Finding
Gateway credentials and SMS data may be transmitted over plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/auth.py:75-88`, `scripts/auth.py:103-121`, `SKILL.md:23`, `README.md:39`, `env.example.txt:1` **Vulnerability Type**: Transmission of sensitive information over an unencrypted channel **Risk Level**: High ### Vulnerable Code ```python base_url, username, password = _get_config() creds = base64.b64encode(f"{username}:{password}".encode()).decode() req = urllib.request.Request( f"{base_url}/auth/token", data=json.dumps({'ttl': 3600, 'scopes': ['messages:send', 'messages:read']}).encode(), headers={ 'Authorization': f'Basic {creds}', 'Content-Type': 'application/json' }, method='POST' ) try: with urllib.request.urlopen(req, timeout=10) as resp: ``` Subsequent authenticated API requests use the same configurable base URL: ```python headers = { 'Authorization': f'Bearer {token}', 'Accept': 'application/json' } body = None if data is not None: body = json.dumps(data).encode() headers['Content-Type'] = 'application/json' req = urllib.request.Request( f"{base_url}{path}", data=body, headers=headers, method=method ) with urllib.request.urlopen(req, timeout=30) as resp: return json.loads(resp.read().decode()) ``` The supplied configuration and documentation encourage plaintext HTTP: ```text SMS_GATE_URL=http://192.168.50.69:8080 ``` ### Technical Analysis The Base64 operation is normal HTTP Basic Authentication encoding. It does not print or otherwise expose the credentials to standard output, and it is necessary for the gateway's documented authentication flow. It is therefore not, by itself, a covert exfiltration channel. However, Base64 provides no confidentiality. When `SMS_GATE_URL` uses `http://`, the Basic Authorization header containing the username and password is sent without transport encryption. Bearer tokens, SMS destination numbers, message content, message history, and webhook management requests are also s ...[truncated 1474 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require `https://` for `SMS_GATE_URL` by default. 2. Permit plaintext HTTP only for loopback addresses or through an explicit, prominently documented insecure-development override. 3. Reject unsupported schemes and validate the parsed host before making requests. 4. Use a properly validated TLS certificate for gateway connections. Do not disable certificate verification. 5. For gateways that cannot provide TLS directly, document a trusted TLS reverse proxy, VPN, or authenticated tunnel. 6. Warn users that a local network alone does not provide confidentiality against compromised peers, access points, or routers. 7. Consider using separate, narrowly scoped credentials and short-lived tokens so compromise does not grant broader gateway administration rights. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/auth.py:54
Finding
JWT bearer token is cached without explicit restrictive file permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/auth.py:54-66` **Vulnerability Type**: Insecure storage of authentication material **Risk Level**: Medium ### Vulnerable Code ```python def _write_cached_token(token_data): """Cache the token response to disk.""" from datetime import datetime expires_at = token_data.get('expires_at', '') try: # Parse ISO 8601 with timezone dt = datetime.fromisoformat(expires_at) expires_epoch = dt.timestamp() except Exception: expires_epoch = time.time() + 3600 cache = { 'access_token': token_data['access_token'], 'expires_epoch': expires_epoch, 'id': token_data.get('id', '') } with open(_TOKEN_CACHE, 'w') as f: json.dump(cache, f) ``` ### Technical Analysis The access token is a bearer credential: any party that obtains it can exercise its scopes until it expires. The cache is created using ordinary `open(..., 'w')`, so its effective permissions depend on the process umask and existing file metadata. The code does not explicitly enforce owner-only permissions, validate file ownership, reject symbolic links, or write the token atomically. On a multi-user system with a permissive umask, the resulting `.token.json` may be readable by other accounts. If an attacker can replace the cache path with a symbolic link, the write may also overwrite another file accessible to the Skill process. The cache is located in the project root, where permissions may be broader than those of a dedicated per-user credential directory. ### Attack Path 1. The Skill authenticates to the gateway and receives a bearer token with `messages:send` and `messages:read` scopes. 2. `_write_cached_token` writes the token to `.token.json` using process-default permissions. 3. A local attacker with access to the project directory reads the file if directory and file permissions permit it. 4. The attacker extracts the `access_token`. 5. The attacker sub ...[truncated 679 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store tokens in a dedicated per-user state directory with mode `0700`. 2. Create the token file atomically with owner-only mode `0600`, such as by using `os.open` with `O_CREAT | O_EXCL` and an explicit mode. 3. Write to a securely created temporary file in the same directory and atomically replace the cache after validation. 4. Reject symbolic links and verify that the cache is a regular file owned by the current user before reading or replacing it. 5. Apply `chmod(0o600)` to an existing cache before use. 6. Remove expired or invalid tokens rather than leaving them on disk. 7. Keep token lifetimes and scopes as narrow as the requested operation allows. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/webhook_server.py:9
Finding
Unauthenticated public webhook receiver forwards attacker-controlled JSON to OpenClaw<![CDATA[ ## Vulnerability Details **File Location**: `scripts/webhook_server.py:9-35`, `scripts/webhook_server.py:56-64` **Vulnerability Type**: Missing webhook authentication, unrestricted network exposure, and unbounded request handling **Risk Level**: High ### Vulnerable Code ```python # OpenClaw webhook endpoint (set via env or default) OPENCLAW_URL = os.environ.get('OPENCLAW_WEBHOOK_URL', 'http://localhost:8080/webhook') class SMSWebhookHandler(BaseHTTPRequestHandler): def do_POST(self): content_length = int(self.headers.get('Content-Length', 0)) body = self.rfile.read(content_length) try: data = json.loads(body) print(f"📥 Incoming SMS webhook received") print(f" Event: {self.path}") print(f" Data: {json.dumps(data, indent=2)}") # Forward to OpenClaw if configured if OPENCLAW_URL != 'disabled': try: req = urllib.request.Request( OPENCLAW_URL, data=body, headers={'Content-Type': 'application/json'}, method='POST' ) urllib.request.urlopen(req, timeout=5) except Exception as e: print(f" ⚠️ Could not forward to OpenClaw: {e}") self.send_response(200) self.end_headers() self.wfile.write(b'{"status":"ok"}') ``` The service binds to every network interface and recommends tunnel exposure: ```python port = int(sys.argv[1]) if len(sys.argv) > 1 else 8787 server = HTTPServer(('0.0.0.0', port), SMSWebhookHandler) print(f"🌐 SMS Webhook Receiver running on http://0.0.0.0:{port}") print(f" Forward to OpenClaw: {OPENCLAW_URL}") print(f"\nConfigure sms-gate.app webhook:") print(f" URL: http://<this-mac-ip>:{port}/sms-received") print(f" Event: sms:received") print(f"\nOr with ngrok ...[truncated 2459 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind to `127.0.0.1` by default and require an explicit option for external interfaces. 2. Require a high-entropy webhook secret or verify a gateway-provided cryptographic signature using constant-time comparison. 3. Validate timestamps and unique event identifiers to prevent replay. 4. Restrict POST handling to the documented endpoint and reject all other paths. 5. Define and validate an expected SMS webhook schema before logging or forwarding data. 6. Enforce a small maximum request size before reading the body and reject missing, invalid, negative, or oversized `Content-Length` values. 7. Add connection timeouts, rate limiting, and concurrency controls. 8. Disable OpenClaw forwarding by default; require explicit configuration of the destination. 9. Authenticate forwarded requests separately so localhost reachability is not treated as authorization. 10. Avoid logging complete SMS bodies by default because they can contain private or attacker-controlled data. 11. When exposing the receiver through ngrok or another tunnel, enable tunnel-layer authentication and restrict allowed clients where possible. ]]>
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
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (26)

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            data = json.loads(resp.read().decode())
            _write_cached_token(data)
            return data['access_token']
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
method=method
    )

    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.loads(resp.read().decode())
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
headers={'Content-Type': 'application/json'},
                        method='POST'
                    )
                    urllib.request.urlopen(req, timeout=5)
                except Exception as e:
                    print(f"   ⚠️ Could not forward to OpenClaw: {e}")
Confidence
93% confidence
Finding
The server forwards full incoming SMS webhook bodies to a URL controlled by the OPENCLAW_WEBHOOK_URL environment variable without validation, restriction, or authentication. Because SMS contents often contain highly sensitive data such as OTPs, personal messages, and phone numbers, this creates a clear exfiltration path and can leak data to any endpoint configured in the environment.

Credential Access

High
Category
Privilege Escalation
Content
4. Copy the example environment file and fill in your values:

```bash
cp .env.example .env
```

5. Edit `.env` with your gateway URL and credentials:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared description presents a broad SMS management capability, including sending messages, checking status, viewing history, and webhook management. The actual code chunk is much narrower: it only receives webhook HTTP requests, prints their contents, and optionally forwards them to a configured OpenClaw endpoint. While this partially aligns with 'manage webhooks for incoming SMS,' it does not implement the other central described capabilities. Additionally, forwarding incoming webhook data to another service is a network behavior not mentioned in the description. Therefore the code chunk does not accurately represent the full declared purpose.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
```
GET    /webhooks
POST   /webhooks        {"event": "sms:received", "url": "https://example.com"}
DELETE /webhooks/{event}
```

Webhook events:
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Credential Access

High
Category
Privilege Escalation
Content
def _load_env():
    """Load .env from the skill root directory."""
    env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), '.env')
    if os.path.exists(env_path):
        with open(env_path) as f:
            for line in f:
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
def _load_env():
    """Load .env from the skill root directory."""
    env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), '.env')
    if os.path.exists(env_path):
        with open(env_path) as f:
            for line in f:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
The code sources a destination URL from an environment variable and uses it to forward incoming SMS webhook contents, enabling transmission to an arbitrary endpoint. In the context of an SMS gateway, this is especially dangerous because the forwarded payload may contain sensitive communications and verification codes, making the behavior functionally equivalent to data exfiltration if misused or misconfigured.

Credential Access

High
Category
Privilege Escalation
Content
#!/usr/bin/env python3
"""Integration tests for SMS Gateway skill.

Requires SMS_GATE_URL, SMS_GATE_USER, and SMS_GATE_PASS in .env or environment.
Tests call the actual scripts in scripts/ as subprocesses.
"""
import json
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
#!/usr/bin/env python3
"""Integration tests for SMS Gateway skill.

Requires SMS_GATE_URL, SMS_GATE_USER, and SMS_GATE_PASS in .env or environment.
Tests call the actual scripts in scripts/ as subprocesses.
"""
import json
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
#!/usr/bin/env python3
"""Integration tests for SMS Gateway skill.

Requires SMS_GATE_URL, SMS_GATE_USER, and SMS_GATE_PASS in .env or environment.
Tests call the actual scripts in scripts/ as subprocesses.
"""
import json
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
#!/usr/bin/env python3
"""Integration tests for SMS Gateway skill.

Requires SMS_GATE_URL, SMS_GATE_USER, and SMS_GATE_PASS in .env or environment.
Tests call the actual scripts in scripts/ as subprocesses.
"""
import json
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
#!/usr/bin/env python3
"""Integration tests for SMS Gateway skill.

Requires SMS_GATE_URL, SMS_GATE_USER, and SMS_GATE_PASS in .env or environment.
Tests call the actual scripts in scripts/ as subprocesses.
"""
import json
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README explicitly instructs users to expose an incoming-SMS webhook via ngrok or a LAN-accessible HTTP endpoint, but it does not warn that SMS contents and sender numbers will be transmitted to that endpoint. This creates a real confidentiality risk because users may unintentionally forward sensitive inbound messages to a public internet URL or an unauthenticated local listener, increasing the chance of interception, logging leakage, or misdelivery.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill requires environment secrets and instructs use of Python scripts, networking, file access, and shell commands, but it does not declare any explicit tool scope or permission boundaries. That creates an over-privileged execution model where an agent may invoke broader capabilities than users expect, increasing the chance of secret exposure, unintended network access, or misuse of local resources.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The documentation encourages sending SMS content and managing message operations without clearly warning that phone numbers, message text, and delivery metadata are sensitive data. Users may disclose personal or regulated information through the skill without understanding the privacy implications or retention/exposure risks.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The webhook instructions recommend exposing inbound SMS via ngrok or other remotely reachable endpoints, but they do not clearly warn that incoming message contents may be transmitted through third-party infrastructure or exposed to the public internet. Because SMS messages often contain highly sensitive data such as OTPs, personal messages, or account notifications, this omission materially increases the risk of interception, misconfiguration, or unintended disclosure.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
This markdown file describes behaviors that transmit phone numbers, message contents, and webhook data over network endpoints, including outbound SMS and callback URLs. It does not include any warning about handling personal data, message disclosure, or the privacy/system implications of configuring webhooks.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The code writes a bearer token cache to .token.json in the skill root without setting restrictive file permissions or protecting the location. On multi-user systems or permissive environments, another local user or process could read the cached token and use it to access SMS read/send capabilities until expiry.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The code reads `SMS_GATE_USER` and `SMS_GATE_PASS`, constructs a Basic Authorization header, and transmits those credentials to a remote `/auth/token` endpoint. While network authentication is part of the helper's purpose, the file does not provide any user-facing notice that local credentials will be sent to an external service.

Missing User Warnings

Medium
Confidence
80% confidence
Finding
This code retrieves message data from an external API and later displays sender phone numbers and message text. While the module docstring says it checks for incoming SMS, it does not explicitly warn that private message contents and identifiers will be fetched and surfaced to the user, which is a user-data/privacy-relevant behavior under the warning rule.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The webhook receiver performs an additional data-forwarding function beyond simply receiving local SMS gateway webhooks, expanding the skill’s behavior beyond its stated role. That mismatch matters because it silently redistributes inbound SMS data to another service, increasing data exposure and trust boundary violations.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The file forwards webhook payloads onward without any in-file warning, consent flow, or disclosure that inbound SMS data will be retransmitted. Lack of transparency around secondary handling of sensitive SMS data increases the chance of unnoticed privacy violations and unsafe deployment by users who assume the tool is only local.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_script(script_name, args=None):
    """Run a script from scripts/ and return (stdout, stderr, returncode)."""
    cmd = ['python3', os.path.join(SCRIPTS_DIR, script_name)] + (args or [])
    result = subprocess.run(cmd, capture_output=True, text=True)
    return result.stdout.strip(), result.stderr.strip(), result.returncode
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.