Back to skill

Security audit

Seo Prospector

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a coherent SEO lead-generation tool, but it has under-disclosed external data sharing and overbroad local/network execution risks that users should review before installing.

Install only if you are comfortable with this skill researching external websites, storing lead data locally, and using third-party APIs. Before production use, fix the mutable helper-script paths, add URL destination allowlisting or private-network blocking, disclose and gate OpenRouter completion, avoid putting API keys in curl arguments, and manually review all outreach for privacy, consent, opt-out, and anti-spam compliance.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/seo_quick_audit.py:40
Finding

Arbitrary URL Fetching Enables Server-Side Request Forgery

Content
View full analysis
FetchResult: def fetch_via_urllib() -> FetchResult: req = urllib.request.Request(url, headers={"User-Agent": user_agent}) ctx = ssl.create_default_context() start = time.perf_counter() try: with urllib.request.urlopen(req, timeout=timeout_s, context=ctx) as resp: raw = resp.read() ``` The curl fallback also follows redirects without validating their destinations: ```python cmd = [ "curl", "-sS", "-L", "--max-time", str(timeout_s), "-A", user_agent, "-w", sentinel + "%{http_code}\n%{url_effective}\n", url, ] proc = subprocess.run(cmd, capture_output=True, text=True) ``` Only the scheme is normalized before the request: ```python def audit(url: str, *, timeout_s: int, user_agent: str, check_robots: bool, check_sitemap: bool) -> dict[str, Any]: if not url.startswith(("http://", "https://")): url = "https://" + url main = fetch(url, timeout_s=timeout_s, user_agent=user_agent) ``` The verifier contains the same unrestricted behavior: ```python head_cmd = [ "curl", "-sS", "-o", "/dev/null", "-w", "%{http_code}|%{redirect_url}|%{url_effective}", "-L", "--max-redirs", "5", "--connect-timeout", str(timeout), "--max-time", str(timeout + 5), url ] proc = subprocess.run(head_cmd, capture_output=True, text=True, timeout=timeout + 10) ``` ### Technical Analysis The audit and verification functions accept URLs derived from command-line arguments, prospect JSON, reports, or directory data. They permit arbitrary HTTP and HTTPS destinations and follow redirects. They do not reject: - Loop ...[truncated 1974 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/perplexity_search.py:31
Finding

Perplexity API Key Is Exposed Through Process Arguments

Content
View full analysis
str: if not CONFIG_PATH.exists(): print(f"Error: Perplexity config not found at {CONFIG_PATH}", file=sys.stderr) sys.exit(1) config = json.loads(CONFIG_PATH.read_text()) key = config.get("env", {}).get("PERPLEXITY_API_KEY") if not key: print("Error: PERPLEXITY_API_KEY not found in config", file=sys.stderr) sys.exit(1) return key ``` The key is then inserted directly into curl's command-line arguments: ```python cmd = [ "curl", "-sS", "-X", "POST", API_URL, "-H", f"Authorization: Bearer {api_key}", "-H", "Content-Type: application/json", "-d", json.dumps(payload), ] proc = subprocess.run(cmd, capture_output=True, text=True, timeout=60) ``` ### Technical Analysis Although the key is initially read from a configuration file, the code places the complete bearer token in curl's argument vector. Command-line arguments can be observable through process inspection facilities, monitoring agents, debugging tools, audit logs, crash reports, or process-listing utilities, depending on operating-system policy. The use of an argument list prevents shell injection, but it does not protect the secret from process-argument disclosure. ### Attack Path 1. A user runs the Perplexity search directly or through the prospect research pipeline. 2. The script reads `PERPLEXITY_API_KEY` from the OpenClaw configuration. 3. A curl process starts with `Authorization: Bearer ` in its argument vector. 4. Another local process, user, monitoring component, or diagnostic utility with process-inspection access observes the command line. 5. The recovered token is used to issue unauthorized Perplexity API requests. ### Impact Assessment An exposed key can permit u ...[truncated 312 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/research_prospect.py:257
Finding

Prospect Reports Are Transmitted to an Undeclared OpenRouter Service

Content
View full analysis
=0.9.0", "apis": [ "perplexity (for web research — free tier works)" ] } ``` ### Technical Analysis When `OPENROUTER_API_KEY` is present in the environment, the normal research pipeline automatically sends the complete generated prospect report to OpenRouter. This report can include: - Business name ...[truncated 1442 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/create_outreach.py:178
Finding

