Back to skill

Security audit

UniFi Advisor

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed read-only UniFi API helper, but users should treat its outputs and short-lived local cache as sensitive network information.

Install only if you are comfortable giving the skill read access to your UniFi cloud account. Treat returned device, client, firewall, VPN, voucher, IP, and firmware details as confidential, and be aware that site inventory metadata is cached locally for about 15 minutes without explicit permission hardening by the script.

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

Warning
Location
scripts/unifi.py:149
Finding

UniFi Network Inventory Cached Without Enforced Restrictive Permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/unifi.py:28 and scripts/unifi.py:149-163
Vulnerability Type: Plaintext storage of sensitive network metadata with permissions inherited from the process umask
Risk Level: Medium

Vulnerable Code

python
CACHE_FILE = Path.home() / ".openclaw" / "unifi-skill.json"
python
        library.append({
            "label":          label,
            "name":           name,
            "hostId":         host_id,
            "siteId":         site_id,
            "hardware":       hw.get("name", ""),
            "shortname":      hw.get("shortname", ""),
            "firmwareVersion": hw.get("firmwareVersion", ""),
            "state":          rs.get("state", ""),
            "apiIntegration": has_api,
            "hasNetwork":     has_network,
            "timezone":       rs.get("timezone", ""),
            "ip":             rs.get("ip", ""),
        })

    CACHE_FILE.write_text(json.dumps({"library": library, "updatedAt": time.time()}, indent=2))

Technical Analysis

The Skill stores discovered UniFi infrastructure metadata in plaintext at ~/.openclaw/unifi-skill.json. The cached data includes host and site identifiers, names, IP addresses, hardware models, firmware versions, controller state, timezone, and API capability information.

Path.write_text() creates or replaces the file using permissions determined by the process umask. The implementation does not explicitly ensure that the parent directory is owner-only or that the cache file has mode 0600. On systems with permissive directory or file permissions, another local user may be able to read the cached inventory.

The API key is not written to this cache. Credential transmission elsewhere in the script is limited to the fixed https://api.ui.com origin and is necessary for the declared UniFi API functionality.

Attack Path

  1. A user with a configured `UN ...[truncated 1415 chars]
Remediation
View remediation

Remediation Suggestions

  • Create ~/.openclaw with owner-only permissions (0700) before writing the cache.
  • Create the cache file explicitly with mode 0600 rather than relying on the process umask.
  • Write to a securely created temporary file in the same directory, flush and synchronize it, set its permissions, and atomically replace the destination.
  • Reject symbolic-link destinations and verify that the cache and parent directory are owned by the current user.
  • Correct permissions on existing cache files during startup.
  • Minimize cached data by excluding fields that are not needed for site resolution, particularly IP addresses and firmware details.
  • Consider disabling caching or providing an opt-out option for shared or otherwise untrusted hosts.

Example hardening approach:

python
CACHE_FILE.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
os.chmod(CACHE_FILE.parent, 0o700)

flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
if hasattr(os, "O_NOFOLLOW"):
    flags |= os.O_NOFOLLOW

fd = os.open(CACHE_FILE, flags, 0o600)
try:
    with os.fdopen(fd, "w", encoding="utf-8") as cache:
        json.dump(
            {"library": library, "updatedAt": time.time()},
            cache,
            indent=2,
        )
    os.chmod(CACHE_FILE, 0o600)
except Exception:
    try:
        os.close(fd)
    except OSError:
        pass
    raise
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

Tainted flow: 'req' from os.environ.get (line 329, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/unifi.py (reported line 54)May include surrounding context.

python
url += "?" + urllib.parse.urlencode({k: v for k, v in params.items() if v is not None})
    req = urllib.request.Request(url, headers={"X-API-Key": API_KEY, "Accept": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            body = json.loads(resp.read().decode())
            return body.get("data", body)
    except urllib.error.HTTPError as e:

Tainted flow: 'req' from os.environ.get (line 329, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/unifi.py (reported line 331)May include surrounding context.

python
url += "?" + urllib.parse.urlencode({k: v for k, v in params.items() if v is not None})
    req = urllib.request.Request(url, headers={"X-API-Key": API_KEY, "Accept": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            body = json.loads(resp.read().decode())
            return body.get("data", body)
    except urllib.error.HTTPError as e:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill performs network access, reads an environment secret, and writes a cache file, but it does not declare an explicit tool scope such as permissions or allowed-tools. That mismatch weakens reviewability and enforcement because users and hosting systems cannot clearly see or constrain the skill's operational surface, increasing the chance of unintended secret access, network querying, or filesystem writes.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Although the skill is read-only, it can retrieve highly sensitive operational data including clients, networks, firewall rules, VPNs, and device state. Omitting a clear warning about this data exposure may cause users to invoke the skill without understanding that it can reveal confidential network topology, security controls, device inventory, and user/client information in outputs.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/unifi.py (reported line 7)May include surrounding context.

python
All requests go through https://api.ui.com — no direct local connection required.
Network Integration endpoints are proxied via:
  https://api.ui.com/v1/connector/consoles/{hostId}/proxy/network/integration/v1/...

The site library is built automatically on every invocation by joining:
  GET /api/v1/hosts  → host names, hardware, connection state, apiIntegration flag

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill caches sensitive network inventory to ~/.openclaw/unifi-skill.json, including host names, hardware, firmware versions, IP addresses, state, and timezone, without setting restrictive file permissions or offering a way to disable caching. On multi-user systems or systems with weak home-directory protections, this can expose useful reconnaissance data to other local users or malware.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The documentation states that users should run the library subcommand to view the site library, and later explicitly says Run 'library' to see the current library. However, the example at L087 uses python3 {baseDir}/scripts/unifi.py library under a comment about listing sites, while the earlier 'How to call the script' section says to use list to see subcommands; this creates an intent/documentation inconsistency about what the command does and which command lists available functionality versus sites.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.