Back to skill

Security audit

Lead Enrichment Scanner

Security checks for vulnerabilities and agentic risk

Overview

This skill is a review concern because it performs lead enrichment but also collects scraped email addresses and has an implemented outreach-drafts feature that contradicts its documentation and can send lead data to an external LLM.

Review before installing. Use only with public company data, avoid sensitive CRM exports, assume scraped email addresses may be collected despite the privacy setting, and assume MiniMax may receive company or lead details whenever MINIMAX_API_KEY is set and enrichment or drafts are run. Choose output paths carefully because existing files may be replaced.

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

Warning
Location
scripts/lead_enrich.py:115
Finding

Hostname-Only SSRF Filtering Can Be Bypassed Through DNS Resolution or Redirects

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/lead_enrich.py:150
Finding

Personal Email Collection Occurs Despite an Enabled Privacy Control

Content
View full analysis
]+name=["\']description["\'][^>]+content=["\']([^"\']+)["\']', r']+property=["\']og:description["\'][^>]+content=["\']([^"\']+)["\']', r']+name=["\']twitter:description["\'][^>]+content=["\']([^"\']+)["\']', ]: m = re.search(pattern, html, re.IGNORECASE) if m: info["description"] = m.group(1).strip()[:300] break # Emails info["emails_found"] = list(set(re.findall(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", html)))[:5] ``` The configuration command only stores the value: ```python if args.no_personal_emails is not None: config["no_personal_emails"] = args.no_personal_emails changed = True ``` Extracted addresses are subsequently persisted in CSV output: ```python fieldnames = [ "name", "domain", "industry", "employee_estimate", "decision_maker_title", "company_summary", "description", "emails_found", "social_links", "email_patterns", ] with open(args.output, "w", newline="") as f: writer = csv.DictW ...[truncated 2074 chars]
Remediation
View remediation

other

Warning
Location
scripts/lead_enrich.py:415
Finding

Undocumented Outreach Command Transmits Lead Data to an External LLM

Content
View full analysis
10: print(f"Free tier limited to 10 drafts. You provided {len(leads)}.") leads = leads[:10] tone = args.tone variants = args.variants if args.dry_run: logger.info(f"\n[DRY RUN] Would generate {variants} drafts each for {len(leads)} leads") logger.info(f" Tone: {tone}") logger.info(f" Output: {args.output}") return drafts_output = [] for i, lead in enumerate(leads, 1): prompt = f"""Write {variants} personalized cold outreach variants to a {lead.get('decision_maker_title', 'decision-maker')} at {lead.get('name', 'a company')} ({lead.get('industry', 'their industry')}). Tone: {tone} Company description: {lead.get('description') or lead.get('company_summary') or 'N/A'} Each variant should be 3-4 sentences max. Start with a specific observation about their company, not a generic intro. Format each variant as: ## Variant 1: ## Variant 2: (etc.) """ llm_response = call_llm(prompt, max_tokens=600) ``` `call_llm` sends the prompt to the pinned MiniMax endpoint: ```python base_url = "https://api.minimax.chat/v1" # pinned, no env override try: data = json.dumps({ "model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens, "temperature": 0.3, }).encode() req = urllib.request.Request( ...[truncated 2469 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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The activation guidance includes broad phrases like 'look up a company' and 'find info on a company', which can overlap with ordinary user requests and cause the skill to trigger unexpectedly. In a skill that performs network scraping and writes output files, overly broad invocation criteria increase the chance of unintentional data collection, external requests, and misuse outside the user's precise intent.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The tool advertises a no_personal_emails privacy control, but never applies it when scraping and exporting emails. This can cause collection and storage of personal email addresses despite user expectations, creating privacy, compliance, and data-handling risk in a lead-enrichment context where scraped contact data is the primary output.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script sends company names, domains, and scraped descriptions to an external LLM service without an explicit consent prompt or prominent disclosure at the point of use. In a data-enrichment workflow, users may not realize scraped or customer-derived data leaves the local environment, which can create privacy, contractual, or regulatory exposure.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
94% confidence
Finding

This finding is valid because the tool transmits lead-enrichment data to a third-party service over the network. While the destination is pinned to a legitimate HTTPS API and not obviously malicious, the skill context makes outbound transfer more sensitive because the application processes scraped business/contact data that may be subject to privacy expectations or contractual limits.

Content

Scanner excerpt · scripts/lead_enrich.py (reported line 212)May include surrounding context.

python
)
            LLM_API_KEY_MISSING_WARNED = True
        return None
    base_url = "https://api.minimax.chat/v1"  # pinned, no env override
    try:
        data = json.dumps({
            "model": model,

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/lead_enrich.py (reported line 51)May include surrounding context.

python
# Allow env override
    env_level = os.environ.get("CLAWHUB_LOG_LEVEL", "").upper()
    if env_level in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"):
        level = getattr(logging, env_level)
    logging.basicConfig(
        level=level,
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The docstring represents the function as a robots.txt compliance check, but the implementation merely scans for any Disallow: line and compares it to the path, without parsing User-agent sections or honoring the passed user_agent argument semantically. This is an intent/documentation mismatch because the code suggests standards-aware enforcement while implementing only a simplistic approximation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The enrich command opens the user-supplied output path in write mode, which will silently overwrite any existing file. While writing output is part of the tool's purpose, there is no visible disclosure or confirmation about the destructive overwrite behavior.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The drafts command writes to the specified markdown output using write mode, replacing any existing file contents. There is no user-facing warning or confirmation before this irreversible file overwrite occurs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The filter command truncates and rewrites the specified output CSV path if it already exists. This is a destructive file operation with no confirmation prompt or warning to the user.

Content

No source excerpt is available for this finding.