Back to skill

Security audit

Mapbox Data Visualization Patterns

Security checks for vulnerabilities and agentic risk

Overview

This documentation-only skill is mostly coherent, but several copyable examples use unsafe map popup HTML patterns that could expose users' apps to browser-side injection.

Review this skill before installing if you may copy its popup examples. Replace Popup#setHTML patterns that include dataset values with DOM construction, textContent, setDOMContent, or a vetted sanitizer, and pin any npm package versions you adopt.

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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:76
Finding
DOM XSS Through Unsanitized Choropleth Feature Properties<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:76-96` **Vulnerability Type**: DOM-based cross-site scripting through unsafe HTML generation **Risk Level**: High ### Vulnerable Code ```javascript // Add hover effect with reusable popup const popup = new mapboxgl.Popup({ closeButton: false, closeOnClick: false }); map.on('mousemove', 'states-layer', (e) => { if (e.features.length > 0) { map.getCanvas().style.cursor = 'pointer'; const feature = e.features[0]; popup .setLngLat(e.lngLat) .setHTML( ` <h3>${feature.properties.name}</h3> <p>Population: ${feature.properties.population.toLocaleString()}</p> ` ) .addTo(map); } }); ``` ### Technical Analysis The example inserts GeoJSON feature properties into an HTML template and passes the result to `mapboxgl.Popup#setHTML()`. The `name` property is not encoded, sanitized, or constrained before the browser parses it as markup. GeoJSON can originate from remote services or user-controlled datasets. If an attacker can influence `feature.properties.name`, they can supply HTML containing executable event handlers or other active content. For example, a malicious name containing an image element with an `onerror` handler would be interpreted as HTML when the popup is displayed. ### Attack Path 1. An attacker gains control over, contributes to, or compromises the GeoJSON source used by the map. 2. The attacker places an HTML payload in the `name` property of a state feature. 3. The application loads the malicious feature. 4. A user moves the pointer over the affected feature. 5. The event handler interpolates the property into a string and calls `setHTML()`. 6. The browser parses the attacker-controlled markup in the application's origin and may execute its script-capable content. ### Impact Assessment Successful exploitation permits arbitrary client-side script execution in the security context of an application implementing th ...[truncated 340 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not pass untrusted feature properties to `setHTML()`. - Construct popup content using DOM nodes and assign dynamic values through `textContent`. - Pass the resulting node to `Popup#setDOMContent()` where supported. - If formatted HTML is unavoidable, sanitize the completed markup with a maintained sanitizer such as DOMPurify under a restrictive configuration. - Validate expected property types before formatting them. - Deploy a restrictive Content Security Policy as defense in depth; do not treat it as a replacement for output encoding. Example hardened pattern: ```javascript const content = document.createElement('div'); const heading = document.createElement('h3'); const population = document.createElement('p'); heading.textContent = String(feature.properties.name ?? ''); population.textContent = `Population: ${Number(feature.properties.population).toLocaleString()}`; content.append(heading, population); popup.setLngLat(e.lngLat).setDOMContent(content).addTo(map); ``` ]]>

T09 · Insecure Skill Coding Practices

