Back to skill

Security audit

EngageLab Email

Security checks for vulnerabilities and agentic risk

Overview

The skill has a legitimate email-sending purpose, but its bundled script mishandles API credentials and allows overly broad external sending behavior.

Review this skill carefully before installing or using it with real EngageLab credentials. Do not use production API keys until the send script is fixed to run, restrict destinations to documented EngageLab endpoints, keep Authorization out of message payloads and logs, add explicit confirmation for live sends, and document privacy expectations for email tracking and attachments.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/send_email.py:6
Finding
Caller-Controlled API Endpoint Can Expose EngageLab Credentials## Vulnerability Details **File Location**: `scripts/send_email.py`, lines 6-16 and 60 **Vulnerability Type**: Unrestricted destination for authenticated network requests **Risk Level**: High ### Vulnerable Code ```python def send_engagelab_email(api_user, api_key, from_address, to_addresses, subject, html_content=None, text_content=None, preview_text=None, cc_addresses=None, bcc_addresses=None, reply_to_addresses=None, vars_data=None, dynamic_vars_data=None, label_id=None, label_name=None, headers=None, attachments=None, settings=None, custom_args=None, request_id=None, data_center_url="https://email.api.engagelab.cc"): # Default to Singapore data center auth_string = f"{api_user}:{api_key}" encoded_auth = base64.b64encode(auth_string.encode('utf-8')).decode('utf-8') url = f"{data_center_url}/v1/mail/send" headers = { "Content-Type": "application/json;charset=utf-8", "Authorization": f"Basic {encoded_auth}" } # ... response = requests.post(url, headers=headers, data=json.dumps(payload)) ``` ### Technical Analysis The function accepts an unrestricted `data_center_url` and sends an HTTP Basic Authorization header to the resulting URL. It does not require HTTPS or verify that the normalized destination hostname belongs to EngageLab. Base64 is the encoding required by HTTP Basic authentication and is not encryption. Anyone controlling the destination server can decode the header and recover `api_user` and `api_key`. Allowing arbitrary destinations is unnecessary because the documented API specification identifies fixed EngageLab data-center endpoints. This behavior exceeds minimum privilege: the Skill needs network access to EngageLab's email API, but it does not need permission to disclose credentials to arbitrary caller-selected hosts. ### Attack Path 1 ...[truncated 1275 chars]
Remediation
## Remediation Suggestions - Replace free-form `data_center_url` input with an enumeration of supported regions mapped internally to exact documented endpoints. - Allow only normalized HTTPS URLs whose host and port exactly match an explicit allowlist, such as: - `email.api.engagelab.cc` - `emailapi-tr.engagelab.com` - Reject embedded credentials, unexpected ports, redirects to unapproved hosts, non-HTTPS schemes, and hostname suffix tricks. - Disable redirects or validate every redirect destination before forwarding the Authorization header. - Keep TLS certificate verification enabled. - Prefer a scoped and revocable credential with only the permissions required to send email. - Add tests confirming that arbitrary, malformed, non-HTTPS, and look-alike destinations are rejected before any request is sent.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/send_email.py:6
Finding
Variable Shadowing Copies the Authorization Credential into the Email Payload## Vulnerability Details **File Location**: `scripts/send_email.py`, lines 6-18, 42-43, and 60 **Vulnerability Type**: Sensitive credential exposure caused by parameter shadowing **Risk Level**: High ### Vulnerable Code ```python def send_engagelab_email(api_user, api_key, from_address, to_addresses, subject, html_content=None, text_content=None, preview_text=None, cc_addresses=None, bcc_addresses=None, reply_to_addresses=None, vars_data=None, dynamic_vars_data=None, label_id=None, label_name=None, headers=None, attachments=None, settings=None, custom_args=None, request_id=None, data_center_url="https://email.api.engagelab.cc"): auth_string = f"{api_user}:{api_key}" encoded_auth = base64.b64encode(auth_string.encode('utf-8')).decode('utf-8') url = f"{data_center_url}/v1/mail/send" headers = { "Content-Type": "application/json;charset=utf-8", "Authorization": f"Basic {encoded_auth}" } # ... if headers: mail_body["headers"] = headers # ... response = requests.post(url, headers=headers, data=json.dumps(payload)) ``` ### Technical Analysis The `headers` argument is intended to represent custom email headers, but it is overwritten with the HTTP request headers. The subsequent `if headers` branch therefore always adds the following structure to the JSON email body: ```json { "headers": { "Content-Type": "application/json;charset=utf-8", "Authorization": "Basic BASE64_API_USER_AND_KEY" } } ``` As a result, the authentication credential is transmitted twice: once in its intended HTTP header and once unnecessarily inside the message payload. This expands credential exposure to request-body logging, API validation diagnostics, payload inspection systems, and any downstream processing applied to custom email headers. It ...[truncated 1719 chars]
Remediation
## Remediation Suggestions - Use distinct names for the two concepts, such as `email_headers` for caller-supplied message headers and `request_headers` for HTTP transport headers. - Build the payload only from `email_headers`; never copy `Authorization`, cookies, API keys, or other transport credentials into the JSON body. - Consider rejecting sensitive or reserved names in custom email headers, including `Authorization`, to prevent accidental secret propagation. - Add a regression test that serializes the complete payload and verifies it contains neither the API key nor any `Authorization` field. - Add a test confirming that caller-provided custom email headers are preserved correctly. - Rotate any real credential used with this implementation if request bodies may have been logged or retained. - Review and sanitize API gateway, application, and diagnostic logs for historical payload copies containing Basic credentials.

