Back to skill

Security audit

Local UIs

Security checks across malware telemetry and agentic risk

Overview

The skill mostly does what it says, but its local-only scanning boundary is not reliably enforced and it stores sensitive local service inventory on disk.

Install only if you are comfortable with a tool that enumerates local listening web services, records page titles and process metadata, writes a persistent HTML dashboard, and opens it by default. Use --no-open if you do not want browser launch, review or delete ~/.local/state/local-uis/dashboard.html before sharing anything, and avoid running it in sensitive network environments until redirects are constrained to loopback.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scan_uis.py:63
Finding
HTTP redirects can escape the declared loopback-only probing boundary<![CDATA[ ## Vulnerability Details **File Location**: `scan_uis.py:63-82` **Vulnerability Type**: Server-side request forgery through unrestricted redirects **Risk Level**: High ### Vulnerable Code ```python def probe(port): """Return dict if the port speaks HTTP, else None.""" url = f"http://127.0.0.1:{port}/" req = urllib.request.Request(url, headers={"User-Agent": "local-uis-scan"}) try: with urllib.request.urlopen(req, timeout=1.4) as r: status = r.status ctype = r.headers.get("Content-Type", "") body = b"" if "html" in ctype.lower() or ctype == "": body = r.read(8192) title = "" mt = re.search(rb"<title[^>]*>(.*?)</title>", body, re.I | re.S) if mt: title = html.unescape(mt.group(1).decode("utf-8", "ignore")).strip()[:90] return {"port": port, "status": status, "ctype": ctype.split(";")[0], "title": title} except urllib.error.HTTPError as e: # 401/403/404 etc still means something is serving here return {"port": port, "status": e.code, "ctype": "", "title": ""} ``` This conflicts with the safety statement in `SKILL.md:26-27`: ```markdown - The tool does not probe other hosts or non-loopback addresses. - A service bound to all interfaces may still be externally reachable; this scanner does not establish network isolation. ``` ### Technical Analysis The initial URL uses `127.0.0.1`, but `urllib.request.urlopen()` follows HTTP redirects by default. The code neither disables redirects nor validates each redirect destination. A process controlling a discovered loopback listener can return a redirect to an arbitrary URL, including: - An internal network service - A link-local service - A cloud metadata-style endpoint - An Internet host - Another service reachable using the invoking user's network permissions The resulting request is no longer constrained to loopback. The standard URL opener may also use ...[truncated 1556 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic redirect handling for scanner requests. 2. If redirects are required, validate every redirect hop rather than only the initial URL. 3. Permit only `http` URLs whose resolved destination is a loopback address. 4. Reject user-info components, non-HTTP schemes, malformed hosts, and redirect chains exceeding a small fixed limit. 5. Resolve hostnames and verify every returned address with `ipaddress.ip_address(address).is_loopback`. 6. Use a proxy-free opener so environment proxy variables cannot change request routing. 7. Add automated tests for redirects to: - Public Internet hosts - RFC 1918 private addresses - Link-local addresses - IPv4 and IPv6 loopback addresses - Hostnames resolving to non-loopback addresses - Multi-hop redirect chains 8. Update `SKILL.md` so its safety claim accurately reflects the enforced behavior. A restrictive implementation should fail closed whenever a redirect target cannot be conclusively validated as loopback. ]]>

T08 · Insecure Dependencies

Warning
Location
README.md:31
Finding
Installation guidance executes mutable third-party supply-chain components<![CDATA[ ## Vulnerability Details **File Location**: `README.md:31` **Vulnerability Type**: Unpinned third-party installer and mutable Skill source **Risk Level**: Medium ### Vulnerable Code ```bash npx skills add AntreasAntoniou/local-uis ``` ### Technical Analysis The documented installation command invokes an npm-hosted command through `npx` without specifying an exact audited version. It also identifies the Skill by a mutable repository-style name rather than an immutable commit, verified release artifact, or cryptographic digest. As a result, the repository contents reviewed in this audit do not fully determine what code will run during installation. The following components may change after the audit: - The npm package providing the `skills` executable - The package's transitive dependencies - The repository revision resolved by the Skill identifier - Installation-time behavior implemented by the external tool This is a supply-chain weakness rather than evidence that the currently reviewed source contains an embedded malicious payload. ### Attack Path 1. An attacker compromises the npm package, its publisher account, a transitive dependency, or the mutable Skill source. 2. The compromised component publishes a new version or changes the revision resolved by the unpinned identifier. 3. A user follows the README and runs: ```bash npx skills add AntreasAntoniou/local-uis ``` 4. `npx` retrieves the currently resolved package instead of a version covered by this audit. 5. The retrieved CLI or installation source executes altered behavior with the invoking user's privileges. ### Impact Assessment Successful supply-chain compromise could execute arbitrary commands with the privileges of the user performing installation. This could expose user-accessible files, modify configuration, install persistence, or retrieve additional payloads. No such malicious behavior was found in the audited project itself. The risk arises because the recommended ...[truncated 79 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin the `skills` CLI to a specific audited version: ```bash npx --yes skills@<exact-version> add <immutable-source> ``` 2. Reference the Skill using an immutable commit hash or signed release instead of a mutable repository head. 3. Publish SHA-256 checksums or cryptographic signatures for release artifacts. 4. Document how users can verify the package and Skill revision before installation. 5. Provide a manual installation method that copies audited files without executing a remote package manager CLI. 6. Review and lock transitive dependencies used by the installer. 7. Prefer package-manager controls that reject lifecycle scripts unless they are explicitly required and audited. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scan_uis.py:24
Finding
Sensitive local service inventory is stored without explicit owner-only permissions<![CDATA[ ## Vulnerability Details **File Location**: `scan_uis.py:24-26` and `scan_uis.py:155` **Vulnerability Type**: Insecure permissions on persistent sensitive output **Risk Level**: Low ### Vulnerable Code ```python OUT = Path.home() / ".local/state/local-uis" OUT.mkdir(parents=True, exist_ok=True) DASH = OUT / "dashboard.html" ``` The dashboard is later written without setting or verifying its permissions: ```python DASH.write_text(doc) ``` ### Technical Analysis The generated dashboard persistently records: - Listening HTTP ports - Process names - Process identifiers - HTTP response statuses - Page titles - Clickable service URLs The directory and file are created using default permission behavior. Their effective modes depend on the process umask and, for an existing file, its previous mode. The code does not explicitly enforce an owner-only directory mode such as `0700` or a file mode such as `0600`. On a multi-user system with a permissive umask or an existing broadly readable dashboard, another local user may be able to inspect this service inventory. Application titles and process metadata can reveal development projects, administration interfaces, notebooks, or other operational details. ### Attack Path 1. A user runs the scanner with a permissive umask, or the output path already exists with permissive permissions. 2. The scanner creates or overwrites `~/.local/state/local-uis/dashboard.html`. 3. The dashboard contains local service and process metadata. 4. Another account with filesystem access reads the dashboard. 5. The exposed inventory is used for local reconnaissance or to identify services worth targeting. ### Impact Assessment This issue does not grant additional privileges by itself. It may disclose sensitive operational metadata to other local users who can traverse the relevant home-directory path. The practical scope depends on home-directory permissions, umask configuration, pre-existing file modes, and whether the host ...[truncated 43 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Explicitly create the output directory with mode `0700`. 2. Verify and correct the mode of an existing output directory before use. 3. Write the dashboard through a securely created temporary file with mode `0600`. 4. Atomically replace the destination after the write completes. 5. Explicitly set the final dashboard mode to `0600`, including when replacing an existing file. 6. Avoid following symbolic links when creating or replacing the dashboard. 7. Consider offering a non-persistent output mode for users who do not need the launcher file. 8. Document the metadata retained in the dashboard and provide a cleanup command. The implementation should not rely solely on the caller's umask to protect sensitive service inventory. ]]>
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 (4)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if "--no-open" not in args:
        opener = shutil.which("open") or shutil.which("xdg-open")
        if opener:
            subprocess.run([opener, str(path)], check=False)
        else:
            print("  no supported browser opener found; use the file URL above", file=sys.stderr)
Confidence
77% confidence
Finding
The script automatically launches the generated dashboard with whatever executable is resolved as `open` or `xdg-open` on the user's PATH. While arguments are passed safely as a list, auto-executing an external opener can trigger unintended application launches and is influenced by the local environment, making this more sensitive in an agent skill that may run without an interactive confirmation step.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill describes and invokes a Python scanner that uses shell-accessible tooling (`lsof`), performs local network probing, reads process/listener metadata, and writes a dashboard file, but it does not declare corresponding permissions. This creates a permission-transparency gap: users or policy engines may underestimate the skill's access to local system state and locally exposed web services, which can reveal sensitive process names, ports, page titles, and authenticated interface presence.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The dashboard is written to `~/.local/state/local-uis/dashboard.html`, which persists a browsable inventory of local HTTP services, ports, titles, and owning processes. That metadata can expose sensitive development tools, admin panels, notebook names, or internal app identities to other local users, backup systems, or later unintended disclosure.

Missing User Warnings

Low
Confidence
95% confidence
Finding
The script opens the generated dashboard automatically unless `--no-open` is supplied, without explicit runtime confirmation. In an agent context, this can surprise users, leak activity into the desktop session, or cause navigation to locally hosted pages that may have side effects on load.

VirusTotal

VirusTotal findings are pending for this skill version.

View on VirusTotal

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test_scan_uis.py:10