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

Content
View full analysis
{rnd['round']}{m['court']}{esc(' / '.join(m['team1']))}" f"{esc(' / '.join(m['team2']))}{score}" ) ``` ```python
Players: {esc(', '.join(state['players']))} | Courts: {state['courts']} | Rounds: {state['round_count'] or len(state['rounds'])} | Points/game: {state['points_per_game']}
``` ```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
View remediation
{esc(rnd['round'])}{esc(m['court'])}" ``` 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)

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · scripts/padel_americano.py (reported line 788)May include surrounding context.

python
if not chrome:
        print(f"No Chrome/Chromium found. Wrote HTML instead: {html_path}")
        return
    subprocess.run([
        chrome,
        "--headless",
        "--disable-gpu",