Back to skill

Security audit

Selzy Email Marketing

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real Selzy marketing helper, but its instructions can send or schedule campaigns before final confirmation and expose API keys or contact data in request URLs.

Review carefully before installing. Use only a restricted Selzy API key if available, never log or share full request URLs, verify list ID, list name, recipient count, sender, subject, body, send time, and timezone, and require a fresh explicit confirmation immediately before every createCampaign call. Enforce the 1-campaign-per-hour limit for campaign creation.

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

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:130
Finding
API Credentials and Contact Data Exposed in URL Query Strings<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:130-139, 194, 209, 230, 258`; `README.md:194`; `TEST_CHECKLIST.md:12, 50` **Vulnerability Type**: Sensitive information exposure through URL query parameters **Risk Level**: Medium ### Vulnerable Code ```markdown All requests require the `SELZY_API_KEY` environment variable. Pass it as the `api_key` parameter. **Base URL:** `https://api.selzy.com/en/api` **Important:** All methods use `GET` with query parameters (Selzy API uses GET for all endpoints). URL-encode parameter values when needed. ## General Request Pattern ```bash curl "https://api.selzy.com/en/api/{METHOD}?format=json&api_key=$SELZY_API_KEY&{params}" ``` ``` Examples also place contact data and email content in the URL: ```bash curl "https://api.selzy.com/en/api/importContacts?format=json&api_key=$SELZY_API_KEY&field_names[]=email&field_names[]=Name&data[][]=john@example.com&data[][]=John&data[][]=jane@example.com&data[][]=Jane&list_ids=12345&overwrite=2" ``` ```bash curl "https://api.selzy.com/en/api/subscribe?format=json&api_key=$SELZY_API_KEY&list_ids=12345&fields[email]=user@example.com&fields[Name]=Alice&double_optin=3" ``` ```bash curl "https://api.selzy.com/en/api/getContact?format=json&api_key=$SELZY_API_KEY&email=user@example.com" ``` ### Technical Analysis The Skill instructs the Agent to authenticate by embedding `SELZY_API_KEY` directly in every request URL. It also embeds subscriber email addresses, names, message bodies, subjects, and other campaign data in query parameters. Although HTTPS encrypts the request during transport, query strings remain vulnerable to disclosure through other channels, including: - Process listings that capture command-line arguments - Shell history and terminal session recording - Debugging or verbose HTTP output - Reverse-proxy, CDN, gateway, and web-server access logs - Monitoring, tracing, crash-reporting, or endpoint-management systems - Copied command output and troubleshoo ...[truncated 1883 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer an authorization header rather than a query parameter if supported by Selzy: ```bash curl \ -H "Authorization: Bearer $SELZY_API_KEY" \ "https://api.selzy.com/en/api/getLists?format=json" ``` 2. Prefer `POST` requests with form or JSON bodies for contact information, message content, and other sensitive parameters whenever the API supports them. 3. If Selzy strictly requires API keys in GET query parameters: - Clearly document that this is an API limitation and that URLs contain secrets. - Avoid examples likely to be copied into persistent shell history. - Disable command tracing such as `set -x` around requests. - Configure HTTP clients, proxies, tracing systems, and application logs to redact `api_key`, `fields`, `data`, `body`, and email parameters. - Do not print complete request URLs in errors or diagnostics. - Use a dedicated restricted client process rather than constructing requests directly in an interactive shell. 4. Store the key in a protected secret manager or a configuration file readable only by the service account. Do not place a real key directly in general-purpose configuration examples without permission-hardening guidance. 5. Use a dedicated Selzy key with the narrowest available permissions and rotate it immediately if a complete request URL is logged or shared. 6. Add automated redaction tests to verify that credentials, contact addresses, and message bodies cannot appear in normal or error logs.]]>

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:463
Finding
Campaign Creation Occurs Before Required User Confirmation<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:463-468, 500-507`; corroborated by `README.md:88-94` **Vulnerability Type**: Incorrect authorization and confirmation ordering for a consequential action **Risk Level**: High ### Vulnerable Code The mandatory campaign workflow places `createCampaign` before confirmation: ```markdown 2. **For new campaigns, follow this workflow (MANDATORY):** ``` 1. getLists → GET list_id AND count of contacts 2. VERIFY: count >= expected recipients (if count=0, STOP and ask) 3. createEmailMessage → MUST include list_id parameter + confirm subject + content 4. RATE LIMIT CHECK: If created campaign in last 60s, WAIT before proceeding 5. createCampaign → confirm timing + recipient count 6. WAIT for explicit "send" or "confirm" from user ``` ``` A common workflow repeats the same unsafe sequence: ```markdown #### "Create a campaign for my VIP customers" ``` 1. getLists → find VIP list, confirm count (MUST match expected recipients) 2. If count is wrong → STOP and alert user (DO NOT proceed) 3. Ask: subject, content type (promo/newsletter/event) 4. createEmailMessage → MUST include list_id parameter, show preview 5. createCampaign (omit start_time for immediate) 6. ⚠️ WAIT for "send it" or "confirm" before considering it done 7. Report: campaign_id, status, recipient count (verify matches step 1) ``` ``` The Skill separately defines omission of `start_time` as an immediate-send operation: ```markdown | `start_time` | No | Schedule time (YYYY-MM-DD HH:MM:SS). Omit for immediate send | ``` ### Technical Analysis The Skill states that campaigns must never be sent without explicit confirmation, but two operational workflows direct the Agent to call `createCampaign` first and obtain confirmation afterward. This is not merely a cosmetic inconsistency. According to the Skill's own API description, `createCampaign` creates and schedules a campaign, and omitting `start_time` causes an immediate ...[truncated 2218 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Move explicit confirmation immediately before every `createCampaign` invocation: ```text 1. Call getLists and identify the exact list. 2. Verify and display list ID, list name, and recipient count. 3. Create or preview the email message with the explicit list ID. 4. Display the final subject, content summary, sender, recipient count, and send time. 5. Ask for explicit confirmation. 6. Only after confirmation, perform the rate-limit check and call createCampaign. 7. Report the returned campaign ID and status. ``` 2. Treat `createCampaign` as the send or scheduling authorization boundary, not as a draft-creation operation. 3. Require a confirmation message that unambiguously follows the final preview. Earlier words such as “send” in the initial request should not bypass confirmation after list resolution and message construction. 4. For immediate campaigns, do not omit `start_time` until the final confirmed call. If Selzy provides a draft-only endpoint, use that endpoint for preparation instead. 5. Include an immutable confirmation summary containing: - List ID and list name - Exact active recipient count - Verified sender address - Final subject - Body preview or content hash - Immediate or scheduled delivery time and timezone 6. If any summarized value changes after confirmation, invalidate the approval and request confirmation again. 7. Make the workflow consistent across `SKILL.md`, `README.md`, and `TEST_CHECKLIST.md`. Remove every example in which `createCampaign` precedes confirmation. 8. Add tests asserting that no `createCampaign` request is issued before the confirmation state is recorded. Include immediate-send, scheduled-send, changed-recipient, and ambiguous-user-request cases. 9. Correct the inconsistent rate-limit text at `SKILL.md:466`, which checks 60 seconds despite the document's stated one-hour hard limit. Enforce the one-hour interval before the final API ...[truncated 9 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (40)

