Back to skill

Security audit

WhatsApp cloud api reference

Security checks for vulnerabilities and agentic risk

Overview

This WhatsApp API reference is mostly purpose-aligned, but it includes copy-paste guidance that can create overpowered long-lived credentials and accidentally message or expose real users.

Install only if you treat it as a rough reference, not production-ready security guidance. Before using it, replace the recipient-validation helper with a non-messaging validation path, avoid logging message bodies or phone numbers, do not put tokens in URLs or shell history, use scoped non-admin credentials where possible, rotate and store secrets properly, and require recipient opt-in before sending.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (6)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:818
Finding
Recipient Validation Sends an Unsolicited WhatsApp Message and Fails Open<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 818-846 **Vulnerability Type**: Validation with unintended external side effects and fail-open exception handling **Risk Level**: High ### Vulnerable Code ```python def is_registered_on_whatsapp(phone_digits: str, token: str, phone_id: str) -> bool: """ Check if number is a registered WhatsApp user. Returns False only on definitive error 131026 (not on WhatsApp). Returns True if successful, uncertain (auth error, etc.), or timeout. """ headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } data = { "messaging_product": "whatsapp", "to": phone_digits, "type": "text", "text": {"body": "_"} # minimal text to check } try: r = requests.post( f"https://graph.facebook.com/v21.0/{phone_id}/messages", headers=headers, json=data, timeout=10 ) if r.status_code == 200: return True # number is valid error_code = r.json().get("error", {}).get("code") if error_code == 131026: return False # NOT on WhatsApp return True # other errors — don't block except: return True # network error — don't block ``` ### Technical Analysis The function is represented as a recipient-validation helper, but it calls the `/messages` endpoint and attempts to deliver an actual `_` message. This is a state-changing messaging operation rather than a read-only validation request. It therefore discloses the supplied telephone number to Meta and can contact a recipient who has not consented to receiving the message. The function also catches every exception and returns `True`. Authentication failures, timeouts, malformed responses, rate limits, and programming errors are consequently treated as successful validation. Downstream code may then send further content to a recipient ...[truncated 906 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove message transmission from recipient-validation logic. - Perform E.164 syntax validation locally and use only an officially documented, consent-preserving verification mechanism. - Require recorded recipient opt-in before any state-changing messaging request. - Fail closed when verification is uncertain. - Catch specific exceptions such as timeout, connection, and JSON-decoding errors; return an explicit indeterminate result rather than `True`. - Separate `valid`, `invalid`, and `verification_failed` states so callers cannot confuse network failure with successful validation. - Add tests confirming that validation never invokes the message-sending endpoint. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
SKILL.md:33
Finding
Permanent Administrative System-User Tokens Exceed Least-Privilege Requirements<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 33-36 and 598-610 **Vulnerability Type**: Excessive and indefinitely valid API privileges **Risk Level**: High ### Vulnerable Guidance ```text 3. Create a permanent System User token: - Meta Business Manager → Settings → System Users → Create Admin user - Assign permissions: whatsapp_business_messaging + whatsapp_business_management - Generate token — this never expires ``` ```text Fix — create a non-expiring System User token: 1. Meta Business Manager → Settings → System Users 2. Create Admin system user 3. Add whatsapp_business_messaging + whatsapp_business_management permissions 4. Generate token — never set an expiry ``` ### Technical Analysis The guidance recommends an administrative system user and a token that never expires. It does not require limiting the system user to the specific WhatsApp Business Account and phone-number assets needed by the application, nor does it define rotation, revocation, or credential-lifetime controls. Messaging permission may be necessary for the declared functionality, and management permission may be needed for some setup operations. However, permanently combining both permissions under an administrative identity exceeds the minimum privilege needed by a runtime component that only sends messages. ### Attack Path 1. An operator follows the guidance and creates an administrative system user. 2. The operator generates a non-expiring token with messaging and management access. 3. The token is placed in an environment file, deployment configuration, command, or application runtime. 4. An attacker obtains it through application compromise, exposed logs, shell history, backups, or developer workstation access. 5. Because the token does not expire, the attacker retains access until manual revocation. 6. The attacker uses the assigned WhatsApp assets to send messages or perform permitted management operations. ### Impact Assessment A compro ...[truncated 331 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create a dedicated non-administrative system identity for the application. - Assign only the specific WABA and phone-number assets required by that deployment. - Separate runtime messaging credentials from setup or management credentials. - Prefer bounded token lifetimes where the platform permits them. - Establish documented rotation and emergency-revocation procedures. - Store credentials in a managed secret store rather than source files or ordinary `.env` files. - Audit token use and alert on unexpected assets, locations, or message volume. - Explicitly explain why each requested permission is needed and omit management permission from send-only deployments. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:959
Finding
Access Tokens and Application Secrets Are Embedded in Request URLs<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 500-501, 598-603, 959-971, and 998 **Vulnerability Type**: Credential disclosure through URL query parameters and command arguments **Risk Level**: High ### Vulnerable Code ```bash curl "https://graph.facebook.com/debug_token?input_token=TOKEN&access_token=TOKEN" ``` ```bash curl "https://graph.facebook.com/debug_token?input_token=YOUR_TOKEN&access_token=YOUR_APP_ID|YOUR_APP_SECRET" ``` ```python TOKEN = os.getenv('WHATSAPP_TOKEN') r = requests.get( f"https://graph.facebook.com/debug_token?input_token={TOKEN}&access_token={TOKEN}" ) data = r.json() ``` ### Technical Analysis The debug endpoint requires sensitive values, but the examples interpolate those values directly into a URL. URLs and shell command arguments are frequently captured in shell history, process inspection, HTTP client diagnostics, proxy logs, monitoring products, crash reports, and URL telemetry. The Python example also reuses the inspected WhatsApp token as the debugger access token. This unnecessarily repeats the same sensitive value in two query parameters and does not maintain separation between the credential being inspected and the credential authorizing inspection. TLS protects the request while in transit to Meta, but it does not prevent disclosure by local history, process, application, proxy, or observability systems. ### Attack Path 1. A user substitutes a real access token or `APP_ID|APP_SECRET` value into the documented command. 2. The user runs the command or diagnostic script. 3. The complete URL is retained by shell history, process monitoring, debug logging, a proxy, or application telemetry. 4. An attacker or unauthorized operator with access to that data extracts the credential. 5. The credential is replayed against Meta APIs within its permissions and lifetime. ### Impact Assessment Exposure can permit unauthorized WhatsApp messaging, account inspection, or management operations accordi ...[truncated 195 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not place literal credentials in copied shell commands. - Load credentials from a managed secret provider and ensure diagnostic tooling redacts query parameters. - Use the distinct authorization credential type required by Meta rather than reusing the token under inspection. - Disable command echoing and avoid saving sensitive invocations in shell history. - Configure reverse proxies, HTTP clients, APM products, and error reporting to redact `input_token`, `access_token`, and similar fields. - Add prominent warnings that debug endpoint URLs contain secrets and must never be shared or logged. - Rotate any credential that may already have appeared in command history or telemetry. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:47
Finding
Third-Party Dependencies Are Installed Without Version or Integrity Controls<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 47, 70, 698, and 963 **Vulnerability Type**: Unpinned and non-reproducible dependency installation **Risk Level**: Medium ### Vulnerable Code ```javascript // npm install axios const axios = require('axios'); ``` ```python # pip install requests import requests, os ``` ```javascript // npm install limiter const { RateLimiter } = require('limiter'); ``` ```python from dotenv import load_dotenv ``` ### Technical Analysis The installation examples resolve package versions dynamically from public registries. No reviewed versions, lockfiles, package hashes, registry restrictions, or integrity-verification process are specified. The final helper also imports `python-dotenv` without documenting a controlled version. The named packages are not shown to be malicious. The vulnerability is the absence of supply-chain controls: future installation results can differ from those audited, and a compromised or incompatible release could enter the environment automatically. ### Attack Path 1. A user follows the installation commands in a new environment. 2. The package manager resolves whatever release is current at that time. 3. A compromised package release, dependency, or otherwise unsafe update is selected. 4. Package installation scripts or imported runtime code execute with the privileges of the installer or application. 5. The compromised dependency can access application data and WhatsApp credentials available to that process. ### Impact Assessment The resulting privileges are those of the package installation and application process. Potential exposure includes environment variables, WhatsApp access tokens, recipient data, message content, and network access. Actual exploitation requires a compromised or unsafe dependency release; no such release was identified in the audited file. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Pin reviewed dependency versions. - Commit and enforce `package-lock.json` or another supported JavaScript lockfile. - Use hashed, locked Python requirements where practical. - Install from approved registries and reject unexpected registry substitutions. - Run package-manager audit and software-composition-analysis checks in CI. - Review lockfile changes before upgrades. - Disable dependency installation scripts where they are unnecessary and supported. - Explicitly declare and pin `python-dotenv` if the helper requires it. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:202
Finding
Webhook Handler Logs Private Message Bodies and Sender Identifiers<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 202-217 **Vulnerability Type**: Plaintext logging of personal identifiers and message content **Risk Level**: Medium ### Vulnerable Code ```javascript async function processWebhookAsync(body) { body.entry?.forEach(entry => entry.changes?.forEach(change => { const value = change.value; // Incoming messages if (value.messages) { value.messages.forEach(msg => { console.log(`Message from ${msg.from}: ${msg.text?.body}`); handleMessage(msg); }); } // Delivery status if (value.statuses) { value.statuses.forEach(status => { console.log(`Message ${status.id} status: ${status.status}`); handleDeliveryStatus(status); }); } }) ); } ``` ### Technical Analysis The webhook example writes the sender's telephone identifier and complete text-message body to standard output. Production platforms commonly forward standard output to persistent centralized logging systems. Message content is not needed to confirm webhook processing and may contain private, regulated, or authentication-related information. The code provides no masking, consent boundary, retention limit, or environment-dependent control. It therefore increases the number of systems and operators that can access WhatsApp conversation data. ### Attack Path 1. A WhatsApp user sends a private message to the business. 2. Meta delivers the message through the configured webhook. 3. The handler writes the sender identifier and full body to standard output. 4. Deployment infrastructure exports and retains the log. 5. An attacker or unauthorized operator with log access reads or exports the conversation data. ### Impact Assessment The issue exposes personal telephone identifiers and potentially sen ...[truncated 281 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not log message bodies by default. - Log only minimal event metadata, such as an internal correlation identifier and processing outcome. - Mask or irreversibly hash telephone identifiers when correlation is required. - Ensure diagnostic content logging is explicit, temporary, access-controlled, and disabled in production. - Apply encryption, least-privilege access, retention limits, and deletion policies to centralized logs. - Add automated checks that reject sensitive webhook fields from structured log payloads. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:635
Finding
Predictable Two-Step Verification PIN Is Used in Executable Setup Examples<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 635-649 **Vulnerability Type**: Weak security credential in copy-and-paste commands **Risk Level**: Medium ### Vulnerable Code ```bash # Set 2FA PIN via API curl -X POST \ "https://graph.facebook.com/v21.0/PHONE_NUMBER_ID/two_step_verification" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"pin": "123456"}' # any 6-digit PIN you choose ``` ```bash curl -X POST \ "https://graph.facebook.com/v21.0/PHONE_NUMBER_ID/register" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"messaging_product": "whatsapp", "pin": "123456"}' # same PIN from step 2 ``` ### Technical Analysis The executable setup commands use the predictable sequence `123456` as a two-step verification PIN and describe it as an acceptable six-digit choice. Users frequently execute documentation examples without changing placeholder values, making this a realistic weak-credential configuration risk. The PIN is also embedded directly in a command, which can expose it through shell history and process-command inspection. ### Attack Path 1. An operator copies both setup commands without replacing `123456`. 2. The WhatsApp phone registration is protected by a publicly documented and highly predictable PIN. 3. The command and PIN may additionally remain in shell history or process telemetry. 4. An attacker who can initiate or interfere with relevant registration operations guesses or retrieves the PIN. 5. The weak second factor provides substantially less protection than intended. ### Impact Assessment The issue weakens protection around WhatsApp phone-number registration and two-step verification. Exploitation still depends on the attacker's ability to reach the relevant account or registration workflow; the PIN alone does not grant local system access or automatically disclose the API token. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Replace the fixed value with a clear non-executable placeholder such as `GENERATED_6_DIGIT_PIN`. - Generate the PIN using a cryptographically secure random-number generator. - Reject common values and sequences such as `123456`, repeated digits, dates, and telephone-number fragments. - Supply the PIN through a protected secret mechanism rather than a literal shell argument. - Prevent shell history and process-monitoring systems from retaining the PIN. - Store the PIN in a managed secret store with restricted access and documented recovery procedures. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (11)