Error
Location
references/circles-lines.md:43
Finding
DOM XSS Through Unsanitized Earthquake Popup Properties<![CDATA[ ## Vulnerability Details **File Location**: `references/circles-lines.md:43-59` **Vulnerability Type**: DOM-based cross-site scripting through unsafe HTML generation **Risk Level**: High ### Vulnerable Code ```javascript // Add popup on click map.on('click', 'earthquakes', (e) => { const props = e.features[0].properties; new mapboxgl.Popup() .setLngLat(e.features[0].geometry.coordinates) .setHTML( ` <h3>Magnitude ${props.mag}</h3> <p>Depth: ${props.depth} km</p> <p>Time: ${new Date(props.time).toLocaleString()}</p> ` ) .addTo(map); }); ``` ### Technical Analysis The `mag` and `depth` properties are interpolated directly into markup passed to `setHTML()`. The documentation does not validate that these properties are finite numbers or encode them for an HTML context. Although earthquake magnitude and depth are normally numeric, GeoJSON property types are not enforced by this code. A compromised or attacker-controlled data source can supply strings containing active HTML. Clicking the corresponding feature causes the application to parse those values as markup. ### Attack Path 1. The attacker controls or modifies an earthquake feature in the GeoJSON source. 2. The attacker substitutes an HTML payload for `mag` or `depth`. 3. A victim's browser loads and renders the feature. 4. The victim clicks the affected map point. 5. The click handler interpolates the malicious value into the popup HTML. 6. The browser parses the payload in the application's origin, potentially executing attacker-controlled script. ### Impact Assessment Exploitation can provide arbitrary JavaScript execution with the victim application's browser privileges. This may allow DOM manipulation, theft of accessible session or application data, authenticated same-origin requests, phishing overlays, and capture of information entered by the victim. Server-level or operating-system privileges are not directly obtained. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Convert and validate `mag`, `depth`, and `time` according to a strict schema before display. - Require `mag` and `depth` to be finite numbers and reject unexpected strings. - Create popup elements with DOM APIs and place all dynamic values in `textContent`. - Use `setDOMContent()` rather than `setHTML()`. - If HTML rendering is required, sanitize every dynamic value or the final markup with a proven sanitizer. - Apply a restrictive Content Security Policy as defense in depth. Example validation: ```javascript const magnitude = Number(props.mag); const depth = Number(props.depth); if (!Number.isFinite(magnitude) || !Number.isFinite(depth)) { return; } ``` ]]>

T09 · Insecure Skill Coding Practices

Error
Location
references/legends-use-cases.md:62
Finding
DOM XSS in Interactive Data Inspector<![CDATA[ ## Vulnerability Details **File Location**: `references/legends-use-cases.md:62-87` **Vulnerability Type**: DOM-based cross-site scripting through arbitrary property rendering **Risk Level**: High ### Vulnerable Code ```javascript map.on('click', 'data-layer', (e) => { const feature = e.features[0]; const properties = feature.properties; // Build properties table const propsTable = Object.entries(properties) .map(([key, value]) => `<tr><td><strong>${key}:</strong></td><td>${value}</td></tr>`) .join(''); new mapboxgl.Popup() .setLngLat(e.lngLat) .setHTML( ` <div style="max-width: 300px;"> <h3>Feature Details</h3> <table style="width: 100%; font-size: 12px;"> ${propsTable} </table> </div> ` ) .addTo(map); }); ``` ### Technical Analysis This generic inspector enumerates every feature property and inserts both property names and values directly into an HTML table. Neither side is encoded or sanitized before the resulting string is passed to `setHTML()`. The generic nature of the inspector increases exposure because any property in an imported dataset becomes an HTML injection source. An attacker only needs control of a property key or value; no particular data schema is required. ### Attack Path 1. An attacker creates or modifies a feature containing malicious HTML in a property name or value. 2. The application imports the dataset and renders the feature. 3. A victim clicks the feature. 4. `Object.entries()` includes the malicious key or value in `propsTable`. 5. The completed table is passed to `setHTML()`. 6. The browser parses the injected markup and may execute script-capable content under the application's origin. ### Impact Assessment The vulnerability can lead to arbitrary browser-side code execution in the hosting application's origin. An attacker may access non-HttpOnly tokens or other client-side data, perform authenticated requests, alter map co ...[truncated 173 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Replace string-based table construction with DOM APIs. - Assign property keys and values through `textContent`. - Optionally enforce an allowlist of displayable property names rather than enumerating arbitrary dataset content. - Limit the number and size of displayed properties to reduce abuse and denial-of-service risk. - Use `setDOMContent()` for the completed container. - If rich HTML is a strict requirement, sanitize it with a maintained HTML sanitizer and prohibit scripts, event attributes, dangerous URLs, and unsafe SVG or MathML content. Example hardened row construction: ```javascript const table = document.createElement('table'); for (const [key, value] of Object.entries(properties)) { const row = document.createElement('tr'); const keyCell = document.createElement('td'); const valueCell = document.createElement('td'); const strong = document.createElement('strong'); strong.textContent = `${key}:`; valueCell.textContent = String(value ?? ''); keyCell.appendChild(strong); row.append(keyCell, valueCell); table.appendChild(row); } const content = document.createElement('div'); content.appendChild(table); new mapboxgl.Popup().setLngLat(e.lngLat).setDOMContent(content).addTo(map); ``` ]]>

T08 · Insecure Dependencies

Note
Location
references/legends-use-cases.md:91
Finding
Unpinned Third-Party npm Dependency Installation Guidance<![CDATA[ ## Vulnerability Details **File Location**: `references/legends-use-cases.md:91-104` **Vulnerability Type**: Unpinned third-party dependency and mutable supply-chain resolution **Risk Level**: Low ### Vulnerable Code ```javascript // Calculate statistical breaks for choropleth // Using classybrew library (npm install classybrew) import classybrew from 'classybrew'; function calculateJenksBreaks(values, numClasses) { const brew = new classybrew(); brew.setSeries(values); brew.setNumClasses(numClasses); brew.classify('jenks'); return brew.getBreaks(); } ``` ### Technical Analysis The documentation recommends `npm install classybrew` without specifying a reviewed version, integrity information, or lockfile workflow. This command resolves mutable registry state at installation time. Future users may therefore receive a version different from the one originally assessed. npm packages can also define installation lifecycle scripts that execute with the installing user's privileges. No malicious behavior by the named package was established during this audit; the issue is the unsafe, non-reproducible installation guidance and its exposure to future package compromise or incompatible releases. ### Attack Path 1. A developer follows the documented `npm install classybrew` instruction. 2. npm resolves the current package release and its transitive dependency graph. 3. If the package account, registry artifact, or dependency chain has been compromised, npm downloads the affected release. 4. Any permitted lifecycle script may execute during installation, or compromised library code may execute when imported by the application. 5. The malicious component operates with the permissions of the installation or application process. ### Impact Assessment Potential impact depends on a future supply-chain compromise. Installation-time code could access files and environment variables available to the developer or CI account, alter build artifacts, or af ...[truncated 182 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Replace the generic installation instruction with a reviewed exact version, for example `npm install --save-exact package@X.Y.Z`. - Commit the generated lockfile and use `npm ci` in CI and production build environments. - Review the package's ownership, release history, source repository, transitive dependencies, and lifecycle scripts. - Run package installation in a least-privileged, isolated build environment without production secrets. - Enable dependency scanning and automated alerts. - Review and test upgrades explicitly rather than accepting mutable versions automatically. - Where feasible, avoid install scripts using an organizational npm policy or `--ignore-scripts`, provided the dependency does not legitimately require them. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (9)

Hidden Instructions

High
Category
Prompt Injection
Content
**Color Scale Types:**

<!-- prettier-ignore -->
```javascript
// Linear (continuous)
['interpolate', ['linear'], ['get', 'value'], 0, '#fff', 100, '#000']
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
**Color Scale Types:**

<!-- prettier-ignore -->
```javascript
// Linear (continuous)
['interpolate', ['linear'], ['get', 'value'], 0, '#fff', 100, '#000']
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
// Poll for updates every 5 seconds
  setInterval(async () => {
    const response = await fetch('https://api.example.com/live-data');
    const data = await response.json();

    // Update source
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The popup example builds HTML with unescaped feature property keys and values and passes it to setHTML(), so any untrusted GeoJSON or vector-tile attribute containing HTML or script-like payloads can be rendered in the page context. In a mapping application, data properties are often externally sourced or user-controlled, which makes this a practical client-side injection/XSS risk rather than a purely theoretical issue.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The example encourages rendering arbitrary feature properties as raw HTML without any warning that map data may be untrusted. Even if no exploit is immediately present in the snippet itself, this pattern normalizes unsafe handling of dataset content and increases the chance that downstream users will expose popups to attacker-controlled values.

Missing User Warnings

Low
Confidence
81% confidence
Finding
This markdown file includes a real-time update example that fetches data from an API endpoint on an interval, which is a network operation covered by the missing-warning rule for markdown files. The surrounding documentation presents the pattern as a generic recipe but does not warn that it will repeatedly contact a remote service and may transmit usage context or consume network resources.

Description-Behavior Mismatch

Low
Confidence
85% confidence
Finding
The manifest describes patterns for visualizing data on maps, focusing on layer types, styling, animation, and performance. The 'Real-Time Data Updates' section includes concrete HTTP polling and WebSocket connectivity examples, which introduce external data acquisition behavior rather than just visualization patterns.

Context-Inappropriate Capability

Low
Confidence
74% confidence
Finding
Polling a remote API with fetch and opening a WebSocket connection are capabilities beyond purely demonstrating map rendering and animation techniques. While related to real-time visualization, the manifest does not explicitly state that the skill covers remote data access or live-stream integration.

Missing User Warnings

Low
Confidence
82% confidence
Finding
This markdown file includes example code that polls an HTTPS endpoint and opens a WebSocket connection to remote services, which are network operations covered by the missing-warning rule for markdown files. The surrounding documentation presents these as patterns but does not mention privacy, external connectivity, or data-sharing implications.

Static analysis

No suspicious patterns detected.