Session Persistence

Medium
Category
Rogue Agent
Content
# ✉️ Selzy Email Marketing Skill for OpenClaw

Full-featured email marketing skill for managing campaigns via Selzy API. Create campaigns, manage contacts, and analyze results directly from chat.

## 🚀 Quick Start
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Intent-Code Divergence

Medium
Confidence
89% confidence
Finding
The README makes a strong security claim that campaigns are never sent without explicit confirmation, yet the documented capabilities include direct send operations and no mechanism in this file demonstrates that confirmation is technically enforced. In an email-marketing skill, this mismatch is dangerous because an agent or integrator may rely on the documentation and trigger unintended outbound mail, causing spam, reputational damage, or compliance issues.

Intent-Code Divergence

Medium
Confidence
89% confidence
Finding
The README says the v2.1 fix requires always calling getLists first, verifying list_id, and checking recipient count before campaign creation, but the exposed workflow still presents createEmailMessage and createCampaign as independently callable operations. That inconsistency can let users or agent planners bypass the safety workflow and send to the wrong audience or only a single unintended recipient, undermining campaign integrity.

External Transmission

Medium
Category
Data Exfiltration
Content
All requests require the `SELZY_API_KEY` environment variable. Pass it as the `api_key` parameter.

**Base URL:** `https://api.selzy.com/en/api`

