Back to skill

Security audit

Email Resend

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches a Resend email utility, but it needs Review because its cron setup can persistently send inbound email summaries to a fixed Telegram destination and its attachment downloader has unsafe file and URL handling.

Install only after reviewing and replacing all Telegram destinations with your own values, preferably using the dynamic preferences flow. Do not enable the cron job unless recurring email summaries are intended, and avoid downloading attachments with this version because remote filenames and URLs are not safely constrained.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (4)

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:149
Finding

Hardcoded Telegram Destination Can Disclose Inbound Email Data to an Unrelated Third Party

Content
View full analysis
Remediation
View remediation
:topic:" ``` 3. Prefer `scripts/configure-cron.py` with preferences explicitly supplied by the user. 4. Before creating the job, display the resolved channel, chat ID, and topic and require confirmation. 5. Reject known example values in production configuration. 6. Provide a command that lets users inspect the installed job and its destination. 7. Minimize announced data by default; send only message counts unless the user explicitly enables body previews. 8. Add a test that fails if documentation contains a literal production-style Telegram destination. ]]>

T01 · Skill Instruction Hijacking

Error
Location
scripts/inbound.py:334
Finding

Inbound Email Content Is Passed to an AI Cron Agent Without an Explicit Untrusted-Data Boundary

Content
View full analysis
]+>', '', detail.get("html", "")) attachments = detail.get("attachments", []) if detail else [] body_preview = format_body(body) attachments_str = format_attachments(attachments) # Format notification for agent to deliver notification = format_notification(email_id, subject, from_addr, date, importance, body_preview, attachments_str) # Note: Agent will handle delivery via message tool ``` From `cron-prompts/email-inbound.md:45-63`: ```markdown ### 2. Run Email Checker ``` python3 ~/.openclaw/workspace/skills/email-resend/scripts/inbound.py ``` Parse the JSON output for new emails. ### 3. Apply User Rules Based on preferences found in step 1: - If no telegram preferences found, use current context channel - Apply any user-specified filtering rules (e.g., only notify for HIGH importance) - Use the user's preferred notification channel ### 4. Produce Summary (DO NOT send individual notifications) ⚠️ IMPORTANT: Do NOT try to send individual email notifications via message tool. The cron delivery system will handle delivering the summary to your chat. ``` ### Technical Analysis Email sender values, subjects, plain-text or stripped HTML bodies, and attachment filenames are controlle ...[truncated 2059 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/download_attachment.py:78
Finding

Untrusted Attachment Filenames Permit Directory Traversal and Arbitrary File Overwrite

Content
View full analysis
None: """Download an attachment from the signed URL.""" response = requests.get(url) response.raise_for_status() with open(output_path, "wb") as f: f.write(response.content) print(f"✅ Downloaded: {output_path.name} ({len(response.content)} bytes)") ``` ### Technical Analysis Attachment filenames originate from inbound email/API data and are not sanitized before being joined to the user-selected output directory. `pathlib` does not automatically confine a path containing `..`, and an absolute second operand can replace the intended base directory entirely. Examples include: - `../../.bashrc` - `../memory/email-preferences.md` - `/home/user/.openclaw/workspace/configuration-file` The use of `"wb"` truncates any existing writable file. There is no canonical-path check, filename normalization, collision prevention, or exclusive file creation. ## ...[truncated 1365 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/download_attachment.py:38
Finding

Attachment Downloader Fetches Unvalidated URLs Returned by the API

Content
View full analysis
None: """Download an attachment from the signed URL.""" response = requests.get(url) response.raise_for_status() with open(output_path, "wb") as f: f.write(response.content) print(f"✅ Downloaded: {output_path.name} ({len(response.content)} bytes)") ``` The URL is taken directly from API response data: ```python output_path = output_dir / att["filename"] download_attachment_url(att["download_url"], output_path) ``` For bulk downloads: ```python for att in attachments: output_path = output_dir / att["filename"] try: download_attachment_url(att["download_url"], output_path) except Exception as e: print(f"❌ Failed to download {att['filename']}: {e}") ``` ### Technical Analysis The downloader accepts an arbitrary `download_url` from the attachment metadata and passes it directly to `requests.get`. It does not validate: - The URL scheme. - The hostname or expected Resend attachment domain. - DNS resolution to private, loopback, link-local, or reserved networks. - Redirect destinations. - Response size. - Request duration. The complete response is buffered in memory through `response.content` before being written to disk. A malicious or compromised API response can therefore direct the process toward an internal service or an extremely large/slow response. The initial authenticated API request to Resend is required for declared functionality. The unrestricted secondary request is not required; attachment retrieval can be limited to documented HTTPS hosts and bounded downloads. ### Attack Path 1. An attacker or compromised upstream response causes attachment met ...[truncated 1217 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (39)

