Back to skill

Security audit

Handsfree Windows Control

Security checks for vulnerabilities and agentic risk

Overview

This Windows automation skill is coherent, but it should be reviewed carefully because setup installs mutable unpinned external code and creates persistent browser/session state for broad desktop and browser control.

Install only if you trust the external handsfree-windows repository and are comfortable with a setup script downloading and installing code at runtime. Prefer a non-admin, isolated Python environment or VM, use --no-browser unless browser automation is needed, use dedicated automation accounts/profiles, review recorded macros and screenshots before sharing, and clear ~/.handsfree-windows when you no longer need stored browser sessions.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/setup.py:37
Finding
Mutable Remote Repository Is Retrieved and Installed Without Integrity Verification## Vulnerability Details **File Location**: `scripts/setup.py`, lines 37-88 **Vulnerability Type**: Remote payload retrieval and execution **Risk Level**: High ### Vulnerable Code ```python REPO_URL = "https://github.com/lijinlar/handsfree-windows.git" DEFAULT_INSTALL_DIR = Path.home() / ".handsfree-windows" / "cli" def clone_or_pull(install_dir: Path) -> bool: if (install_dir / ".git").exists(): print(f"\n[INFO] Repo already exists at {install_dir}. Pulling latest...") rc = run(["git", "pull", "--ff-only"], cwd=install_dir, desc="git pull") else: print(f"\n[INFO] Cloning handsfree-windows into {install_dir} ...") install_dir.parent.mkdir(parents=True, exist_ok=True) rc = run( ["git", "clone", REPO_URL, str(install_dir)], desc=f"git clone {REPO_URL}", ) return rc == 0 def pip_install(install_dir: Path) -> bool: print(f"\n[INFO] Installing handsfree-windows (editable) from {install_dir} ...") rc = run( [sys.executable, "-m", "pip", "install", "-e", str(install_dir)], desc="pip install -e", ) return rc == 0 ``` ### Technical Analysis The setup process retrieves the current contents of a mutable Git branch and immediately passes that checkout to `pip install -e`. It does not pin an audited commit or immutable release, verify a cryptographic checksum or signature, or validate that a pre-existing repository has the expected remote URL. A Python package installation can execute package build hooks and backend code. In addition, editable installation makes the installed command continue to reference the mutable checkout. The effective code executed by the skill can therefore differ from the code that existed when the skill was audited. The pre-existing-directory path creates another trust-boundary problem: the presence of `.git` is treated as sufficient proof that the directory is the e ...[truncated 1473 chars]
Remediation
## Remediation Suggestions 1. Pin the dependency to a specifically reviewed commit hash or immutable, versioned release rather than pulling the active branch. 2. Verify the downloaded artifact with a trusted cryptographic hash or release signature before installation. 3. For an existing checkout, verify the canonicalized remote URL exactly matches the approved repository before running any Git operation. 4. Fetch the pinned commit explicitly and verify `HEAD` equals the expected hash. Do not use an unrestricted `git pull`. 5. Prefer a hash-verified wheel from a controlled package repository over an editable installation. 6. Avoid editable installations for production use because subsequent changes to the checkout immediately affect executable behavior. 7. Run installation with a non-privileged account in an isolated virtual environment, and do not execute package installation as an administrator. 8. Review and lock all transitive dependencies of the retrieved package.

T08 · Insecure Dependencies

