Back to skill

Security audit

Ebusy Courts

Security checks for vulnerabilities and agentic risk

Overview

This skill is for checking eBusy tennis reservations, but it needs review because it can send your eBusy login to any configured server URL.

Install only if you trust the agent configuration that supplies EBUSY_BASE_URL and the credentials. Use a dedicated eBusy password if possible, set the base URL only to the intended HTTPS eBusy host, avoid untrusted hall profiles or shared TOOLS.md mappings, and rotate the credential if the skill may have run with an untrusted URL.

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

T09 · Insecure Skill Coding Practices

Error
Location
ebusy_api.py:40
Finding
Unrestricted Login Destination Can Expose eBusy Credentials<![CDATA[ ## Vulnerability Details **File Location**: `ebusy_api.py`, lines 40-78 **Vulnerability Type**: Unvalidated credential transmission destination **Risk Level**: High ### Vulnerable Code ```python BASE_URL = os.getenv("EBUSY_BASE_URL", "https://medenhalle.ebusy.de") USERNAME = os.getenv("EBUSY_USERNAME") PASSWORD = os.getenv("EBUSY_PASSWORD") COURT_ID = int(os.getenv("EBUSY_COURT_ID", "1")) FIRST_COURT_NO = int(os.getenv("EBUSY_FIRST_COURT_NO", "1")) class EbusyAPI: def __init__(self, base_url: str | None = None): self.base_url = base_url or BASE_URL self.session = requests.Session() def login(self, username: str, password: str) -> bool: """Log into the eBusy instance using CSRF token + session. Returns True on success, False otherwise. """ login_page = self.session.get(f"{self.base_url}/login") login_page.raise_for_status() soup = BeautifulSoup(login_page.text, "html.parser") csrf_el = soup.find("input", {"name": "_csrf"}) if not csrf_el or not csrf_el.get("value"): raise RuntimeError("Could not find CSRF token on login page") csrf_token = csrf_el["value"] login_data = { "username": username, "password": password, "_csrf": csrf_token, "remember-me": "on", } response = self.session.post( f"{self.base_url}/login", data=login_data, headers={"Accept": "application/xml, text/xml"}, ) return response.ok ``` ### Technical Analysis The client reads `EBUSY_BASE_URL` from the environment and uses it directly as the destination for authenticated network requests. It does not parse or validate the URL's scheme, hostname, port, embedded credentials, or network address. The ability to select a hall endpoint is necessary for the declared multi-hall functionality. However, allowing an unrestricted destination is broader than ne ...[truncated 3068 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the configured URL with `urllib.parse.urlparse` and require an absolute `https://` URL. 2. Reject URLs containing embedded credentials, fragments, unexpected ports, malformed hostnames, or unsupported components. 3. Maintain an explicit allowlist of approved eBusy hostnames. Configuration should select a known profile rather than allowing an arbitrary credential destination. 4. Resolve the hostname and reject loopback, private, link-local, multicast, reserved, and other non-public address ranges unless a specifically approved private deployment requires them. 5. Bind each credential set to its approved hall hostname so credentials cannot be combined with an unrelated URL. 6. Disable automatic redirects on login requests with `allow_redirects=False`, or validate the scheme and hostname of every redirect before following it. Do not forward authentication data across origins. 7. Apply explicit connection and read timeouts to all requests. 8. Update `SKILL.md` to document the HTTPS-only requirement, trusted-host policy, redirect behavior, and credential-to-host binding. 9. Keep credentials in a protected secret store with the narrowest possible runtime exposure, and rotate affected credentials if the Skill may have run with an untrusted base URL. A hardened validation flow should occur before the first network request and should fail closed if the destination cannot be conclusively identified as an approved eBusy server. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (7)

Ae1

High
Category
analysis-evasion
Content
- The skill itself (this `SKILL.md` + `ebusy_api.py`) contains only
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
- EBUSY_FIRST_COURT_NO optional: base court number in that module (e.g. 2135)

These can be provided via:
- a .env file loaded by your environment/venv,
- export in your shell profile,
- or the OpenClaw gateway/agent env configuration.
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
- EBUSY_FIRST_COURT_NO optional: base court number in that module (e.g. 2135)

These can be provided via:
- a .env file loaded by your environment/venv,
- export in your shell profile,
- or the OpenClaw gateway/agent env configuration.
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
- EBUSY_FIRST_COURT_NO optional: base court number in that module (e.g. 2135)

These can be provided via:
- a .env file loaded by your environment/venv,
- export in your shell profile,
- or the OpenClaw gateway/agent env configuration.
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill requires environment access and outbound network access to authenticate to third-party eBusy instances, but the manifest does not declare any explicit tool scope or permissions. This weakens sandboxing and reviewability because an agent may invoke a credential-using networked script without a clearly documented permission boundary.

Skill Enumeration

Medium
Category
Agent Snooping
Content
## Files

- `skills/ebusy-courts/ebusy_api.py` – core Python client
- `skills/ebusy-courts/SKILL.md` – this documentation

Optional (local-only, **do not publish**):
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Natural-Language Policy Violations

Low
Confidence
77% confidence
Finding
The usage guidance gives a German-only example for agent invocation ("Suche freie Zeiten...") and does not state that other languages are supported or that the German focus is a deliberate, documented locale limitation. Under the policy rule, forcing or implicitly constraining language without opt-in can be a natural-language policy concern.

Static analysis

No suspicious patterns detected.