Back to skill

Security audit

Competitor Radar

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its competitor-monitoring purpose, but it needs review because it can persistently scrape configured sites, turn scraped text into agent instructions, store sensitive license data, and send reports to external chat services.

Review before installing. Remove or replace the bundled active competitors, restrict monitored URLs to intended public http/https domains, disable or rewrite the [AGENT] prompt blocks so scraped pages are treated only as data, pin dependencies, set explicit retention/deletion rules, and only configure chat delivery channels where you are comfortable sending the digest contents and using those service tokens.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (4)

T01 · Skill Instruction Hijacking

Error
Location
scripts/diff.py:216
Finding

Indirect Prompt Injection Through Scraped Competitor Content

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/scrape.py:67
Finding

Server-Side Request Forgery Through Unrestricted Monitoring URLs

Content
View full analysis
str | None: """Fetch page HTML via curl. Returns text or None on failure.""" try: result = subprocess.run( [ "curl", "-sL", "--max-time", str(timeout), "--user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/122.0.0.0 Safari/537.36", "--compressed", url, ], capture_output=True, text=True, timeout=timeout + 5 ) if result.returncode != 0 or not result.stdout.strip(): return None return result.stdout except Exception as e: print(f"[curl] Error fetching {url}: {e}", file=sys.stderr) return None ``` ```python # scripts/scrape.py:109-130 try: with sync_playwright() as pw: browser = pw.chromium.launch(headless=True) ctx = browser.new_context( user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/122.0.0.0 Safari/537.36", viewport={"width": 1280, "height": 900}, ) page = ctx.new_page() page.goto(url, wait_until="networkidle", timeout=30000) if wait_selector: try: page.wait_for_selector(wait_selector, timeout=5000) except PWTimeout: pass html = page.content() browser.close() return html except Exception as e: print(f"[playwright] Err ...[truncated 2736 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/license.py:43
Finding

License Key Exposed in Process Arguments and Plaintext Storage

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
requirements.txt:4
Finding

Unpinned Third-Party Dependencies Permit Unreviewed Future Releases

Content
View full analysis
=6.0.0 # RSS/Atom feed parsing html2text>=2024.2.26 # HTML to readable text conversion playwright>=1.43.0 # Headless browser for JS-heavy pages (pricing pages) ``` ```bash # README.md:28-33 cd ~/.openclaw/workspace/skills/competitor-radar pip install -r requirements.txt playwright install chromium ``` ### Technical Analysis The dependency specifications use open-ended minimum versions. A fresh installation can therefore select any later release published under those package names. The project provides no lockfile, hashes, upper bounds, or reproducible environment definition. This is not evidence that any currently named package is malicious. The weakness is that the reviewed source tree does not determine the exact code that users will install. A compromised package account, malicious future release, or incompatible update could introduce code that was never covered by this audit. The separate Playwright browser installation also downloads an external browser artifact without project-level integrity pinning. ### Attack Path 1. A user follows the documented installation procedure. 2. `pip` resolves the newest package versions satisfying the `>=` constraints. 3. A compromised or malicious future release is selected. 4. Package installation hooks or imported runtime code execute with the installing or OpenClaw user's privileges. 5. The dependency can access the same files, environment variables, and network resources available to the Skill process. ### Impact Assessment A malicious dependency can execute arbitrary Python code under the account installing or running the Skill. That scope may include access to messaging tokens, the optional GitHub token, snapshots, digests, license data, and the user's O ...[truncated 156 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (41)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The description claims a broad competitor-radar capability spanning six monitoring signals plus scheduled digest delivery and same-day alerts. The actual code chunk is narrowly scoped to GitHub activity only: repo listing, star counts, weekly star delta, new repos, and recent releases. That partially overlaps with the declared GitHub-related signal, but it does not implement the other five signals, alerting, or messaging integrations. The code’s primary purpose is therefore materially narrower than the declared purpose, making the description inaccurate for this supplied code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The code chunk does not perform competitor monitoring, content diffing, RSS/blog tracking, job tracking, GitHub analysis, alerting, or message delivery to Slack/Telegram/WhatsApp/Discord. Instead, its primary purpose is monetization and access control: validating a Gumroad license, persisting license information, and reporting account tier. While licensing could be a supporting component of a larger product, this code materially differs from the declared skill description and introduces undeclared capabilities involving external API calls to Gumroad and local license storage. Therefore this chunk is a clear description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The code aligns partially with the declared competitor-monitoring purpose: it can discover relevant URLs, scrape pricing pages, capture homepage hero text for positioning, and collect blog/RSS updates. However, the declared description promises a broader 6-signal monitoring system with notification delivery and alerting. This code chunk only covers a subset of those signals and mainly performs scraping plus local snapshot storage. There is no implementation for job postings, GitHub metrics/releases, funding/pricing keyword alerting, digest generation, or sending results to Slack/Telegram/WhatsApp/Discord. Because several core declared capabilities are absent, the description overstates what this supplied code chunk actually does.

Content

No source excerpt is available for this finding.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/diff.py (reported line 217)May include surrounding context.

python
"Are they moving upmarket? Simplifying? Cutting the free tier? Adding enterprise? "
        "What should competitors know about this?"
    )
    return prompt_hint


def llm_interpret_hero_diff(diff_result: dict, competitor_name: str) -> str:

Ssd 1

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The code embeds pricing-derived llm_prompt text from upstream data directly into a digest as an [AGENT: ...] instruction block intended for later model expansion. Because that upstream content ultimately comes from scraped competitor-controlled pages, an attacker can plant prompt-injection text that is preserved and later executed/interpreted by an agent, potentially causing data exfiltration, tool misuse, or unsafe downstream actions.

