Back to skill

Security audit

Earl Display Control

Security checks for vulnerabilities and agentic risk

Overview

This skill is a coherent local TV dashboard controller, but it exposes and renders sensitive household state in ways users should review before installing.

Install only if you are comfortable with a local household dashboard storing reminders, room notes, patterns, and optional precise coordinates in earl_mind.json. Before use, bind the server to 127.0.0.1, avoid exposing port 8000 to other devices, use approximate coordinates or disable weather if privacy matters, and do not let untrusted messages or web content write into the state file until the dashboard escapes or sanitizes rendered fields. Back up earl_mind.json before using clear/reset helper methods, and verify the target process before running any force-kill command.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (2)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
SKILL.md:40
Finding
Unauthenticated Exposure of Household State on All Network Interfaces<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:40-44` **Additional Location**: `README.md:96-101` **Vulnerability Type**: Unauthenticated network exposure and insufficient interface binding **Risk Level**: Medium ### Vulnerable Code ```bash cd {baseDir}/VisuoSpatialSketchpad && python3 -m http.server 8000 ``` The corresponding setup instructions in `README.md` use the same server configuration: ```bash cd VisuoSpatialSketchpad python -m http.server 8000 ``` ### Technical Analysis Python's `http.server` binds to all available network interfaces by default when no `--bind` argument is supplied. Consequently, this command does not restrict the dashboard to `localhost`, even though the project describes the service as locally hosted. The server exposes the entire `VisuoSpatialSketchpad` directory without authentication or authorization. After setup, that directory includes `earl_mind.json`, which may contain: - Precise latitude, longitude, and timezone - Household name - Room occupancy and security status - Household reminders and activities - Behavioral observations and long-term patterns - Mood, notes, and other private household information Any host able to reach TCP port 8000 can request these files directly. The issue is particularly significant on shared, untrusted, or poorly segmented local networks. ### Attack Path 1. A user follows the documented command and starts `python3 -m http.server 8000`. 2. The server listens on all available interfaces rather than only loopback. 3. An attacker on a reachable network scans the host or otherwise discovers port 8000. 4. The attacker requests: ```text http://HOST_IP:8000/earl_mind.json ``` 5. The server returns the household state without requesting authentication. 6. The attacker can repeatedly retrieve the file to monitor changes in occupancy, reminders, and other household activity. ### Impact Assessment An attacker does not gain operating-system command execution from this ...[truncated 575 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind the server explicitly to the loopback interface: ```bash python3 -m http.server 8000 --bind 127.0.0.1 ``` 2. Update every operating-system-specific instruction and example to use the restricted binding. 3. Do not serve private state from the same unauthenticated document root as public static assets. Place the dashboard assets in a dedicated public directory. 4. If remote display access is required, replace `http.server` with an application server that provides: - Authentication and authorization - TLS - Explicit route allowlisting - Secure response headers - Access logging and rate limiting 5. Configure host firewall rules to reject inbound connections to port 8000 from non-loopback interfaces. 6. Minimize the data returned to the browser. Avoid exposing precise coordinates, room status, or long-term household observations unless those fields are required by the display. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
VisuoSpatialSketchpad/sketchpad.html:803
Finding
Stored DOM Cross-Site Scripting Through Unescaped Dashboard State<![CDATA[ ## Vulnerability Details **File Location**: `VisuoSpatialSketchpad/sketchpad.html:803-943` **Related Sources**: `VisuoSpatialSketchpad/earl_api.py:65-224`, `VisuoSpatialSketchpad/sketchpad.html:1090-1097` **Vulnerability Type**: Stored DOM cross-site scripting **Risk Level**: High ### Vulnerable Code The dashboard builds HTML from state values without escaping them: ```javascript const patternChips = patterns.length ? patterns.map(p => { const dots = Array.from({length: 5}, (_, i) => `<div class="cdot ${i < Math.round(p.confidence * 5) ? 'filled' : ''}"></div>` ).join(''); return ` <div class="pattern-chip"> <div class="pattern-text">${p.pattern}</div> <div class="pattern-meta"> <div class="confidence-dots">${dots}</div> <span class="pattern-obs">${p.observations} obs</span> </div> </div>`; }).join('') : ''; ``` Additional untrusted fields are interpolated into the main HTML template: ```javascript app.innerHTML = ` <div class="dashboard"> <div class="header"> <div class="earl-photo" title="Mood: ${mind.identity.mood}"> ${hasPhoto ? `<img src="${mind.identity.photo}" alt="Earl">` : `<div class="earl-photo-placeholder">${gnomeEmoji}</div>` } </div> <div class="header-id"> <h1>${mind.identity.name}</h1> <div class="subtitle">${mind.identity.role} · ${mind.spatial_awareness.house_name}</div> </div> ``` Household items are also rendered directly: ```javascript ${(mind.house_stuff?.items || []).map(item => ` <div class="house-item"> <div class="house-item-priority priority-${item.priority}"></div> <div class="house-item-icon">${item.icon || '📌'}</div> <div class="house-item-content"> <div class="house-item-title">${item.title}</div> <div class="house-item-detail">${item.detail}</div> <div ...[truncated 3549 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not construct the dashboard by interpolating state into `innerHTML`. Create DOM elements and assign untrusted strings through `textContent`: ```javascript const title = document.createElement('div'); title.className = 'house-item-title'; title.textContent = item.title; ``` 2. For fixed markup that must remain template-based, escape all untrusted values according to their output context. HTML text, HTML attributes, URLs, CSS values, and class names require different validation rules. 3. Validate the complete JSON schema before rendering or saving: - Require strings where strings are expected. - Enforce reasonable maximum lengths. - Restrict `priority` to `high`, `medium`, or `low`. - Require finite numbers and clamp coordinates, energy, heat, and sizes. - Restrict colors to a safe hexadecimal format. - Reject unexpected object keys and element types. 4. Restrict photo sources to approved schemes and origins. Prefer local asset identifiers rather than arbitrary URLs. Reject dangerous or unnecessary schemes. 5. If formatted HTML is an intentional feature, sanitize it using a maintained allowlist-based HTML sanitizer before insertion. Do not rely on regular expressions for HTML sanitization. 6. Add a restrictive Content Security Policy as defense in depth, for example by disallowing inline scripts, object embedding, and unapproved outbound connections. Refactor the existing inline JavaScript and styles as needed to support a nonce- or hash-based policy. 7. Limit browser network destinations with `connect-src` and image destinations with `img-src`. Allow only the local origin and the explicitly required weather endpoint. 8. Treat content originating from users, messaging systems, AI-generated text, and external integrations as untrusted even if it is written through the local Python API. 9. Add automated tests that store representative HTML and attribute-injection payloads in every rend ...[truncated 66 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • 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
Findings (26)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The description partially matches the content-update portion of the code: it can update Earl's mood, house stuff, hot takes, sketchpad, and room/spatial state by modifying a local JSON file. However, several prominent declared capabilities are not implemented at all in this code chunk, especially the operational TV/dashboard management actions (wake display, restart server, launch kiosk browser) and weather updates. Conversely, the code includes additional capabilities not mentioned in the description, such as setting Earl's photo and recording long-term patterns. Because the declared purpose emphasizes broader TV dashboard management beyond what this code actually does, this is a material description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description presents a broad TV dashboard management skill with multiple operational actions and general content-updating capabilities. The supplied code chunk instead performs one narrow file-editing task: it reorders a single hard-coded 'hot take' entry in earl_mind.json. While this is loosely related to 'hot takes,' the actual behavior is much narrower and materially different from the declared primary purpose. It neither manages the display/server/browser nor generally updates dashboard state; it only reprioritizes one specific item in stored data.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The description presents a broader TV dashboard management skill, including operational actions on the display/server/browser and general updates to several dashboard sections. This code chunk performs only narrow local data mutation in earl_mind.json. It does support part of the declared domain by updating house stuff and a hot take-like section, but it omits the major advertised behaviors and instead makes specific hard-coded edits plus an extra reset of long_term_patterns. Therefore the supplied code does not accurately represent the declared description.

Session Persistence

Medium
Category
Rogue Agent
Content
### Setup

1. Clone this repo
2. Copy the template state file to create your live state:
   ```bash
   cp VisuoSpatialSketchpad/earl_mind.template.json VisuoSpatialSketchpad/earl_mind.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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill documents capabilities to read and write persistent dashboard data and to fetch network data, but it does not declare any explicit tool scope or permissions boundary. This increases the chance that an agent executes file or network actions without least-privilege controls or clear operator review, especially because the skill encourages direct JSON mutation and external weather fetching.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill instructs use of forceful process-termination commands such as kill -9 and Stop-Process -Force without requiring validation of the target process or warning about collateral termination. In an agentic setting, this can disrupt unrelated services, cause data loss, or be misapplied if port ownership or process selection is wrong.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly instructs direct mutation and manual editing of a persisted JSON state file without emphasizing that these changes are durable and can alter live dashboard behavior. This creates risk of accidental corruption, unauthorized content changes, or integrity issues if an agent writes malformed or unintended data to the file.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The clear_house_stuff method deletes all house-stuff items and immediately persists the change via save(), but there is no confirmation prompt or user-facing disclosure at the point of deletion. Although the docstring names the action, the operation is irreversible within this API and lacks any stronger warning about bulk data removal.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The clear_sketchpad method removes all sketchpad canvas entries and immediately writes the updated state to disk. While the docstring says 'Wipe the sketchpad clean,' there is no confirmation step, user-visible log, or stronger warning that this is a destructive persisted action.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The script silently removes a hard-coded 'hot take' entry from Earl's persisted state, which does not match the declared display-control/dashboard-management purpose of the skill. In a skill expected to update or display information, undisclosed deletion of content is risky because it can tamper with user data, hide information, or serve as covert behavior inconsistent with the manifest.

Tainted flow: 'data' from pathlib.Path.read_text (line 6, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
break
if match is not None:
    takes.insert(0, takes.pop(match))
    path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding='utf-8')
    print(f"Moved '{TARGET}' to top")
else:
    print(f"Take '{TARGET}' not found")
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The page builds a third-party weather request using precise latitude, longitude, and timezone from local state and sends it to api.open-meteo.com without any user-facing disclosure or consent flow. In a home-dashboard context, that leaks household location metadata to an external service and can expose sensitive occupancy or residence information.

External Transmission

Medium
Category
Data Exfiltration
Content
const unit = loc.temperature_unit || 'fahrenheit';
  const wind = loc.wind_speed_unit || 'mph';
  const tz = loc.timezone || 'America/New_York';
  return `https://api.open-meteo.com/v1/forecast?latitude=${loc.latitude}&longitude=${loc.longitude}&current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,weather_code&temperature_unit=${unit}&windspeed_unit=${wind}&timezone=${tz}&forecast_days=4`;
}

const WMO_CODES = {
Confidence
90% confidence
Finding
This code transmits location-derived query parameters to an external domain over the network. Although the destination is a legitimate weather API and HTTPS is used, the security concern is the unnecessary exposure of household coordinates and related metadata to a third party from a domestic display-control skill.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This code overwrites the JSON file on disk via `path.write_text(...)`, which is a safety-relevant data modification. The file contains no confirmation prompt, logging/print statement, docstring, or comment disclosing that it will persistently change `earl_mind.json`.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This script writes the modified `mind` object back to `earl_mind.json`, replacing prior contents for several sections including `house_stuff` and `long_term_patterns`. There is no confirmation prompt, logging, comment, or docstring warning the user that running the script will alter persisted data.

Tainted flow: 'mind' from pathlib.Path.read_text (line 3, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
mind["long_term_patterns"] = []
mind["meta"]["last_updated"] = datetime.datetime.utcnow().isoformat()
mind["meta"]["update_count"] = mind["meta"].get("update_count", 0) + 1
mind_path.write_text(json.dumps(mind, indent=2, ensure_ascii=False), encoding='utf-8')
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'mind' from pathlib.Path.read_text (line 4, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
path = Path('earl_mind.json')
mind = json.loads(path.read_text(encoding='utf-8'))
mind['identity']['current_vibe'] = 'Text me on Telegram to add stuff.'
path.write_text(json.dumps(mind, indent=2, ensure_ascii=False), encoding='utf-8')
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'mind' from pathlib.Path.read_text (line 4, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
path = Path('earl_mind.json')
mind = json.loads(path.read_text(encoding='utf-8'))
mind['identity']['current_vibe'] = 'Text me on Telegram to add stuff.'
path.write_text(json.dumps(mind, indent=2, ensure_ascii=False), encoding='utf-8')
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'mind' from pathlib.Path.read_text (line 4, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
path = Path('earl_mind.json')
mind = json.loads(path.read_text(encoding='utf-8'))
mind['identity']['current_vibe'] = 'Text me on Telegram to add stuff.'
path.write_text(json.dumps(mind, indent=2, ensure_ascii=False), encoding='utf-8')
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script constructs a weather API request using latitude, longitude, and timezone read from a local mind file, which discloses precise location data to a third-party service. In this skill context, the data appears functionally necessary for weather retrieval, but there is no minimization, consent, or indication that household location is being transmitted externally, creating a privacy leak of sensitive home-location metadata.

External Transmission

Medium
Category
Data Exfiltration
Content
_house_name = _mind_data.get("spatial_awareness", {}).get("house_name", "the house")

URL = (
    f"https://api.open-meteo.com/v1/forecast?latitude={_lat}&longitude={_lon}"
    f"&current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m"
    f"&temperature_unit={_temp_unit}&windspeed_unit={_wind_unit}&timezone={_tz}"
)
Confidence
88% confidence
Finding
This code makes an outbound HTTPS request to api.open-meteo.com and includes precise latitude and longitude in the query string. The transmission is not inherently malicious, but in a home-dashboard skill the context increases sensitivity because it can expose the physical location of a residence to an external provider and to logs, caches, or monitoring systems that capture URLs.

Missing User Warnings

Low
Confidence
85% confidence
Finding
This markdown file tells users to populate `latitude` and `longitude` for their house, which is sensitive location data. Although the README later mentions that the live state file contains real household state, the setup instructions themselves do not warn users that entering exact coordinates exposes private location information.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The configuration sets "timezone" to "America/New_York", "temperature_unit" to "fahrenheit", and "wind_speed_unit" to "mph". This imposes a specific locale and unit convention in natural-language/config values without any indication of user opt-in or that the skill is intentionally region-specific.

Intent-Code Divergence

Low
Confidence
97% confidence
Finding
The fallback message tells users "I auto-wake by restarting the server + reloading this page," implying this page performs server restart behavior. In this file, the implemented behavior is limited to fetching local JSON and weather data on intervals; there is no code that restarts any server or triggers a wake action.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
The weather configuration defaults to `fahrenheit`, `mph`, and `America/New_York`, which bakes in a specific regional locale when user preferences are absent. This can violate language/locale policy expectations because the file does not offer user choice or clearly justify the U.S.-centric defaults.

Static analysis

No suspicious patterns detected.