Back to skill

Security audit

Living Room Air Monitor

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its air-quality reporting purpose, but it needs Review because it handles a hub auth token insecurely and can modify the runtime environment automatically.

Review before installing. Use this only if you are comfortable storing living-room sensor history locally and sending detailed reports through configured email or WhatsApp tools. Before regular use, fix TLS validation or certificate pinning for the Dirigera hub, remove the automatic pip install, protect temporary report files, verify CONTACTS.json permissions and recipients, and add cron only deliberately.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/collect_air_data.py:46
Finding
Disabled TLS Verification Exposes the Dirigera Bearer Token<![CDATA[ ## Vulnerability Details **File Location**: `scripts/collect_air_data.py`, lines 46-49 **Vulnerability Type**: TLS certificate validation disabled for an authenticated request **Risk Level**: High ### Vulnerable Code ```python # Create SSL context that ignores certificate verification ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE ``` The resulting context is subsequently used for the request containing the bearer token: ```python req = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response: devices = json.loads(response.read().decode('utf-8')) ``` ### Technical Analysis The collector transmits a bearer token in the `Authorization` header while explicitly disabling both certificate-chain validation and hostname verification. Consequently, the client cannot establish that it is communicating with the legitimate Dirigera hub. Although the destination is a private LAN address, local network traffic is not inherently trustworthy. An attacker capable of manipulating local routing, ARP resolution, or network infrastructure can impersonate the hub using any TLS certificate. The client will accept that certificate and disclose its bearer token. The attacker can also return fabricated device data. Because accepted readings are written to the SQLite database and may later be included in reports, this affects both credential confidentiality and data integrity. ### Attack Path 1. The attacker gains access to the same local network or compromises a router, access point, or other device able to manipulate traffic. 2. The attacker redirects traffic intended for `192.168.1.100:8443`, such as through ARP spoofing or routing manipulation. 3. The attacker presents an arbitrary TLS certificate while impersonating the Dirigera API. 4. The collector accepts the certificate because certificate and hostname verification are d ...[truncated 794 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Keep certificate-chain and hostname validation enabled. - Configure the client to trust the Dirigera hub's certificate or its issuing CA explicitly. - If the device uses a self-signed certificate, pin the expected certificate or public-key fingerprint and fail closed if it changes. - Do not fall back to an unverified connection when validation fails. - Restrict the token file to the account running the collector, preferably with permissions equivalent to `0600`. - Use a token with the minimum Dirigera API permissions required to read environmental sensor data. - Rotate the existing token if it may previously have traversed an untrusted local network. - Avoid logging authorization headers or token values in future error-handling changes. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/generate_chart.py:13
Finding
Runtime Installation of an Unpinned Third-Party Dependency<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_chart.py`, lines 13-23 **Vulnerability Type**: Unattended and unpinned package installation during module import **Risk Level**: Medium ### Vulnerable Code ```python # Try to import matplotlib, install if not available try: import matplotlib matplotlib.use('Agg') # Use non-interactive backend import matplotlib.pyplot as plt import matplotlib.dates as mdates except ImportError: print("matplotlib not found. Installing...") os.system(f"{sys.executable} -m pip install matplotlib --quiet") import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.dates as mdates ``` ### Technical Analysis If `matplotlib` cannot be imported, the module automatically invokes pip. This behavior occurs as part of importing or running chart functionality and does not require separate user confirmation. The installation does not specify a version, package hashes, an approved package index, or an isolated environment. Its effective dependency graph can therefore change after the Skill has been audited. Package installation and imports may execute package-controlled code with the privileges of the account running the Skill. The use of `os.system` also unnecessarily invokes a shell. No direct attacker-controlled shell fragment is visible in this project, so a concrete command-injection vulnerability is not established; nevertheless, a shell is unnecessary for this operation. ### Attack Path 1. Chart generation or report generation is invoked in an environment where `matplotlib` is unavailable or its import raises `ImportError`. 2. The exception handler executes pip automatically. 3. Pip resolves the latest available `matplotlib` release and transitive dependencies from the configured package source. 4. If the configured index, package account, network path, or a resolved dependency is compromised, attacker-controlled package content is downlo ...[truncated 732 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove all automatic dependency installation from module import and runtime execution. - Declare `matplotlib` and its transitive dependency constraints in a reviewed dependency manifest or lock file. - Pin approved versions and use package hashes where supported. - Install dependencies explicitly during a documented setup phase in an isolated virtual environment. - Configure pip to use an approved HTTPS package index. - Fail with a clear dependency error when `matplotlib` is unavailable rather than modifying the environment automatically. - If a subprocess must be used during an explicit setup operation, invoke it without a shell: ```python subprocess.run( [sys.executable, "-m", "pip", "install", "--require-hashes", "-r", "requirements.txt"], check=True, ) ``` - Review and update pinned dependencies through a controlled maintenance process. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/send_report.py:126
Finding
Air-Quality Report Written to a Predictable Shared Temporary File<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_report.py`, lines 126-135 **Vulnerability Type**: Unsafe predictable temporary file containing report data **Risk Level**: Medium ### Vulnerable Code ```python # Create temporary file for email body temp_body = "/tmp/air_quality_email_body.txt" # Append chart location if available email_body = body if attachment_path and os.path.exists(attachment_path): email_body += f"\n\n📊 Chart available at: {attachment_path}" with open(temp_body, 'w') as f: f.write(email_body) ``` ### Technical Analysis The email report is written to a constant filename in the shared `/tmp` directory. The code does not securely reserve a unique file, prevent symbolic-link traversal, set restrictive file permissions explicitly, or remove the file after use. The report contains timestamped living-room temperature, humidity, PM2.5, and CO2 history. It can therefore reveal occupancy-related patterns and environmental conditions. A predictable path also creates a race between concurrent report operations. Another local process can attempt to pre-create or monitor the path. Because normal `open(..., 'w')` behavior follows symbolic links, a pre-existing symbolic link can redirect the write to another file writable by the Skill account. Actual cross-user exploitability depends on operating-system temporary-directory protections, ownership, and process umask. ### Attack Path 1. A local attacker learns the fixed filename `/tmp/air_quality_email_body.txt` from the public Skill code. 2. Before report generation, the attacker creates or monitors an object at that path. 3. When an email report is generated, the Skill opens the predictable path with write-and-truncate behavior. 4. If the attacker can establish a usable symbolic link, the Skill follows it and writes the report to the linked target; alternatively, the attacker waits for the report file to appear and reads it if resulting permissions allow. 5. The file rema ...[truncated 697 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Use Python's `tempfile` module to create a unique temporary file securely. - Restrict the file to the current user, with permissions equivalent to `0600`. - Delete the file in a `finally` block regardless of whether transmission succeeds. - Prefer passing the report through standard input if the `gog` client supports it, eliminating the temporary file entirely. - Avoid reusing one temporary path across concurrent invocations. - Store temporary report data in a private runtime directory rather than a globally shared directory when practical. Example hardening pattern: ```python import os import tempfile temp_path = None try: with tempfile.NamedTemporaryFile( mode="w", prefix="air_quality_", suffix=".txt", delete=False, encoding="utf-8", ) as temp_file: temp_path = temp_file.name os.chmod(temp_path, 0o600) temp_file.write(email_body) result = subprocess.run( [ "gog", "gmail", "send", "--to", EMAIL, "--subject", subject, "--body-file", temp_path, ], capture_output=True, text=True, ) finally: if temp_path: try: os.unlink(temp_path) except FileNotFoundError: pass ``` ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill's stated purpose focuses on air-quality monitoring, but it also accesses shared contact data and sends outbound communications through external CLIs. That extra behavior increases the data-sharing and command-execution surface, and because it is not declared in permissions, users and reviewers may not realize the skill can exfiltrate report contents or contact information.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
import matplotlib.dates as mdates
except ImportError:
    print("matplotlib not found. Installing...")
    os.system(f"{sys.executable} -m pip install matplotlib --quiet")
    import matplotlib
    matplotlib.use('Agg')
    import matplotlib.pyplot as plt
Confidence
93% confidence
Finding
The script invokes pip through os.system using a shell command, which introduces unnecessary command-execution risk and performs code-fetching/execution at runtime. Even though sys.executable is usually trusted, runtime package installation from the network expands the attack surface and can execute unreviewed package install hooks in the current environment.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill advertises and documents capabilities that read/write local files, access the network, and invoke shell commands, but it does not declare any explicit tool scope or permissions boundary. This weakens reviewability and enforcement, making it easier for a broadly capable skill to access sensitive local resources or execute external commands without clear user/admin approval.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The documentation encourages sending reports by email or WhatsApp but does not clearly warn that sensor history and potentially presence-related household data will be transmitted to third-party services. Users may unknowingly share private environmental and occupancy-adjacent data outside the local system.

Session Persistence

Medium
Category
Rogue Agent
Content
### CONTACTS.json

Create `~/.openclaw/workspace/CONTACTS.json` with your contact information:

```json
{
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The manifest says the skill collects temperature, humidity, PM2.5, and CO2 readings every hour, but this file's top-level documentation explicitly says to run via cron every 4 hours. That is a direct contradiction between documented intent and the declared skill behavior, not merely an omitted detail.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
A charting utility should not silently modify the host environment by downloading and installing packages during normal execution. This behavior is outside the core air-quality reporting purpose, creates supply-chain exposure, and may execute package installation code without operator awareness.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script automatically performs a package installation without explicit warning or confirmation, causing unexpected environment changes and potential network access. In a skill that only claims to generate charts, silently pulling code from package sources is risky and undermines user control and auditability.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The script reads recipient contact data from a shared workspace file outside the skill's own data boundary, creating a cross-domain trust issue. If another skill, process, or attacker can modify that file, reports could be silently redirected to an unauthorized email address or WhatsApp number, causing data exfiltration.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The script delegates data transmission to external CLI tools, expanding the trust boundary to whatever binaries named 'gog' and 'wacli' resolve at runtime. If those tools are replaced, trojaned, or PATH-hijacked, they can exfiltrate report contents, contacts, or other local data under the script's privileges.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The email-sending path transmits full report contents externally without an in-code warning or consent gate. In the context of a home-monitoring skill, this can disclose environmental and behavioral information to unintended recipients if configuration is wrong or users are unaware the action is external.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"--body-file", temp_body
        ]
        
        result = subprocess.run(cmd, capture_output=True, text=True)
        
        if result.returncode == 0:
            print(f"✅ Email sent successfully to {EMAIL}")
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
"--body-file", temp_body
        ]
        
        result = subprocess.run(cmd, capture_output=True, text=True)
        
        if result.returncode == 0:
            print(f"✅ Email sent successfully to {EMAIL}")
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
"--body-file", temp_body
        ]
        
        result = subprocess.run(cmd, capture_output=True, text=True)
        
        if result.returncode == 0:
            print(f"✅ Email sent successfully to {EMAIL}")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The code sends detailed historical air-quality readings over WhatsApp without any built-in consent prompt or warning that data is leaving the local monitoring environment. Even if the data is not highly regulated, occupancy patterns and household conditions can be inferred from timestamps and readings, making silent external transmission a meaningful privacy issue.

Missing User Warnings

Low
Confidence
84% confidence
Finding
This code reads a bearer token from a local credential file to authenticate to the hub. Although the file purpose is evident in code, there is no explicit user-facing disclosure in the script description or comments warning that credentials are accessed during execution.