Warning
Location
scripts/setup.py:91
Finding
Unpinned Playwright Package and Browser Artifacts Are Installed at Runtime## Vulnerability Details **File Location**: `scripts/setup.py`, lines 91-105 **Vulnerability Type**: Insecure dependency installation **Risk Level**: Medium ### Vulnerable Code ```python def install_playwright_chromium() -> bool: print("\n[INFO] Installing Playwright + Chromium browser (~200 MB download, one-time) ...") # Ensure playwright Python package is installed first rc = run( [sys.executable, "-m", "pip", "install", "playwright"], desc="pip install playwright", ) if rc != 0: return False rc = run( [sys.executable, "-m", "playwright", "install", "chromium"], desc="playwright install chromium", ) return rc == 0 ``` ### Technical Analysis The installer requests `playwright` without an exact version or hash constraint. Package selection therefore depends on the package index configuration and the latest version available at installation time. The installed package is then trusted to select and download a Chromium artifact without an application-level version pin or independent integrity check in this project. As a result, the audited source does not fully determine which Python package code and browser binary will be installed. A compromised upstream release, compromised package distribution channel, unsafe custom package-index configuration, or future incompatible release could alter installation-time and runtime behavior. ### Attack Path 1. A malicious or compromised Playwright package becomes available through a package index trusted by the user's pip configuration, or an attacker controls an additional index configured with sufficient precedence. 2. The user or agent runs setup without `--no-browser`. 3. `pip install playwright` resolves and installs the unpinned package. 4. Package installation logic executes with the setup process's privileges. 5. The installed module is invoked as `python -m playwright install chromiu ...[truncated 730 chars]
Remediation
## Remediation Suggestions 1. Pin Playwright to an exact reviewed version, for example through a locked requirements file. 2. Use `--require-hashes` with approved hashes for Playwright and all transitive Python dependencies. 3. Configure pip to use only an explicitly trusted HTTPS index and disable unapproved extra indexes. 4. Pin the corresponding Chromium revision and independently verify its expected checksum or signature before use. 5. Cache approved artifacts in a controlled internal repository to prevent unexpected upstream changes. 6. Install dependencies inside a dedicated virtual environment under a non-privileged account. 7. Add dependency scanning and periodic controlled upgrades rather than resolving the latest release during every setup. 8. Fail closed when artifact verification is unavailable instead of continuing with an unverified browser installation.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill instructs the agent to run shell commands such as `python scripts/setup.py`, `pip uninstall`, and multiple `hf` CLI invocations, but it does not declare any `permissions` or `allowed-tools` scope. That creates an authority mismatch: a caller may not realize the skill requires arbitrary shell execution, package installation, network downloads, filesystem writes, and browser automation with persisted session data. In this context, the missing scope is more dangerous because the skill explicitly performs first-use setup from external sources and stores browser profiles/cookies on disk.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The documentation explicitly states that browser profiles and session state are stored persistently on disk, but provides no warning that cookies, authentication state, history-like artifacts, and other sensitive browsing data may remain after use. In a desktop/browser automation skill, this increases the chance that an agent or user will unknowingly reuse or expose prior session data, especially on shared machines or when automating authenticated websites.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The screenshot command saves captured browser content to disk but does not warn that screenshots may contain sensitive on-screen data such as personal information, credentials, tokens, or internal application content. Because this skill is designed for automated browser and desktop interaction, silent persistence of captures can create confidentiality and retention risks.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def check_playwright_browsers() -> None:
    """Check if Playwright browser binaries are downloaded (optional)."""
    try:
        result = subprocess.run(
            [sys.executable, "-m", "playwright", "install", "--dry-run"],
            capture_output=True,
            text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd: list[str], cwd: Path | None = None, desc: str = "") -> int:
    print(f"\n>>> {desc or ' '.join(cmd)}")
    result = subprocess.run(cmd, cwd=str(cwd) if cwd else None)
    if result.returncode != 0:
        print(f"[ERROR] Command failed (exit {result.returncode}): {' '.join(cmd)}")
    return result.returncode
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
check = script_dir / "check_setup.py"
    if check.exists():
        print("\n[INFO] Running environment check ...")
        subprocess.run([sys.executable, str(check)])
    else:
        print("\n[WARN] check_setup.py not found, skipping verification.")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
Confidence
85% confidence
Finding
The `record --out <macro.yaml>` command writes a macro file to disk, but the reference does not warn that using it will create or overwrite local files containing recorded automation steps. Those files may capture sensitive workflow details, URLs, window titles, or typed actions, and unexpected file creation/modification can matter in agentic environments.

Static analysis

No suspicious patterns detected.