**Important:** All methods use `GET` with query parameters (Selzy API uses GET for all endpoints). URL-encode parameter values when needed.
Confidence
92% confidence
Finding
The skill is explicitly designed to transmit user and contact data to an external third-party API using an API key. In context this is expected functionality, but it is still a real data-exposure surface because recipient lists, email content, and campaign metadata leave the local trust boundary.

External Transmission

Medium
Category
Data Exfiltration
Content
## General Request Pattern

```bash
curl "https://api.selzy.com/en/api/{METHOD}?format=json&api_key=$SELZY_API_KEY&{params}"
```

**Response Format:**
Confidence
92% confidence
Finding
The general request pattern sends requests to Selzy with query parameters, meaning operational and potentially sensitive data are transmitted to an external service. This is inherent to the integration, but it expands the exposure surface and may also leak sensitive values via logs, browser history, proxies, or monitoring systems if query strings contain secrets or personal data.

External Transmission

Medium
Category
Data Exfiltration
Content
Create a new contact list.

```bash
curl "https://api.selzy.com/en/api/createList?format=json&api_key=$SELZY_API_KEY&title=VIP%20Customers"
```

**Response:** `{"result": {"id": 12345}}`
Confidence
84% confidence
Finding
Creating a list on an external email-marketing platform is expected behavior, but it still performs state-changing transmission to a third party. In the context of marketing automation, such calls can create unauthorized campaign infrastructure or expose business metadata externally if triggered without proper approval.

External Transmission

Medium
Category
Data Exfiltration
Content
Bulk import contacts into a list.

```bash
curl "https://api.selzy.com/en/api/importContacts?format=json&api_key=$SELZY_API_KEY&field_names[]=email&field_names[]=Name&data[][]=john@example.com&data[][]=John&data[][]=jane@example.com&data[][]=Jane&list_ids=12345&overwrite=2"
```

| Parameter | Description |
Confidence
97% confidence
Finding
The importContacts example transmits personal data such as names and email addresses to an external provider. This is expected functionality, but it is a genuine sensitive-data transfer that can create privacy, consent, and compliance risks if contacts are uploaded without appropriate legal basis or user approval.

External Transmission

Medium
Category
Data Exfiltration
Content
Add a single contact with opt-in control.

```bash
curl "https://api.selzy.com/en/api/subscribe?format=json&api_key=$SELZY_API_KEY&list_ids=12345&fields[email]=user@example.com&fields[Name]=Alice&double_optin=3"
```