Tainted flow: 'headers' from os.environ.get (line 32, credential/environment) → requests.get (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 · scripts/download_attachment.py (reported line 34)May include surrounding context.

python
url = f"{API_BASE}/emails/receiving/{email_id}/attachments"
    headers = {"Authorization": f"Bearer {API_KEY}"}
    
    response = requests.get(url, headers=headers)
    response.raise_for_status()
    return response.json()

Tainted flow: 'API_KEY' from os.environ.get (line 21, credential/environment) → requests.get (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 · scripts/draft-reply.py (reported line 208)May include surrounding context.

python
def fetch_email(email_id):
    """Fetch full email details from Resend."""
    resp = requests.get(
        f"{API_BASE}/emails/receiving/{email_id}",
        headers={"Authorization": f"Bearer {API_KEY}"}
    )

Tainted flow: 'API_KEY' from os.environ.get (line 21, credential/environment) → requests.post (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 · scripts/draft-reply.py (reported line 353)May include surrounding context.

python
}
    }
    
    resp = requests.post(
        f"{API_BASE}/emails",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json=payload

Tainted flow: 'headers' from os.environ.get (line 175, credential/environment) → requests.get (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 · scripts/inbound.py (reported line 180)May include surrounding context.

python
for attempt in range(MAX_RETRIES):
        try:
            resp = requests.get(f"{API_BASE}/emails/receiving", headers=headers, params=params, timeout=30)
            if resp.status_code == 200:
                return resp.json().get("data", [])
            last_error = f"API {resp.status_code}: {resp.text}"

Tainted flow: 'API_KEY' from os.environ.get (line 28, credential/environment) → requests.get (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 · scripts/inbound.py (reported line 199)May include surrounding context.

python
def fetch_email_detail(email_id):
    for attempt in range(MAX_RETRIES):
        try:
            resp = requests.get(f"{API_BASE}/emails/receiving/{email_id}", 
                              headers={"Authorization": f"Bearer {API_KEY}"}, timeout=30)
            if resp.status_code == 200:
                return resp.json()

Tainted flow: 'API_KEY' from os.environ.get (line 26, credential/environment) → requests.get (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 · scripts/outbound.py (reported line 58)May include surrounding context.

python
return email_id
    
    try:
        response = requests.get(
            f"{API_BASE}/emails/{email_id}",
            headers={"Authorization": f"Bearer {API_KEY}"}
        )

Tainted flow: 'API_KEY' from os.environ.get (line 26, credential/environment) → requests.post (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 · scripts/outbound.py (reported line 156)May include surrounding context.

python
if payload_attachments:
            payload["attachments"] = payload_attachments
    
    response = requests.post(
        f"{API_BASE}/emails",
        headers={
            "Authorization": f"Bearer {API_KEY}",

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The documented behavior extends beyond a normal email utility into cron creation/deletion, Telegram routing, and OpenClaw-specific task delivery, none of which are clearly foregrounded in the top-level purpose. Hidden or underemphasized side effects are dangerous because users may invoke an email skill without realizing it can modify scheduled tasks or redirect notifications to external targets.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The documented behavior extends beyond a normal email utility into cron creation/deletion, Telegram routing, and OpenClaw-specific task delivery, none of which are clearly foregrounded in the top-level purpose. Hidden or underemphasized side effects are dangerous because users may invoke an email skill without realizing it can modify scheduled tasks or redirect notifications to external targets.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The documented behavior extends beyond a normal email utility into cron creation/deletion, Telegram routing, and OpenClaw-specific task delivery, none of which are clearly foregrounded in the top-level purpose. Hidden or underemphasized side effects are dangerous because users may invoke an email skill without realizing it can modify scheduled tasks or redirect notifications to external targets.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The documented behavior extends beyond a normal email utility into cron creation/deletion, Telegram routing, and OpenClaw-specific task delivery, none of which are clearly foregrounded in the top-level purpose. Hidden or underemphasized side effects are dangerous because users may invoke an email skill without realizing it can modify scheduled tasks or redirect notifications to external targets.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill declares powerful capabilities in practice—environment access, file reads/writes, network access, and shell execution—but does not declare any explicit tool scope or permissions boundary. That makes it harder for reviewers and runtime policy to constrain what the skill can do, increasing the risk of unexpected file modification, command execution, or data exfiltration if the skill or downstream scripts are abused.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

Triggers like 'send email', 'check emails', 'email me', and 'email notification' are broad everyday phrases that can be matched in benign conversation and cause the skill to activate unexpectedly. Because the skill can read memory, use networked email APIs, and write state, accidental invocation could expose email content, create drafts, or send notifications without sufficiently explicit user intent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The instructions direct storing personal contact data such as sender email, display name, chat ID, and thread ID in a persistent memory file, but do not require an explicit privacy notice or consent flow explaining retention and downstream use. In a messaging/email integration context, this creates avoidable privacy risk because identifiers can be reused by cron jobs and notification routing across sessions without the user fully understanding persistence.

Content

No source excerpt is available for this finding.

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
80% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · cron-prompts/email-inbound.md (reported line 10)May include surrounding context.

Before processing, load the skill's documentation to understand capabilities, rules, and best practices:

text
read path="skills/email-resend/SKILL.md"

Key rules from SKILL.md to follow:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The cron prompt explicitly executes an inbound email-checking script, which necessarily accesses and processes potentially sensitive mailbox contents, but the prompt does not disclose this data access in-band to the user at execution time. In a cron context, this matters because the task may run unattended and process personal communications, attachments, and metadata without a contemporaneous reminder or consent checkpoint.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The document specifies persistent storage of email metadata and message-chain history in local memory files, but it does not mention retention limits, access controls, encryption, or user consent. Because the skill handles inherently sensitive communications data, storing subjects, sender addresses, timestamps, and reply history can expose private information if the host is shared, compromised, or logs/backups are later accessed.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This script configures OpenClaw cron-based Telegram notifications even though the skill is presented primarily as a Resend email integration. That broader operational behavior can surprise users and expands the data-flow surface by automatically forwarding email-related content into another channel, which is especially risky in a messaging/notification skill context.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/configure-cron.py (reported line 119)May include surrounding context.

python
def delete_existing_cron():
    """Delete existing email-resend-inbound cron if it exists"""
    result = subprocess.run(
        ["openclaw", "cron", "list", "--format", "json"],
        capture_output=True,
        text=True

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/configure-cron.py (reported line 133)May include surrounding context.

python
for cron in crons:
            if cron.get('name') == 'email-resend-inbound':
                print(f"Deleting existing cron: {cron.get('id')}")
                subprocess.run(
                    ["openclaw", "cron", "delete", cron.get('id')],
                    check=True
                )

Ssd 3

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The cron message explicitly tells the agent to include newly found emails in its reply, creating a built-in natural-language exfiltration path from inbound email to Telegram/OpenClaw notifications. In the context of an email-handling skill, this is dangerous because sensitive message bodies, personal data, or attachments-derived content may be disclosed automatically to a secondary channel or the wrong recipient if routing is misconfigured.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/configure-cron.py (reported line 155)May include surrounding context.

python
]
    
    print(f"Running: {' '.join(cron_cmd)}")
    result = subprocess.run(cron_cmd, capture_output=True, text=True)
    
    if result.returncode != 0:
        print(f"Error adding cron: {result.stderr}")

Tainted flow: 'cron_cmd' from os.environ.get (line 143, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/configure-cron.py (reported line 155)May include surrounding context.

python
]
    
    print(f"Running: {' '.join(cron_cmd)}")
    result = subprocess.run(cron_cmd, capture_output=True, text=True)
    
    if result.returncode != 0:
        print(f"Error adding cron: {result.stderr}")

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% 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 · scripts/draft-reply.py (reported line 299)May include surrounding context.

python
Reply: send | edit | cancel"""
    
    resp = requests.post(url, json=data)
    if resp.status_code != 200:
        # Fallback to text-only
        return preview_msg

External Transmission

Medium
Category
Data Exfiltration
Confidence
84% confidence
Finding

This is an intentional external transmission of email content to a third-party API, which is core to the skill, but it still carries privacy and data-handling risk because the payload may include sensitive user text and quoted original email content. In the skill context this is expected functionality, so the issue is not the transmission itself but the lack of strong consent/visibility around what leaves the local environment.

Content

Scanner excerpt · scripts/draft-reply.py (reported line 353)May include surrounding context.

python
}
    }
    
    resp = requests.post(
        f"{API_BASE}/emails",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json=payload

Static analysis

No suspicious patterns detected.