T09 · Insecure Skill Coding Practices
- Location
SKILL.md:130- Finding
API Credentials and Contact Data Exposed in URL Query Strings
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
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.
SKILL.md:130API Credentials and Contact Data Exposed in URL Query Strings
SKILL.md:463Campaign Creation Occurs Before Required User Confirmation
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.
# ✉️ 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
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.
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.
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.
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.
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.
curl "https://api.selzy.com/en/api/{METHOD}?format=json&api_key=$SELZY_API_KEY&{params}"
Response Format:
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.
Create a new contact list.
curl "https://api.selzy.com/en/api/createList?format=json&api_key=$SELZY_API_KEY&title=VIP%20Customers"
Response: {"result": {"id": 12345}}
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.
Bulk import contacts into a list.
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 |
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.
Add a single contact with opt-in control.
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:
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.
**double_optin values:**
- `0` = no confirmation
- `3` = send confirmation email
- `4` = already confirmed (force subscribe)
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.
Unsubscribe/remove a contact.
curl "https://api.selzy.com/en/api/exclude?format=json&api_key=$SELZY_API_KEY&contact_type=email&contact=user@example.com"
getContactThe 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.
Get contact details by email.
curl "https://api.selzy.com/en/api/getContact?format=json&api_key=$SELZY_API_KEY&email=user@example.com"
createFieldThe 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.
Create an email template for campaigns.
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 |
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.
Modify an existing email template.
curl "https://api.selzy.com/en/api/updateEmailMessage?format=json&api_key=$SELZY_API_KEY&id=67890&subject=Updated%20Subject"
getMessageCreating 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.
Create a reusable template (separate from messages).
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>"
getTemplateThe 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.
Create and schedule a campaign.
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 |
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.
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.
Cancel a scheduled campaign before it sends.
curl "https://api.selzy.com/en/api/cancelCampaign?format=json&api_key=$SELZY_API_KEY&campaign_id=11111"
getCampaignStatusThe 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.
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.
# 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.
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.
# 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
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.
# 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 ✅
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.
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.
Verify API access:
# 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"
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.
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"
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.
curl "https://api.selzy.com/en/api/getSenderEmails?format=json&api_key=YOUR_KEY"
curl "https://api.selzy.com/en/api/getCampaignCommonStats?format=json&api_key=YOUR_KEY&campaign_id=123456"
---
No suspicious patterns detected.