Unescaped Prospect Data Allows Stored HTML Injection in Outreach Emails

Content
View full analysis
{issue} ''' ``` Other prospect and configuration values are also substituted without escaping: ```python replacements = { "{{SUBJECT}}": subject, "{{PREHEADER}}": preheader, "{{GREETING}}": greeting, "{{OPENING}}": opening, "{{ISSUES_INTRO}}": "", "{{VALUE_PROP}}": value_prop, "{{CTA}}": cta, "{{SENDER_NAME}}": agency.get("owner", "Your Name"), "{{AGENCY_NAME}}": agency.get("name", "Your Agency"), "{{PHONE}}": agency.get("phone", ""), "{{WEBSITE}}": agency.get("website", "youragency.com"), } for key, val in replacements.items(): html = html.replace(key, val) ``` ### Technical Analysis Prospect reports contain data obtained from external websites, search results, user input, and potentially LLM-generated content. Values extracted from those reports—including business names, contact names, industries, findings, and strengths—are treated as trusted HTML. Because the output is built through string interp ...[truncated 1486 chars]
Remediation
View remediation
`, quotes, remote image tags, and anchor tags in every report field. 7. Render generated drafts in a safe preview environment before sending. ]]>

T07 · Tool Hijacking and Spoofing

Warning
Location
scripts/research_prospect.py:30
Finding

Research Pipeline Executes Mutable Scripts Outside the Audited Skill Package

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (38)

Tainted flow: 'req' from os.environ.get (line 305, credential/environment) → urllib.request.urlopen (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/research_prospect.py (reported line 318)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req, timeout=60, context=_SSL_CTX) as resp:
            body = json.loads(resp.read().decode("utf-8"))
        completed = body["choices"][0]["message"]["content"].strip()
        # Sanity check: completed report should still contain the business name

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description promises a fairly complete SEO prospect research and outreach capability focused on local business leads, prospect reports, outreach packages, scheduled clusters, daily summaries, and lead pipeline management. The supplied code does not implement those workflows. It only loads a Perplexity API key from a local config file, sends a user-provided query to the Perplexity API via curl, and prints either the response text or full JSON with citations. While such a search wrapper could be a supporting component inside a prospecting system, this code chunk by itself is materially broader in one sense (arbitrary web search with custom system prompts) and much narrower in another (it lacks the advertised prospecting, outreach, scheduling, batching, reporting, and pipeline features). Therefore the description does not accurately represent what this code chunk actually does.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 87)May include surrounding context.

md
python3 scripts/prospect_tracker.py today-clusters

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 88)May include surrounding context.

md
python3 scripts/prospect_tracker.py today-clusters

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 89)May include surrounding context.

md
python3 scripts/prospect_tracker.py today-clusters

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 90)May include surrounding context.

md
python3 scripts/prospect_tracker.py today-clusters

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 158)May include surrounding context.

md
python3 scripts/prospect_tracker.py today-clusters

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · assets/templates/email-html.html (reported line 9)May include surrounding context.

html
<meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="x-apple-disable-message-reformatting">
  <title>{{SUBJECT}}</title>
  <!--[if mso]>
  <noscript>
    <xml>
      <o:OfficeDocumentSettings>

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · assets/templates/email-html.html (reported line 50)May include surrounding context.

html
&#847; &#847; &#847; &#847; &#847; &#847; &#847; &#847; &#847; &#847; &#847; &#847; &#847; &#847; &#847;
  </div>

  <!-- Email Wrapper -->
  <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #f4f4f7;">
    <tr>
      <td align="center" style="padding: 24px 0;">

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The complete report, including prospect details, raw research, and synthesized business intelligence, is sent to an external LLM API automatically whenever OPENROUTER_API_KEY is present. In a lead-generation skill, this increases risk because users may not realize third-party transmission is happening, creating confidentiality, compliance, and contractual data-handling issues.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding

The skill advertises and documents shell, file, network, and environment-dependent workflows but does not declare any explicit tool scope or permissions boundaries. That creates an overly broad execution surface where a caller or agent runtime may grant more capability than users expect, increasing the risk of unintended file access, command execution, or external data exfiltration.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger phrases are broad and map to common business-assistant language such as 'find leads,' 'generate outreach,' and 'daily prospect summary,' which raises the chance of accidental invocation in unrelated contexts. Unintended activation matters more here because the skill can drive research, tracking, file writes, and outreach generation, potentially causing privacy, compliance, or operational side effects.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill handles prospect research, contact data, outreach generation, and pipeline tracking, but the description does not warn about privacy, consent, retention, or anti-spam obligations. In this context, missing user-facing safeguards can lead operators to collect and store personal/business contact information or generate outreach without understanding legal and policy constraints.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest markets the skill as an 'autopilot' lead-generation system that discovers businesses, audits them, and generates outreach without clearly constraining when user confirmation is required. In an agent environment, broad automation language can encourage unsupervised collection of business data and bulk outbound actions, increasing the chance of privacy violations, spam, or unintended external interactions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The manifest explicitly describes automated outreach, prospect databases, verification pipelines, cron jobs, and delivery of summaries to Discord, but does not present any user-facing warning about data handling, third-party transmission, or external side effects. This is dangerous because users may enable the skill without understanding that business contact data may be collected, processed, stored, and transmitted to external services or messaging platforms.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The file provides operational cold-outreach templates across email, LinkedIn, DM/text, and phone channels, but it contains no safeguards about lawful basis, consent requirements, privacy expectations, scraping or contact-data provenance, or platform anti-spam rules. In a skill explicitly designed for prospect research and outreach, this omission can enable unsolicited or non-compliant messaging workflows at scale, increasing risk of privacy violations, spam complaints, and regulatory exposure.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The template explicitly directs users to include actual customer review quotes in prospect reports, but provides no guidance on minimization, attribution, terms-of-use compliance, or handling personal data that may appear in reviews. In a lead-generation and outreach workflow, this increases the risk of unnecessary collection and redistribution of third-party personal content, especially if reports are shared internally, exported, or used in outbound materials.

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/batch_research.py (reported line 48)May include surrounding context.

python
def get_today_clusters() -> dict:
    """Get today's scheduled clusters from rotation."""
    cmd = ["python3", str(SCRIPTS_DIR / "prospect_tracker.py"), "today-clusters"]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
    try:
        return json.loads(result.stdout)
    except json.JSONDecodeError:

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/batch_research.py (reported line 61)May include surrounding context.

python
def get_today_clusters() -> dict:
    """Get today's scheduled clusters from rotation."""
    cmd = ["python3", str(SCRIPTS_DIR / "prospect_tracker.py"), "today-clusters"]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
    try:
        return json.loads(result.stdout)
    except json.JSONDecodeError:

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/daily_summary.py (reported line 67)May include surrounding context.

python
def get_today_clusters() -> dict:
    """Get today's scheduled clusters from rotation."""
    cmd = ["python3", str(SCRIPTS_DIR / "prospect_tracker.py"), "today-clusters"]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
    try:
        return json.loads(result.stdout)
    except json.JSONDecodeError:

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/batch_research.py (reported line 116)May include surrounding context.

python
"--cluster", cluster_name,
        "--output-json"
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
    try:
        return json.loads(result.stdout)
    except json.JSONDecodeError:

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/generate_outreach_batch.py (reported line 59)May include surrounding context.

python
"--report", str(report_path),
            "--template", args.template
        ]
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
        if result.returncode == 0:
            print(f"  ✅ {result.stdout.strip().split(chr(10))[-1] if result.stdout else 'done'}", file=sys.stderr)
            success += 1

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The manifest describes SEO prospect research and outreach workflows, where web search is expected, but this helper also accesses a credential file in the user's home directory to obtain a secret. Reading local secrets is a distinct capability not mentioned in the skill description and is not inherent to prospect research itself.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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/perplexity_search.py (reported line 28)May include surrounding context.

python
CONFIG_PATH = Path.home() / ".openclaw" / "mcp-config" / "perplexity.json"
API_URL = "https://api.perplexity.ai/chat/completions"


def load_api_key() -> str:

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The skill's purpose is SEO prospect research and outreach, for which network access is expected, but launching an external subprocess is a broader execution capability than the manifest implies. This introduces command-execution behavior unrelated to the user-facing intent of researching prospects.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.