**double_optin values:**
Confidence
96% confidence
Finding
The subscribe example sends a user's email and profile fields to an external mailing platform, which is a sensitive outbound transfer. Because subscription state affects marketing consent and communications, misuse can lead to unauthorized enrollment and regulatory exposure.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
```

**double_optin values:**
- `0` = no confirmation
- `3` = send confirmation email
- `4` = already confirmed (force subscribe)
Confidence
90% confidence
Finding
The skill documents subscription modes that allow adding contacts with no confirmation or forcing them as already confirmed. In a marketing context this enables autonomous or low-friction enrollment into mailing lists, which can lead to unauthorized messaging, spam complaints, and regulatory violations.

External Transmission

Medium
Category
Data Exfiltration
Content
Unsubscribe/remove a contact.

```bash
curl "https://api.selzy.com/en/api/exclude?format=json&api_key=$SELZY_API_KEY&contact_type=email&contact=user@example.com"
```

### 2.4 Get Contact — `getContact`
Confidence
78% confidence
Finding
The exclude example sends contact identifiers to a third-party service to change subscription status. Although expected, it is still an external transmission of personal data and a state-changing action that can affect user communications rights.

External Transmission

Medium
Category
Data Exfiltration
Content
Get contact details by email.

```bash
curl "https://api.selzy.com/en/api/getContact?format=json&api_key=$SELZY_API_KEY&email=user@example.com"
```

### 2.5 Create Custom Field — `createField`
Confidence
86% confidence
Finding
The getContact example transmits an email address to a third-party API for lookup, exposing personal data outside the local environment. Even though this is core functionality, the lookup can reveal or retrieve subscriber information and therefore carries privacy risk.

External Transmission

Medium
Category
Data Exfiltration
Content
Create an email template for campaigns.

```bash
curl "https://api.selzy.com/en/api/createEmailMessage?format=json&api_key=$SELZY_API_KEY&sender_name=My%20Store&sender_email=news@yourdomain.com&subject=Summer%20Sale%20🔥&body=<h1>Hello!</h1><p>Check%20out%20our%20deals</p>&list_id=12345"
```

| Parameter | Required | Description |
Confidence
98% confidence
Finding
The createEmailMessage example transmits campaign content, sender identity, and targeting metadata to an external provider via query parameters. This is sensitive operational data, and using GET increases the chance of leakage through logs, analytics, proxies, and intermediary infrastructure.

External Transmission

Medium
Category
Data Exfiltration
Content
Modify an existing email template.

```bash
curl "https://api.selzy.com/en/api/updateEmailMessage?format=json&api_key=$SELZY_API_KEY&id=67890&subject=Updated%20Subject"
```

### 3.3 Get Message — `getMessage`
Confidence
84% confidence
Finding
Updating an email message sends campaign metadata to an external service and mutates state there. This is expected, but still a real external transmission and control surface that could be abused to alter campaign content without proper approval.

External Transmission

Medium
Category
Data Exfiltration
Content
Create a reusable template (separate from messages).

```bash
curl "https://api.selzy.com/en/api/createEmailTemplate?format=json&api_key=$SELZY_API_KEY&name=Welcome%20Series&subject=Welcome!&body=<h1>Welcome {{Name}}!</h1>"
```

### 3.6 Get Template — `getTemplate`
Confidence
87% confidence
Finding
Creating a reusable email template sends content to a third-party platform and changes external state. That is intended behavior, but still introduces exposure of proprietary campaign material and possible misuse if invoked without review.

External Transmission

Medium
Category
Data Exfiltration
Content
Create and schedule a campaign.

```bash
curl "https://api.selzy.com/en/api/createCampaign?format=json&api_key=$SELZY_API_KEY&message_id=67890&start_time=2026-02-10%2010:00:00&timezone=Europe/Moscow&track_read=1&track_links=1"
```

| Parameter | Required | Description |
Confidence
97% confidence
Finding
The createCampaign example triggers outbound email campaign execution through a third-party service, a high-impact state-changing external action. In context, misuse could send bulk communications to large contact lists, with business, reputational, and compliance consequences.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The example hard-codes Europe/Moscow and elsewhere Europe/Belgrade without requiring user confirmation of locale or timezone. In an email-campaign skill, this can cause messages to be sent at unintended times, potentially violating user expectations, harming campaigns, or triggering compliance and business-process issues.

External Transmission

Medium
Category
Data Exfiltration
Content
Cancel a scheduled campaign before it sends.

```bash
curl "https://api.selzy.com/en/api/cancelCampaign?format=json&api_key=$SELZY_API_KEY&campaign_id=11111"
```

### 4.3 Get Campaign Status — `getCampaignStatus`
Confidence
74% confidence
Finding
Canceling a campaign is a state-changing external action that affects communications and business processes. While not inherently malicious, it can disrupt operations if triggered without strong authorization and confirmation.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The skill repeatedly documents a hard safety limit of 1 campaign creation per hour, but this instruction says to proceed if the last campaign was created more than 60 seconds ago. That contradiction can cause an agent to violate the platform's anti-abuse threshold, leading to account suspension and unintended high-volume outbound email activity.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
# Missing list_id — Selzy sends to 1 contact by default!
curl "https://api.selzy.com/en/api/createEmailMessage?format=json&api_key=$KEY&sender_name=My+Store&sender_email=me@example.com&subject=Sale&body=<h1>Sale!</h1>"
```

