T09 · Insecure Skill Coding Practices
- Location
scripts/push-to-crm.sh:93- Finding
Credential and Lead Data Disclosure Through Pipedrive Endpoint Injection
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill largely matches its lead-generation purpose, but it can send outreach emails, write CRM records, store credentials and lead data, and contact user-controlled destinations without enough enforced safeguards.
Review this skill before installing. Use it only in a controlled workspace, keep API tokens out of committed config files, restrict file permissions, validate CRM tenant and lead-domain values, run outreach only after explicit human review, and confirm templates include required opt-out and sender information. Avoid unattended campaign sending until approval and compliance checks are enforced in code.
scripts/push-to-crm.sh:93Credential and Lead Data Disclosure Through Pipedrive Endpoint Injection
scripts/enrich-leads.sh:85Server-Side Request Forgery During Lead Website Enrichment
scripts/init-workspace.sh:8Plaintext Service Credentials Created Without Restrictive File Permissions
scripts/create-campaign.sh:21Path Traversal in Campaign, Template, and Lead-Set Identifiers
scripts/send-campaign.sh:69Documented Human Approval and Email Compliance Controls Are Not Enforced
If the skill description promises broad CRM and outreach automation while actual behavior is narrower and includes outbound email via SendGrid without clearly declared permissions, users may approve or invoke the skill under false assumptions. That can lead to unauthorized external transmission of lead/contact data or email sending beyond what the manifest transparently discloses.
If the skill description promises broad CRM and outreach automation while actual behavior is narrower and includes outbound email via SendGrid without clearly declared permissions, users may approve or invoke the skill under false assumptions. That can lead to unauthorized external transmission of lead/contact data or email sending beyond what the manifest transparently discloses.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
crm.objects.contacts.write, crm.objects.deals.writeCode accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## Zoho CRM
### Get Access Token
1. Go to https://api-console.zoho.com/
2. Create a Self Client
3. Generate token with scope: `ZohoCRM.modules.leads.CREATE,ZohoCRM.modules.contacts.CREATE`
The skill advertises workflows that inherently require network, filesystem, and likely secret access, but it does not declare an explicit tool scope or permission boundary. That makes it easier for an agent runtime to over-grant capabilities or for users to misunderstand the operational reach of the skill, increasing the chance of unintended data access or outbound actions.
The activation guidance is extremely broad and overlaps with many normal sales and CRM tasks, which raises the risk that the skill is invoked in situations involving sensitive customer or prospect data without deliberate user intent. In a skill that can scrape, enrich, sync to CRMs, and send outreach, ambiguous triggering materially increases the chance of privacy-impacting or externally visible actions.
The skill handles collection, enrichment, storage, and third-party transmission of personal and business contact data, yet the documentation gives only cursory compliance notes and does not clearly explain consent, lawful basis, retention, or third-party sharing risks. In this context, missing privacy warnings are significant because the workflows directly encourage scraping, enrichment, CRM upload, and outreach at scale.
The guide shows users placing live CRM API credentials directly into a JSON configuration example without any warning about secret handling, storage, rotation, or avoiding commits to source control. In an automation skill that orchestrates CRM and outreach actions, this increases the chance users will hardcode reusable tokens in files that may be logged, shared, or checked into repositories, leading to unauthorized CRM access.
The CSV fallback documents that lead data will be exported to a local file but provides no warning that the file may contain sensitive personal or business contact data. In a lead-generation/CRM context, writing prospect data to disk without retention, access-control, or cleanup guidance can cause unintended disclosure through shared workspaces, backups, or accidental attachment/sharing.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
try:
resp = requests.get(
"https://api.search.brave.com/res/v1/web/search",
headers={"X-Subscription-Token": brave_key},
params={"q": search_query, "count": min(count, 20)},
timeout=15,
The script sends the lead's domain to Hunter.io and later fetches the company's website for enrichment, which transmits user-managed lead data over the network. Although there are progress prints, there is no explicit disclosure, confirmation, or comment warning that external services will receive lead information.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
if hunter_key and domain:
try:
resp = requests.get(
"https://api.hunter.io/v2/domain-search",
params={"domain": domain, "api_key": hunter_key},
timeout=10,
)
The script builds a request target directly from lead-controlled domain data and performs a server-side fetch without validation. An attacker who can supply or modify lead JSON could cause requests to arbitrary hosts, including internal services or cloud metadata endpoints via DNS rebinding or crafted hostnames, turning the agent into an SSRF primitive.
# Basic web scrape for additional signals
if domain:
try:
resp = requests.get(f"https://{domain}", timeout=8, headers={"User-Agent": "ReighlanLeadBot/1.0"})
if resp.status_code == 200:
text = resp.text.lower()
# Tech signals
After writing the enriched record, the script removes the original file from the raw directory. This is an irreversible file operation, but there is no confirmation prompt or explicit warning that running the script will delete source lead files.
This shell script writes a config.json file that includes fields for API keys, access tokens, refresh tokens, and SMTP credentials. Although it logs that the file was created, it does not warn the user that sensitive secrets will be stored in this workspace path, which is a user-disclosure gap for credential-related file creation.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Create contact
try:
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/contacts",
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
json={
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Create contact
try:
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/contacts",
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
json={
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Create contact
try:
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/contacts",
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
json={
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Create contact
try:
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/contacts",
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
json={
"properties": {
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
return False
try:
resp = requests.post(
f"https://{domain}.pipedrive.com/api/v1/persons",
params={"api_token": api_key},
json={
The Pipedrive request target is constructed from a configurable domain value without strict validation: https://{domain}.pipedrive.com/api/v1/persons. If an attacker can influence config or environment variables, they may redirect requests to attacker-controlled or unintended subdomains, causing unauthorized transmission of lead data and API usage against the wrong tenant. In a skill that automates handling of large volumes of lead/contact data, this increases the risk of silent data leakage at scale.
return False
try:
resp = requests.post(
f"https://{domain}.pipedrive.com/api/v1/persons",
params={"api_token": api_key},
json={
The csv branch appends lead contact data to reports/leads-export.csv, which is a file write involving user/business data. While network pushes are part of the script's stated purpose and are visibly announced, this local export path has no specific warning, confirmation, or explanatory comment/disclosure near the operation.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
body = body.replace(f"{{{key}}}", r.get(key, ""))
try:
resp = requests.post(
"https://api.sendgrid.com/v3/mail/send",
headers={
"Authorization": f"Bearer {sendgrid_key}",
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
try:
resp = requests.post(
"https://api.sendgrid.com/v3/mail/send",
headers={
"Authorization": f"Bearer {sendgrid_key}",
"Content-Type": "application/json",