Back to skill

Security audit

Mentions Full

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a coherent MentionsAPI integration, but its persistent paid webhook monitor accepts unsafe webhook destinations despite documenting HTTPS-only use.

Install only if you are comfortable giving the skill your MentionsAPI key and paying per API call. Use `watch_brand` carefully: provide only an HTTPS webhook endpoint you control, use a strong secret, verify HMAC signatures, and confirm how to disable monitors to avoid ongoing charges.

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

Warning
Location
handler.py:176
Finding

Non-HTTPS Webhook URLs Are Accepted for Sensitive Monitoring Events

Content
View full analysis

Vulnerability Details

File Location: handler.py, lines 176–203
Vulnerability Type: Insufficient validation of a sensitive-data destination
Risk Level: Medium

The watch_brand function documents that the webhook must use HTTPS, but it only verifies that webhook_url is nonempty. It forwards arbitrary URL values to MentionsAPI when creating a persistent monitor.

python
if not query or not brand:
    return {"error": "invalid_argument", "detail": "query and brand are required."}
if not webhook_url or not webhook_secret:
    return {
        "error": "invalid_argument",
        "detail": "webhook_url and webhook_secret are required.",
    }
if len(webhook_secret) < 16:
    return {
        "error": "invalid_argument",
        "detail": "webhook_secret must be at least 16 characters.",
    }
return _post(
    "/v1/watch",
    {
        "query": query,
        "brand": brand,
        "mode": mode,
        "interval": interval,
        "webhook_url": webhook_url,
        "webhook_secret": webhook_secret,
        "trigger_on": trigger_on,
    },
)

Technical Analysis

Brand-monitoring events can contain sensitive competitive intelligence, including mention status, rankings, citations, and changes over time. The project documentation therefore requires an HTTPS webhook endpoint. However, the implementation does not parse the URL, verify its scheme, reject embedded credentials, or confirm that it contains a valid hostname.

Consequently, an http:// destination or another malformed or unsupported URL is submitted to the remote API. If MentionsAPI accepts an HTTP destination, subsequent webhook events can travel without transport encryption. HMAC signing only authenticates the message and protects its integrity; it does not provide confidentiality and does not independently prevent replay attacks.

The primary API credential is not affected by this issue: _post sends i ...[truncated 1831 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse webhook_url with urllib.parse.urlsplit.
  2. Require the scheme to be exactly https, using a case-normalized comparison.
  3. Require a nonempty, syntactically valid hostname.
  4. Reject URLs containing embedded usernames or passwords.
  5. Reject fragments and unsupported URL forms.
  6. If MentionsAPI does not support internal destinations, reject loopback, link-local, private, multicast, and otherwise non-public addresses. Apply equivalent checks after DNS resolution to mitigate hostname-based bypasses and DNS rebinding.
  7. Retain equivalent server-side validation at MentionsAPI so clients cannot bypass the Skill's checks.
  8. Add tests confirming rejection of http://, credential-bearing, malformed, loopback, and private-network webhook URLs, along with acceptance of a valid public HTTPS URL.
  9. Continue requiring HMAC signatures and document that receivers must validate the signature, timestamp, and nonce to prevent tampering and replay.
  10. Provide monitor-management functionality or documentation so users can promptly revoke incorrectly configured persistent monitors and stop recurring charges.

A minimal local validation pattern is:

python
from urllib.parse import urlsplit

parsed = urlsplit(webhook_url)
if (
    parsed.scheme.lower() != "https"
    or not parsed.hostname
    or parsed.username is not None
    or parsed.password is not None
    or parsed.fragment
):
    return {
        "error": "invalid_argument",
        "detail": "webhook_url must be a valid HTTPS URL without credentials or fragments.",
    }
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (9)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · handler.py (reported line 97)May include surrounding context.

python
"Accept": "application/json",
            },
        )
        with urllib.request.urlopen(req, timeout=TIMEOUT_SECONDS) as resp:
            raw = resp.read().decode("utf-8")
            return json.loads(raw) if raw else {}
    except urllib.error.HTTPError as e:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill declares access to an API key and clearly expects network use, but it does not declare any explicit tool scope such as permissions or allowed-tools. That weakens least-privilege enforcement and makes it harder for a host platform to restrict what the skill may access, increasing the blast radius if the skill is misused or extended unexpectedly.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