**Result:** Campaign #327590492 sent to 1 recipient instead of 4 contacts in list.
Confidence
95% confidence
Finding
Although presented as a negative example, this URL still illustrates sending API keys and HTML content in a GET query string to a third party. That pattern risks leakage of credentials and message content through logs and intermediaries even when the example is meant to teach a pitfall.

External Transmission

Medium
Category
Data Exfiltration
Content
# Response: [{"id": 12345, "title": "My first list", "count": 4}]

# Step 2: Create message WITH list_id
curl "https://api.selzy.com/en/api/createEmailMessage?format=json&api_key=$KEY&sender_name=My+Store&sender_email=me@example.com&subject=Sale&body=<h1>Sale!</h1>&list_id=12345"
# Response: {"result": {"message_id": 67890}}

# Step 3: Create campaign
Confidence
97% confidence
Finding
This example sends sender identity, subject, and HTML body to the external service via a GET URL, exposing both sensitive operational data and the API key in the query string. In practice this can leak campaign content and credentials into logs or monitoring systems.

External Transmission

Medium
Category
Data Exfiltration
Content
# Response: {"result": {"message_id": 67890}}

# Step 3: Create campaign
curl "https://api.selzy.com/en/api/createCampaign?format=json&api_key=$KEY&message_id=67890"
# Response: {"result": {"campaign_id": 11111}}

# Result: Campaign sent to ALL 4 contacts ✅
Confidence
90% confidence
Finding
The example triggers actual campaign creation through an external provider, a high-impact outbound action capable of emailing many recipients. In the context of this skill, unauthorized or mistaken use has direct business and compliance consequences.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The error-handling table advises retrying after 1-2 seconds on HTTP 429, which conflicts with the stated hard limit for createCampaign operations. If applied to campaign creation, this guidance could trigger repeated abusive retries against a protected write endpoint and increase the risk of account blocking.

External Transmission

Medium
Category
Data Exfiltration
Content
Verify API access:
```bash
# Check connection
curl "https://api.selzy.com/en/api/getLists?format=json&api_key=YOUR_KEY"

# Check verified senders
curl "https://api.selzy.com/en/api/getSenderEmails?format=json&api_key=YOUR_KEY"
Confidence
89% confidence
Finding
The quick-test command instructs users to place the API key directly into a URL query string. Even as a test, this promotes a practice that can leak credentials via shell history, logs, proxy records, and screenshots.

External Transmission

Medium
Category
Data Exfiltration
Content
curl "https://api.selzy.com/en/api/getLists?format=json&api_key=YOUR_KEY"

# Check verified senders
curl "https://api.selzy.com/en/api/getSenderEmails?format=json&api_key=YOUR_KEY"

# Test campaign stats (replace ID)
curl "https://api.selzy.com/en/api/getCampaignCommonStats?format=json&api_key=YOUR_KEY&campaign_id=123456"
Confidence
89% confidence
Finding
This test example likewise embeds the API key in a URL, normalizing insecure handling of credentials during external requests. The risk is credential disclosure through local and network logging surfaces.

External Transmission

Medium
Category
Data Exfiltration
Content
curl "https://api.selzy.com/en/api/getSenderEmails?format=json&api_key=YOUR_KEY"

# Test campaign stats (replace ID)
curl "https://api.selzy.com/en/api/getCampaignCommonStats?format=json&api_key=YOUR_KEY&campaign_id=123456"
```

---
Confidence
89% confidence
Finding
This test command again places the API key in the request URL, which may expose credentials through shell history and observability tooling. Repetition of this pattern in documentation increases the chance users will copy unsafe practices into production automation.

Static analysis

No suspicious patterns detected.