Back to skill

Security audit

Cricket Live Score

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed cricket-score-to-Telegram notifier, with some operational and URL-validation caveats but no evidence of hidden exfiltration or destructive behavior.

Before installing, confirm you are comfortable giving the skill a Telegram bot token and sending score text, chat IDs, and optional generated audio through Telegram. Use an explicit least-privilege bot token when possible, provide a real cricbuzz.com live-score URL, choose a reasonable polling interval, and avoid enabling voice memos if third-party text-to-speech processing is not acceptable.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cricket-live.py:102
Finding

Weak Cricbuzz Hostname Validation Permits Server-Side Request Forgery

Content
View full analysis

Vulnerability Details

File Location: scripts/cricket-live.py, lines 102-115
Vulnerability Type: Server-Side Request Forgery caused by incomplete hostname and redirect validation
Risk Level: Medium

python
def validate_url(url):
    """Ensure the URL is a valid Cricbuzz live score page to prevent SSRF."""
    parsed = urllib.parse.urlparse(url)
    if parsed.scheme not in ('http', 'https'):
        print(f"Error: URL must use http or https (got {parsed.scheme})", file=sys.stderr)
        sys.exit(1)
    if not parsed.hostname or not parsed.hostname.endswith('cricbuzz.com'):
        print(f"Error: URL must be a cricbuzz.com domain (got {parsed.hostname})", file=sys.stderr)
        sys.exit(1)
    return url


def fetch_raw(match_url):
    req = urllib.request.Request(match_url, headers={"User-Agent": "Mozilla/5.0"})
    return urllib.request.urlopen(req, timeout=15).read().decode("utf-8", errors="ignore")

Technical Analysis

The hostname check uses parsed.hostname.endswith('cricbuzz.com') without requiring a DNS label boundary. Consequently, unrelated attacker-controlled domains such as evilcricbuzz.com satisfy the validation rule.

The implementation also does not validate the resolved IP address. An accepted hostname could resolve or rebind to a loopback, private, link-local, or otherwise restricted address. In addition, urllib.request.urlopen() follows HTTP redirects by default, but redirect destinations are not passed through validate_url(). An initially permitted destination could therefore redirect the request to an internal service.

Although the function claims to prevent SSRF, these weaknesses allow the application to issue requests beyond the Cricbuzz hosts required for its declared functionality.

Attack Path

  1. An attacker or untrusted caller supplies a URL such as https://evilcricbuzz.com/live-score.
  2. urlparse() extracts evilcricbuzz.com as the hostname ...[truncated 1383 chars]
Remediation
View remediation

Remediation Suggestions

  1. Permit only exact Cricbuzz domains with a DNS label boundary:
    python
    hostname = (parsed.hostname or "").rstrip(".").lower()
    if hostname != "cricbuzz.com" and not hostname.endswith(".cricbuzz.com"):
        raise ValueError("Only Cricbuzz hosts are permitted")
    
  2. Prefer a narrow allowlist of the exact hostnames genuinely required by the Skill rather than permitting every subdomain.
  3. Require HTTPS and reject embedded credentials, fragments, unexpected ports, and malformed hostnames.
  4. Disable automatic redirects or implement a redirect handler that revalidates every destination before following it.
  5. Resolve all destination addresses before connecting and reject loopback, private, link-local, reserved, multicast, and unspecified IP ranges for both IPv4 and IPv6.
  6. Protect against DNS rebinding by ensuring the validated address is the address used for the connection, or enforce equivalent egress restrictions outside the application.
  7. Apply network-level egress controls so this process can connect only to approved Cricbuzz, Telegram, and text-to-speech endpoints.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/cricket-live.py:448
Finding

Unbounded Update Interval Allows Excessive Network Polling

Content
View full analysis

Vulnerability Details

File Location: scripts/cricket-live.py, lines 448-456 and 529
Vulnerability Type: Missing numeric input bounds and resource-consumption control
Risk Level: Low

python
parser.add_argument("--interval", type=int, default=300, help="Update interval in seconds (default: 300)")
parser.add_argument("--chat-id", required=True, help="Telegram chat ID")
parser.add_argument("--bot-token", default=None, help="Telegram bot token (falls back to OpenClaw config if not provided)")
parser.add_argument("--voice", action="store_true", default=False, help="Send voice memo with each update")
args = parser.parse_args()

bot_token = args.bot_token or os.environ.get("TELEGRAM_BOT_TOKEN") or load_bot_token()
match_url = validate_url(args.url)
interval = args.interval
chat_id = args.chat_id
voice_enabled = args.voice
python
        time.sleep(interval)

Technical Analysis

The --interval argument accepts any integer without lower or upper bounds. Supplying zero causes time.sleep(0) to return immediately, so the infinite loop repeatedly downloads the score page without an intentional delay. A negative value causes time.sleep() to raise an exception and terminate the program after the first iteration.