80% confidence
Finding

Referencing an MCP server via npx @mentionsapi/mcp without pinning an exact version allows consumers to fetch whatever package version is current at execution time. If the package is updated maliciously, compromised upstream, or changed incompatibly, users could run unreviewed code with network and environment access.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · tests/test_handler.py (reported line 63)May include surrounding context.

python
mock_urlopen.return_value = _mock_response(b"{}")
        with patch.dict(os.environ, {"MENTIONSAPI_KEY": "lvk_test"}):
            handler.check_mentions(query="best CRM", brand="HubSpot", mode="all_live")
        self.assertEqual(_req(mock_urlopen).full_url, "https://api.mentionsapi.com/v1/check")
        body = _sent_body(mock_urlopen)
        self.assertEqual(body["mode"], "all_live")
        self.assertNotIn("providers", body)

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · tests/test_handler.py (reported line 73)May include surrounding context.

python
mock_urlopen.return_value = _mock_response(b"{}")
        with patch.dict(os.environ, {"MENTIONSAPI_KEY": "lvk_test"}):
            handler.check_mentions(query="best CRM", brand="HubSpot", mode="all_live")
        self.assertEqual(_req(mock_urlopen).full_url, "https://api.mentionsapi.com/v1/check")
        body = _sent_body(mock_urlopen)
        self.assertEqual(body["mode"], "all_live")
        self.assertNotIn("providers", body)

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · tests/test_handler.py (reported line 81)May include surrounding context.

python
mock_urlopen.return_value = _mock_response(b"{}")
        with patch.dict(os.environ, {"MENTIONSAPI_KEY": "lvk_test"}):
            handler.check_mentions(query="best CRM", brand="HubSpot", mode="all_live")
        self.assertEqual(_req(mock_urlopen).full_url, "https://api.mentionsapi.com/v1/check")
        body = _sent_body(mock_urlopen)
        self.assertEqual(body["mode"], "all_live")
        self.assertNotIn("providers", body)

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · tests/test_handler.py (reported line 98)May include surrounding context.

python
mock_urlopen.return_value = _mock_response(b"{}")
        with patch.dict(os.environ, {"MENTIONSAPI_KEY": "lvk_test"}):
            handler.check_mentions(query="best CRM", brand="HubSpot", mode="all_live")
        self.assertEqual(_req(mock_urlopen).full_url, "https://api.mentionsapi.com/v1/check")
        body = _sent_body(mock_urlopen)
        self.assertEqual(body["mode"], "all_live")
        self.assertNotIn("providers", body)

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · tests/test_handler.py (reported line 117)May include surrounding context.

python
mock_urlopen.return_value = _mock_response(b"{}")
        with patch.dict(os.environ, {"MENTIONSAPI_KEY": "lvk_test"}):
            handler.check_mentions(query="best CRM", brand="HubSpot", mode="all_live")
        self.assertEqual(_req(mock_urlopen).full_url, "https://api.mentionsapi.com/v1/check")
        body = _sent_body(mock_urlopen)
        self.assertEqual(body["mode"], "all_live")
        self.assertNotIn("providers", body)

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · tests/test_handler.py (reported line 127)May include surrounding context.

python
mock_urlopen.return_value = _mock_response(b"{}")
        with patch.dict(os.environ, {"MENTIONSAPI_KEY": "lvk_test"}):
            handler.check_mentions(query="best CRM", brand="HubSpot", mode="all_live")
        self.assertEqual(_req(mock_urlopen).full_url, "https://api.mentionsapi.com/v1/check")
        body = _sent_body(mock_urlopen)
        self.assertEqual(body["mode"], "all_live")
        self.assertNotIn("providers", body)

Static analysis

No suspicious patterns detected.