Content

No source excerpt is available for this finding.

Ssd 1

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The code similarly inserts homepage-derived llm_prompt text into an agent instruction block, turning untrusted website content into future agent directives. In this skill context, competitor websites are explicitly monitored and their content changes are attacker-controllable, which makes prompt-injection especially relevant and increases the likelihood of malicious instruction propagation into Slack/Discord digests or later automation steps.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The top-level description says the skill delivers digests and alerts to Slack, Telegram, WhatsApp, or Discord, but it does not clearly warn that collected competitor data will be transmitted to external third-party messaging platforms. This is dangerous because users may unintentionally exfiltrate scraped or derived monitoring data into services with separate retention, access control, and compliance properties.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The README describes weekly digests and archives but does not clearly warn users that competitor configurations, snapshots, jobs, digests, and alerts are stored persistently, with snapshots noted elsewhere as 'never deleted.' This creates a real transparency and data-retention risk because users may enable the skill without understanding that collected content is retained long-term and could accumulate sensitive or proprietary third-party data.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The README advertises broad natural-language triggers such as 'What changed at Notion this week?' and 'Who is Figma hiring?' that overlap with ordinary chat. In an agent environment, this can cause unintended skill invocation and unauthorized scraping, querying, or delivery actions when a user is merely conversing rather than explicitly invoking the tool.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill instructs use of shell execution, file reads/writes, environment inspection, and network access but does not declare any tool scope or allowed-tools boundary. That increases the blast radius of prompt injection or operator error because the runtime may grant broader capabilities than users expect, especially for a user-invocable skill that scrapes sites and edits local state.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill advertises sending digests and alerts to Slack, Telegram, WhatsApp, or Discord without clearly warning that competitor data and possibly derived analysis will be transmitted to third-party services. That creates a data-sharing and privacy risk, especially if users track sensitive targets or include internal annotations in stored digests.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The trigger phrases are broad enough to overlap with ordinary conversation, increasing the chance the skill activates on ambiguous user input. In a skill with shell, network, and file-write behaviors, accidental activation can lead to unintended scraping, local state modification, cron creation, or outbound actions without sufficiently explicit user intent.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
91% confidence
Finding

This code performs outbound network requests to attacker-influenced URLs from competitor configuration using curl via subprocess. Although it avoids shell injection by passing an argument list, it still enables SSRF-style behavior, allowing the skill to reach internal services, cloud metadata endpoints, or other unintended network locations if an attacker can add or modify competitor URLs.

Content

Scanner excerpt · scripts/alert.py (reported line 72)May include surrounding context.

python
def curl_fetch(url: str, timeout: int = 10) -> str | None:
    try:
        result = subprocess.run(
            [
                "curl", "-sL", "--max-time", str(timeout),
                "--user-agent", "Mozilla/5.0",

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

python
+ ["--channels"] + channels
        )
        try:
            subprocess.run(cmd, timeout=30)
        except Exception as e:
            print(f"[alert] Delivery error: {e}", file=sys.stderr)
        finally:

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

python
return False

        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
            resp = result.stdout
            if '"ok":false' in resp or '"error"' in resp:
                print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)

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

python
return False

        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
            resp = result.stdout
            if '"ok":false' in resp or '"error"' in resp:
                print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)

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

python
return False

        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
            resp = result.stdout
            if '"ok":false' in resp or '"error"' in resp:
                print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)

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

python
return False

        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
            resp = result.stdout
            if '"ok":false' in resp or '"error"' in resp:
                print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)

Tainted flow: 'cmd' from os.environ.get (line 131, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/deliver.py (reported line 142)May include surrounding context.

python
return False

        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
            resp = result.stdout
            if '"ok":false' in resp or '"error"' in resp:
                print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)

Tainted flow: 'cmd' from os.environ.get (line 131, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/deliver.py (reported line 183)May include surrounding context.

python
return False

        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
            resp = result.stdout
            if '"ok":false' in resp or '"error"' in resp:
                print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)

Tainted flow: 'cmd' from os.environ.get (line 131, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/deliver.py (reported line 221)May include surrounding context.

python
return False

        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
            resp = result.stdout
            if '"ok":false' in resp or '"error"' in resp:
                print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)

Tainted flow: 'cmd' from os.environ.get (line 131, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/deliver.py (reported line 262)May include surrounding context.

python
return False

        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
            resp = result.stdout
            if '"ok":false' in resp or '"error"' in resp:
                print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)

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

python
chunks = chunk_message(formatted, PLATFORM_LIMITS["telegram"])

    for chunk in chunks:
        url = f"https://api.telegram.org/bot{token}/sendMessage"
        payload = {
            "chat_id": chat_id,
            "text": chunk,

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

python
formatted = format_for_whatsapp(text)
    chunks = chunk_message(formatted, PLATFORM_LIMITS["whatsapp"])
    url = f"https://api.twilio.com/2010-04-01/Accounts/{sid}/Messages.json"

    for chunk in chunks:
        cmd = [

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

python
"""Run a sibling script and return its JSON stdout."""
    cmd = ["python3", str(SCRIPTS_DIR / script)] + list(args)
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
        if result.returncode != 0:
            print(f"[digest] {script} exited {result.returncode}: {result.stderr[:200]}",
                  file=sys.stderr)

Static analysis

No suspicious patterns detected.