other

Note
Location
scripts/send_email.py:59
Finding
Exception Handler Can Reference an Uninitialized Response Variable## Vulnerability Details **File Location**: `scripts/send_email.py`, lines 59-68 **Vulnerability Type**: Improper exception handling **Risk Level**: Low ### Vulnerable Code ```python try: response = requests.post(url, headers=headers, data=json.dumps(payload)) response.raise_for_status() # Raise an exception for HTTP errors return response.json() except requests.exceptions.RequestException as e: print(f"Error sending email: {e}") if response is not None: print(f"Response status code: {response.status_code}") print(f"Response body: {response.text}") return None ``` ### Technical Analysis The local variable `response` is assigned only after `requests.post()` returns. If that call raises a `RequestException` before returning—for example, due to a DNS failure, refused connection, TLS error, or timeout—the exception handler attempts to evaluate `response` before it has been assigned. Python then raises `UnboundLocalError`, masking the original network exception and bypassing the intended `None` return. This is a reliability and error-handling flaw rather than a demonstrated path to unauthorized privileges. ### Attack Path 1. The function attempts to send an email. 2. The HTTP operation raises `RequestException` before assigning a response object. 3. Execution enters the exception handler. 4. The condition `if response is not None` references an uninitialized local variable. 5. Python raises `UnboundLocalError`, obscuring the original failure and potentially terminating the caller's workflow. An attacker who can influence the destination or network conditions could trigger this failure, but the reviewed code does not establish an additional privilege gain from doing so. ### Impact Assessment The issue can cause denial of the current email operation, misleading diagnostics, and unexpected termination in integrations that rely on the documented `None` failure behav ...[truncated 106 chars]
Remediation
## Remediation Suggestions Initialize the variable before entering the protected block and retain the original exception context: ```python response = None try: response = requests.post( url, headers=request_headers, json=payload, timeout=30, ) response.raise_for_status() return response.json() except requests.exceptions.RequestException as exc: print(f"Error sending email: {exc}") if response is not None: print(f"Response status code: {response.status_code}") print(f"Response body: {response.text}") return None ``` Also configure an explicit timeout and use structured, redacted logging so that response bodies do not inadvertently expose sensitive data.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (6)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
A description-behavior mismatch is a security-relevant integrity problem because users and orchestrators may trust the declared functionality while the actual implementation does something else or nothing at all. Here, the skill claims to send emails with templates, variables, attachments, and settings, but the detected implementation is only a placeholder; this can mask broken controls, mislead operators about what data is transmitted, and create space for later substitution of unexpected behavior.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises email-sending behavior and references a Python script that performs API calls, but it does not declare any explicit tool scope or permissions for network and file-write capabilities. In an agent environment, undeclared outbound network access and file operations reduce transparency and can enable unintended data exfiltration or unsafe side effects without clear user or platform controls.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This skill is explicitly designed to transmit content to an external email service, yet the description does not clearly warn that prompts, recipient addresses, message bodies, and attachments may leave the local environment. In agent workflows, missing disclosure increases the risk of accidental exfiltration of sensitive or regulated data because users may not realize an external API and email channel are involved.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The API spec explicitly exposes open, click, and unsubscribe tracking capabilities without any warning about consent, disclosure, or privacy/legal implications. In a skill whose purpose is to send emails, this can normalize privacy-invasive usage and lead downstream agents or users to enable tracking by default without understanding compliance obligations.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This code performs an outbound HTTP POST to an email API containing message content, sender/recipient addresses, and optional attachments, but there is no confirmation prompt or user-facing notice before transmission. Aside from an internal error print, the file does not disclose that user or system data will be sent to an external service.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The function hard-codes a default Singapore data center URL and comments that this is the default, which imposes a region choice without any explicit user opt-in or documented justification. This can violate language/locale policy expectations when a skill silently selects a specific regional service endpoint.

Static analysis

No suspicious patterns detected.