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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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)

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Missing User Warnings

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

Content

No source excerpt is available for this finding.

Missing User Warnings

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

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/check_setup.py (reported line 67)May include surrounding context.

python
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,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/setup.py (reported line 47)May include surrounding context.

python
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

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/setup.py (reported line 103)May include surrounding context.

python
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.")

Missing User Warnings

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

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.