The absence of a minimum interval permits unnecessary CPU activity and excessive outbound traffic to Cricbuzz. Depending on score changes and voice settings, it may also contribute to repeated downstream Telegram or text-to-speech traffic, although score deduplication reduces that secondary effect.

Attack Path

  1. A caller starts the Skill with --interval 0.
  2. The script enters its indefinite polling loop.
  3. Each iteration invokes fetch_raw(match_url).
  4. time.sleep(0) provides no meaningful pause.
  5. The process continuously sends network requests until it is stopped, the match is detected as complete, or another error interrupts execution.

Impact Asses

...[truncated 521 chars]

Remediation
View remediation

Remediation Suggestions

  1. Reject intervals below a safe operational minimum, such as 30 or 60 seconds.
  2. Enforce the constraint during argument parsing:
    python
    def valid_interval(value):
        interval = int(value)
        if interval < 30:
            raise argparse.ArgumentTypeError("interval must be at least 30 seconds")
        return interval
    
    parser.add_argument("--interval", type=valid_interval, default=300)
    
  3. Consider defining a reasonable maximum to prevent accidental configurations that effectively disable useful updates.
  4. Add exponential backoff and jitter after network failures or rate-limit responses.
  5. Document the permitted interval range in SKILL.md and ensure callers cannot bypass it through alternative invocation paths.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (11)

Tainted flow: 'req' from os.environ.get (line 82, 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/cricket-live.py (reported line 50)May include surrounding context.

python
data = json.dumps({"chat_id": chat_id, "text": text, "parse_mode": "Markdown"}).encode()
    req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
    try:
        urllib.request.urlopen(req, timeout=10)
    except Exception:
        data = json.dumps({"chat_id": chat_id, "text": text}).encode()
        req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})

Tainted flow: 'req' from os.environ.get (line 53, 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/cricket-live.py (reported line 54)May include surrounding context.

python
except Exception:
        data = json.dumps({"chat_id": chat_id, "text": text}).encode()
        req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
        urllib.request.urlopen(req, timeout=10)


def send_voice(text, bot_token, chat_id):

Tainted flow: 'req' from os.environ.get (line 53, 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/cricket-live.py (reported line 84)May include surrounding context.

python
url = f"https://api.telegram.org/bot{bot_token}/sendVoice"
        req = urllib.request.Request(url, data=bytes(body),
            headers={"Content-Type": f"multipart/form-data; boundary={boundary}"})
        urllib.request.urlopen(req, timeout=15)
        os.unlink(tmp.name)
    except Exception as e:
        print(f"Voice error: {e}", file=sys.stderr)

Tainted flow: 'req' from os.environ.get (line 53, 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/cricket-live.py (reported line 105)May include surrounding context.

python
def fetch_raw(match_url):
    req = urllib.request.Request(match_url, headers={"User-Agent": "Mozilla/5.0"})
    return urllib.request.urlopen(req, timeout=15).read().decode("utf-8", errors="ignore")


def extract_match_info(raw):

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared purpose understates important behaviors: the skill can source Telegram credentials from local config or environment and uses external services/libraries for voice generation and message delivery. This mismatch is dangerous because users may authorize a seemingly simple score-following skill without realizing it accesses secrets and transmits content to third-party services.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill documents network access and credential consumption from environment/config, but it does not declare any explicit tool scope or permissions boundary. In an agent ecosystem, missing scope declarations reduce transparency and can allow a skill to use broader capabilities than a user expects, especially when it can read local secrets and send data over the network.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The introductory description emphasizes convenience but does not prominently warn that match data and optional generated voice content will be transmitted to Telegram via the Bot API. In privacy- or enterprise-sensitive environments, undisclosed outbound transmission can cause unintended data sharing and weakens informed consent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The skill lists multiple credential sources, including environment variables and a local config file, without a strong warning about secret handling. This increases the risk of accidental secret exposure, misuse of unrelated bot tokens present on the host, or user surprise that local credentials will be consumed automatically.

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/cricket-live.py (reported line 46)May include surrounding context.

python
# ─── Telegram helpers ─────────────────────────────────────────────────────────

def send_telegram(text, bot_token, chat_id):
    url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
    data = json.dumps({"chat_id": chat_id, "text": text, "parse_mode": "Markdown"}).encode()
    req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
    try:

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/cricket-live.py (reported line 81)May include surrounding context.

python
# ─── Telegram helpers ─────────────────────────────────────────────────────────

def send_telegram(text, bot_token, chat_id):
    url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
    data = json.dumps({"chat_id": chat_id, "text": text, "parse_mode": "Markdown"}).encode()
    req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
    try:

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script sets gTTS(clean, lang='en'), forcing spoken output to English regardless of user preference. This is a natural-language locale policy issue because the file does not offer a language choice or explain why English is required.

Content

No source excerpt is available for this finding.