Back to skill

Security audit

Padel Americano Game Scorer

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent padel tournament tool, but its PDF export runs Chromium with sandboxing disabled on data loaded from editable JSON files.

Review the PDF export behavior before installing. The core tournament scheduling and scoring features are local and purpose-aligned, but avoid using export-pdf on tournament JSON files from untrusted people or locations. Prefer regenerating state through the CLI, use a dedicated working directory, and consider isolating PDF export until the skill removes --no-sandbox and validates or escapes all rendered state fields.

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
scripts/padel_americano.py:747
Finding
HTML Injection During PDF Export with Chromium Sandbox Disabled<![CDATA[ ## Vulnerability Details **File Location**: `scripts/padel_americano.py:747-750`, `scripts/padel_americano.py:769`, and `scripts/padel_americano.py:780-797` **Vulnerability Type**: HTML injection through unvalidated tournament state combined with unsafe browser execution **Risk Level**: High ### Vulnerable Code ```python for rnd in state["rounds"]: for m in rnd["matches"]: score = "-" if not m.get("score") else f"{m['score'][0]}-{m['score'][1]}" schedule_rows.append( f"<tr><td>{rnd['round']}</td><td>{m['court']}</td><td>{esc(' / '.join(m['team1']))}</td>" f"<td>{esc(' / '.join(m['team2']))}</td><td>{score}</td></tr>" ) ``` ```python <div class='meta'>Players: {esc(', '.join(state['players']))} | Courts: {state['courts']} | Rounds: {state['round_count'] or len(state['rounds'])} | Points/game: {state['points_per_game']}</div> ``` ```python def cmd_export_pdf(args): state = load_state(args.state) out = Path(args.out) out.parent.mkdir(parents=True, exist_ok=True) html_path = out.with_suffix(".html") html_path.write_text(html_report(state), encoding="utf-8") chrome = shutil.which("google-chrome") or shutil.which("chromium") or shutil.which("chromium-browser") if not chrome: print(f"No Chrome/Chromium found. Wrote HTML instead: {html_path}") return subprocess.run([ chrome, "--headless", "--disable-gpu", "--no-sandbox", f"--print-to-pdf={out}", html_path.as_uri(), ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) print(f"Exported {out}") ``` ### Technical Analysis The application loads tournament state directly from a JSON file and does not validate it against a strict schema. Although tournament and player names are escaped in the HTML report, several other JSON-derived fields are inserted into HTML without escaping, including: - Round identifiers - Court identifiers - Score ele ...[truncated 2719 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Validate state files against a strict schema before use** - Require round, court, court-count, round-count, points, and score values to be integers. - Reject booleans, strings, objects, and arrays where numeric values are expected. - Enforce sensible minimum and maximum values. - Validate the structure and element types of teams, matches, rounds, and scores. 2. **Escape every value inserted into HTML** - Apply `html.escape(str(value))` to all JSON-derived values, including fields expected to be numeric. - Use a template engine with automatic HTML escaping rather than manually constructing HTML with f-strings. Example: ```python f"<tr><td>{esc(rnd['round'])}</td><td>{esc(m['court'])}</td>" ``` The score should also be escaped after formatting: ```python score = "-" if not m.get("score") else f"{m['score'][0]}-{m['score'][1]}" safe_score = esc(score) ``` 3. **Remove `--no-sandbox`** - Run Chromium with its normal sandbox enabled. - If the deployment environment cannot support the Chromium sandbox, perform conversion inside a separately isolated container or restricted worker account. - Limit the worker's filesystem access and disable network access when it is not required. 4. **Restrict browser capabilities** - Disable JavaScript for report rendering where supported. - Block external network requests and external resource loading. - Use a restrictive Content Security Policy, such as one that permits only inline styles required by the report and denies scripts, frames, objects, and network connections. 5. **Handle generated HTML securely** - Create intermediate files with restrictive permissions. - Prefer a secure temporary directory. - Remove the intermediate HTML after successful PDF creation unless the user explicitly requests an HTML copy. - Avoid overwriting unrelated files through ambiguous output paths. 6. **Fail safely** - Report Chr ...[truncated 168 chars]
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • 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
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill invokes a Python CLI that reads and writes tournament JSON files and can export PDFs, which implies shell execution plus filesystem access. Because the manifest does not declare any tool scope such as allowed-tools or permissions, an agent may grant broader-than-necessary capabilities at runtime, increasing the chance of unintended file access or command execution beyond the skill's operational needs.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The PDF export path writes attacker-influenced HTML to disk and then opens it in Chrome/Chromium with --headless and --no-sandbox. Even though player names are HTML-escaped, disabling the browser sandbox removes an important defense boundary, so any browser vulnerability, unsafe local resource access, or renderer compromise during PDF generation could directly impact the host running the skill.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if not chrome:
        print(f"No Chrome/Chromium found. Wrote HTML instead: {html_path}")
        return
    subprocess.run([
        chrome,
        "--headless",
        "--disable-gpu",
Confidence
89% confidence
Finding
The code launches an external browser process to render a locally generated HTML file into PDF. While it avoids shell injection by passing an argument list to subprocess.run, it still executes a large, complex third-party binary on attacker-influenced content and does so with browser sandboxing disabled, which increases the consequences of any browser exploit or unsafe file handling during rendering.

Static analysis

No suspicious patterns detected.