Back to skill

Security audit

Sports Ticker

Security checks across malware telemetry and agentic risk

Overview

This sports-alert skill mostly matches its purpose, but it also looks for another skill's search API key and uses under-disclosed third-party search fallbacks.

Review before installing. Use it only if you are comfortable with ESPN plus optional Brave/Serper searches, and do not allow it to read web-search-plus .env credentials. If you enable alerts, inspect the generated cron jobs and Telegram delivery settings so recurring notifications run only when and where you intend.

SkillSpector

By NVIDIA
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (19)

Lp3

Medium
Category
MCP Least Privilege
Confidence
82% confidence
Finding
The skill metadata declares no permissions, yet the documented functionality clearly implies shell execution, file reads/writes, network access, and environment interaction. This creates a transparency and trust problem: users and orchestrators cannot accurately assess the skill's operational footprint, increasing the chance of unintended data exposure or execution in overly permissive contexts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The documented purpose presents the skill as a simple ESPN-based sports ticker with no API keys needed, but the analyzed behavior includes additional web-search services, API key handling, reading another skill's .env path, scheduled job generation, and persistent local state. That mismatch is dangerous because it conceals materially broader data access and execution behavior than a user would reasonably expect, especially the cross-skill secret access pattern.

Context-Inappropriate Capability

Medium
Confidence
99% confidence
Finding
The skill reads another skill's .env file to obtain a SERPER_API_KEY, which is cross-skill credential access. That violates isolation expectations and can silently reuse secrets that the current skill was never granted, expanding blast radius and enabling unauthorized external requests.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill advertises ESPN-based live sports alerts but falls back to general web search providers, sending team queries to unrelated third parties. This broadens data egress and trust assumptions beyond what users would reasonably expect from the skill description, especially when combined with credential borrowing from another skill.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The skill description frames this as an ESPN-based sports ticker, but the code silently expands scope into general web search via Brave and Serper. That mismatch matters because it changes the data flow, privacy profile, and trust boundary: team queries are sent to third-party search providers not implied by the manifest.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The skill reads API credentials from another skill's .env file, crossing isolation boundaries between components and enabling credential reuse without authorization. This is dangerous because it normalizes secret harvesting from neighboring skills and can silently expand this skill's capabilities using credentials the user never intended it to access.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The README promotes auto-cron generation and live alerting without clearly emphasizing that this can cause recurring outbound notifications on the user's behalf. In an agent-integrated environment, that omission can lead users to enable persistent scheduled actions without fully understanding the volume or duration of messages that may be sent.

Missing User Warnings

Low
Confidence
71% confidence
Finding
The setup command explicitly documents a force mode that overwrites an existing config, but the warning is minimal and does not clearly emphasize data loss or recommend backup/confirmation safeguards. While not inherently malicious, destructive behavior without strong warning can lead to accidental loss of user configuration and disruption of scheduled alerts.

Vague Triggers

High
Confidence
95% confidence
Finding
The manifest registers very generic triggers like "sports", "score", "match", and "game", which are common terms in ordinary conversation and can cause the skill to activate outside clearly intended contexts. In an agent ecosystem, overly broad activation increases the chance of unsolicited invocation, prompt interception, or routing user requests to this skill when a more appropriate or safer handler should respond.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
Reading credentials from another skill's .env file without clear disclosure undermines user consent and violates reasonable expectations about secret scoping. Even if used only for search fallback, the hidden cross-skill secret access is a meaningful security and transparency problem.

Missing User Warnings

Low
Confidence
85% confidence
Finding
The code transmits search queries containing team names and the current date to external search providers without visible user disclosure or consent. While the data is low sensitivity, the undisclosed network transmission expands external exposure beyond the ESPN-based functionality users would expect.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def _try_serper(query: str) -> str:
        # Load key from env or web-search-plus .env
        serper_key = os.environ.get("SERPER_API_KEY", "")
        if not serper_key:
            env_paths = [
                Path(__file__).parent.parent.parent / "web-search-plus" / ".env",
Confidence
98% confidence
Finding
os.environ.get("SERPER_API_KEY

External Transmission

Medium
Category
Data Exfiltration
Content
try:
            params = urllib.parse.urlencode({"q": query, "count": 3})
            req = urllib.request.Request(
                f"https://api.search.brave.com/res/v1/web/search?{params}",
                headers={"Accept": "application/json", "X-Subscription-Token": brave_key},
            )
            data = json.loads(urllib.request.urlopen(req, timeout=10).read())
Confidence
84% confidence
Finding
https://api.search.brave.com/

Credential Access

High
Category
Privilege Escalation
Content
return ""

    def _try_serper(query: str) -> str:
        # Load key from env or web-search-plus .env
        serper_key = os.environ.get("SERPER_API_KEY", "")
        if not serper_key:
            env_paths = [
Confidence
99% confidence
Finding
.env

Credential Access

High
Category
Privilege Escalation
Content
serper_key = os.environ.get("SERPER_API_KEY", "")
        if not serper_key:
            env_paths = [
                Path(__file__).parent.parent.parent / "web-search-plus" / ".env",
                Path(os.environ.get("HOME", "/root")) / "clawd/skills/web-search-plus/.env",
            ]
            for ep in env_paths:
Confidence
99% confidence
Finding
.env"

Credential Access

High
Category
Privilege Escalation
Content
if not serper_key:
            env_paths = [
                Path(__file__).parent.parent.parent / "web-search-plus" / ".env",
                Path(os.environ.get("HOME", "/root")) / "clawd/skills/web-search-plus/.env",
            ]
            for ep in env_paths:
                if ep.exists():
Confidence
99% confidence
Finding
.env"

Credential Access

High
Category
Privilege Escalation
Content
def _load_key(env_var: str, env_file_key: str) -> str:
    """Load API key from env or web-search-plus .env file."""
    key = os.environ.get(env_var, "")
    if not key:
        env_paths = [
Confidence
98% confidence
Finding
.env

Credential Access

High
Category
Privilege Escalation
Content
key = os.environ.get(env_var, "")
    if not key:
        env_paths = [
            Path(__file__).parent.parent.parent / "web-search-plus" / ".env",
            Path(os.environ.get("HOME", "/root")) / "clawd/skills/web-search-plus/.env",
        ]
        for ep in env_paths:
Confidence
99% confidence
Finding
.env"

Credential Access

High
Category
Privilege Escalation
Content
if not key:
        env_paths = [
            Path(__file__).parent.parent.parent / "web-search-plus" / ".env",
            Path(os.environ.get("HOME", "/root")) / "clawd/skills/web-search-plus/.env",
        ]
        for ep in env_paths:
            if ep.exists():
Confidence
99% confidence
Finding
.env"

VirusTotal

66/66 vendors flagged this skill as clean.

View on VirusTotal

Static analysis

No suspicious patterns detected.