Missing User Warnings

Medium
Confidence
81% confidence
Finding
The skill is a markdown reference file, so SQP-2 for markdown applies when the description omits warnings about behaviors affecting user data or privacy. Multiple examples transmit phone numbers, message bodies, and related metadata to Meta's Cloud API, but the document does not explicitly disclose that using these patterns shares user communications data with a third-party service.

External Transmission

Medium
Category
Data Exfiltration
Content
import requests, os

def send_message(phone: str, text: str) -> dict:
    r = requests.post(
        f"https://graph.facebook.com/v21.0/{os.environ['WA_PHONE_NUMBER_ID']}/messages",
        headers={"Authorization": f"Bearer {os.environ['WA_ACCESS_TOKEN']}"},
        json={
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
return r.json()
```

### Quick test via curl

```bash
curl -X POST "https://graph.facebook.com/v21.0/YOUR_PHONE_ID/messages" \
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
92% confidence
Finding
The webhook example logs full incoming message bodies and sender numbers directly to console. In production this can expose sensitive user content and phone numbers into centralized logs, third-party log processors, developer terminals, and long-retention observability systems.

External Transmission

Medium
Category
Data Exfiltration
Content
**Step 1 — check phone number status:**

```bash
curl "https://graph.facebook.com/v21.0/PHONE_NUMBER_ID?fields=verified_name,code_verification_status,quality_rating,status" \
  -H "Authorization: Bearer YOUR_TOKEN"
```
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Run this to check if a number has WhatsApp before sending:

```bash
curl "https://graph.facebook.com/v21.0/PHONE_NUMBER_ID/contacts" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -X POST \
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The helper is presented as a WhatsApp-registration check, but it performs a real `/messages` send with body `_`. That can contact end users without consent, create unintended outbound traffic/costs, violate the 24-hour/template policy, and produce false assumptions because many non-131026 failures are treated as success.

External Transmission

Medium
Category
Data Exfiltration
Content
"text": {"body": "_"}  # minimal text to check
    }
    try:
        r = requests.post(
            f"https://graph.facebook.com/v21.0/{phone_id}/messages",
            headers=headers,
            json=data,
Confidence
98% confidence
Finding
This external transmission occurs inside the misleading registration-check helper and sends a real message as a side effect of validation. Because the code defaults to True on most errors, it can both leak content externally and silently message users when the caller believes it is only checking eligibility.

External Transmission

Medium
Category
Data Exfiltration
Content
}

    try:
        r = requests.post(url, headers=headers, json=data)
        r.raise_for_status()
        return {"success": True, "message_id": r.json().get("messages")[0].get("id")}
    except requests.HTTPError as e:
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
}

    try:
        r = requests.post(url, headers=headers, json=data)
        r.raise_for_status()
        return {"success": True, "message_id": r.json().get("messages")[0].get("id")}
    except requests.HTTPError as e:
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
}

    try:
        r = requests.post(url, headers=headers, json=data)
        r.raise_for_status()
        return {"success": True, "message_id": r.json().get("messages")[0].get("id")}
    except requests.HTTPError as e:
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.