Back to skill

Security audit

Mapbox Search Integration

Security checks for vulnerabilities and agentic risk

Overview

This Mapbox search skill is coherent, but needs Review because several production-style examples teach unsafe web and server patterns.

Review before installing or using this skill as implementation guidance. Prefer the SDK-based patterns, but do not copy the unsafe innerHTML/setHTML examples into a real app; add safe DOM rendering, authentication or rate limits for backend proxy routes, per-user search session isolation, token restrictions, and clear user notice for search/location data sent to Mapbox.

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

T09 · Insecure Skill Coding Practices

Error
Location
references/web-search-js.md:119
Finding
DOM-based cross-site scripting through unescaped search result fields<![CDATA[ ## Vulnerability Details **File Location**: `references/web-search-js.md`, lines 119–123, 181–190, and 347–357 **Vulnerability Type**: DOM-based cross-site scripting **Risk Level**: High ### Vulnerable Code ```javascript .setHTML( `<h3>${result.properties.name}</h3> <p>${result.properties.full_address || ''}</p>` ) ``` ```javascript resultsContainer.innerHTML = response.suggestions .map( (suggestion) => ` <div class="result-item" data-id="${suggestion.mapbox_id}"> <strong>${suggestion.name}</strong> <div>${suggestion.place_formatted}</div> </div> ` ) .join(''); ``` ```javascript resultsContainer.innerHTML = results .map( (result) => ` <div class="result" data-id="${result.mapbox_id}"> <strong>${result.name}</strong> <p>${result.place_formatted || ''}</p> </div> ` ) .join(''); ``` ### Technical Analysis The examples interpolate Mapbox response fields directly into HTML strings and pass the resulting strings to `setHTML()` or `innerHTML`. These APIs parse their input as active HTML rather than plain text. The affected values include result names, formatted addresses, and identifiers. They originate outside the application’s trust boundary. If a malicious or compromised upstream record contains HTML event handlers, dangerous elements, or malformed attribute content, the browser can interpret that content in the application’s origin. The use of an unquoted `data-id` attribute increases the attack surface because specially formed identifier content could break out of the attribute even when it does not contain a complete HTML element. ### Attack Path 1. An attacker causes malicious markup to appear in a place name, formatted address, or other search-result field available through the upstream search service. 2. A victim searches for a term that returns the attacker-controlled record. 3. The application interpolates the returned value into an HTML template without contextual escaping. 4. The app ...[truncated 856 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not construct result interfaces using `innerHTML` with external values. - Create DOM elements explicitly and assign external values through `textContent`: ```javascript const item = document.createElement('div'); item.className = 'result-item'; item.dataset.id = String(suggestion.mapbox_id); const name = document.createElement('strong'); name.textContent = suggestion.name || ''; const address = document.createElement('div'); address.textContent = suggestion.place_formatted || ''; item.append(name, address); resultsContainer.appendChild(item); ``` - For popups, use a DOM-node API such as `setDOMContent()` if supported: ```javascript const popupContent = document.createElement('div'); const heading = document.createElement('h3'); const address = document.createElement('p'); heading.textContent = result.properties.name || ''; address.textContent = result.properties.full_address || ''; popupContent.append(heading, address); new mapboxgl.Popup() .setLngLat(result.geometry.coordinates) .setDOMContent(popupContent) .addTo(map); ``` - If HTML rendering is unavoidable, sanitize every untrusted value with a maintained allowlist-based sanitizer before insertion. - Quote all generated attributes and validate identifiers against an expected character set. - Deploy a restrictive Content Security Policy as defense in depth. CSP must not replace output encoding or safe DOM construction. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/nodejs-search.md:24
Finding
Unauthenticated backend search proxy permits Mapbox quota and cost abuse<![CDATA[ ## Vulnerability Details **File Location**: `references/nodejs-search.md`, lines 24–48 **Vulnerability Type**: Unprotected server-side API proxy **Risk Level**: Medium ### Vulnerable Code ```javascript // Express.js API endpoint example app.get('/api/search', async (req, res) => { const { query, proximity, country } = req.query; try { // Get suggestions (Search JS Core handles session management) const response = await search.suggest(query, { proximity: proximity ? proximity.split(',').map(Number) : undefined, country: country, limit: 10 }); res.json(response.suggestions); } catch (error) { res.status(500).json({ error: error.message }); } }); // Retrieve full details for a selected result app.get('/api/search/:id', async (req, res) => { try { const result = await search.retrieve(req.params.id); res.json(result.features[0]); } catch (error) { res.status(500).json({ error: error.message }); } }); ``` ### Technical Analysis The example exposes server-side endpoints backed by `process.env.MAPBOX_TOKEN`, but it does not demonstrate authentication, authorization, rate limiting, request-size limits, or strict parameter validation. Any client able to reach these routes can cause the server to make paid or quota-limited Mapbox requests. The `query`, `country`, `proximity`, and result identifier values are accepted directly from request parameters. In particular: - Search query length and type are not constrained. - `proximity` is converted with `Number` without checking coordinate count, finiteness, or valid longitude and latitude ranges. - `country` is not restricted to an application-approved allowlist. - The retrieval identifier is not validated. - No per-user or per-address request budget is enforced. Although the server token is not returned directly, its authority can be exercised indirectly through the public proxy. ### Attack Path 1. An attacker discovers or is given access to ...[truncated 1172 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require authentication for search routes when they are not intentionally public. - Apply rate limits per account, API client, and source address. Include both short burst limits and longer usage budgets. - Enforce strict schemas: - Require `query` to be a string within a small, documented length range. - Require exactly two finite proximity coordinates. - Enforce longitude from `-180` through `180` and latitude from `-90` through `90`. - Restrict country and result types to application-approved allowlists. - Validate result identifiers against the provider’s documented format. - Reject unknown parameters rather than forwarding arbitrary client options. - Add request deadlines and cancellation. - Configure provider-side token scopes to the minimum required permissions. - Add usage monitoring, anomaly detection, and budget alerts. - Return generic client-facing errors while recording detailed provider errors only in protected server logs. - If anonymous public search is required, consider a separately scoped and restricted token and enforce aggressive gateway-level abuse controls. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/nodejs-search.md:18
Finding
Global server-side SearchSession shares mutable search state across users<![CDATA[ ## Vulnerability Details **File Location**: `references/nodejs-search.md`, lines 18–48 **Vulnerability Type**: Cross-user session-state isolation failure **Risk Level**: Medium ### Vulnerable Code ```javascript // Initialize search session (handles session tokens automatically) const search = new SearchSession({ accessToken: process.env.MAPBOX_TOKEN }); // Express.js API endpoint example app.get('/api/search', async (req, res) => { const { query, proximity, country } = req.query; try { // Get suggestions (Search JS Core handles session management) const response = await search.suggest(query, { proximity: proximity ? proximity.split(',').map(Number) : undefined, country: country, limit: 10 }); res.json(response.suggestions); } catch (error) { res.status(500).json({ error: error.message }); } }); // Retrieve full details for a selected result app.get('/api/search/:id', async (req, res) => { try { const result = await search.retrieve(req.params.id); res.json(result.features[0]); } catch (error) { res.status(500).json({ error: error.message }); } }); ``` ### Technical Analysis `SearchSession` is instantiated once at module scope and then reused by every incoming request. Search sessions contain state used to associate suggestion requests with a subsequent retrieval operation. A single global instance therefore merges unrelated users and concurrent search flows into one mutable session lifecycle. In a multi-user Express process, requests can interleave. One user’s retrieval can terminate or rotate session state while another user is still issuing suggestions. This violates session isolation and can produce incorrect billing attribution, inconsistent retrieval behavior, or cross-request interference. The problem is not that the Mapbox access token exists at module scope; a server credential may legitimately be shared. The vulnerable design is sharing a stateful search-session object ...[truncated 1283 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not use one module-global `SearchSession` for unrelated users. - Maintain one session per logical search flow: 1. Generate an opaque application session identifier. 2. Create a dedicated `SearchSession` for that identifier. 3. Store it in a bounded, short-lived server-side session store. 4. Bind the identifier to the authenticated user or a signed client session. 5. Delete the entry after retrieval, abandonment timeout, or logout. - Prevent clients from selecting another user’s session identifier. - Set strict expiration and maximum-entry limits to prevent the session store from becoming a denial-of-service target. - If the SDK supports explicitly supplied session tokens, associate each token with exactly one client search flow and rotate it after retrieval. - Add concurrency tests with interleaved users to confirm that one user’s retrieval cannot modify another user’s search lifecycle. ]]>
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
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (18)

Hidden Instructions

High
Category
Prompt Injection
Content
**Problem:**

```html
<!-- Tiny touch targets -->
<div style="height: 20px; padding: 2px;">Search result</div>
```
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Unvalidated Output Injection

High
Category
Output Handling
Content
});

  // Render custom results UI
  resultsContainer.innerHTML = response.suggestions
    .map(
      (suggestion) => `
    <div class="result-item" data-id="${suggestion.mapbox_id}">
Confidence
98% confidence
Finding
The example builds HTML with untrusted fields such as `suggestion.name` and `suggestion.place_formatted` and injects it via `innerHTML`. If upstream data contains malicious markup, this can lead to DOM-based XSS in applications that copy this pattern, allowing script execution in the user's browser and theft of tokens or session data.

Unvalidated Output Injection

High
Category
Output Handling
Content
});

function displayResults(results) {
  resultsContainer.innerHTML = results
    .map(
      (result) => `
    <div class="result" data-id="${result.mapbox_id}">
Confidence
98% confidence
Finding
This example again concatenates untrusted result fields into an HTML string and assigns it to `resultsContainer.innerHTML`. Any malicious or unexpectedly crafted content returned by the third-party service could trigger XSS, which is especially dangerous in a browser integration because it runs with the application's origin and can access user data and API tokens.

Vague Triggers

Medium
Confidence
86% confidence
Finding
The skill advertises invocation on phrases like "I need to add search to my map" and "How do I implement location search?" which are broad enough to match many general mapping discussions. The file does not provide exclusion conditions or clearer scope boundaries to distinguish when this skill should activate instead of other mapping or UI skills.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
override fun onQueryTextChange(newText: String): Boolean {
                if (newText.length >= 2) {
                    // Search SDK handles debouncing automatically
                    performSearch(newText)
                }
                return true
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

External Transmission

Medium
Category
Data Exfiltration
Content
<!DOCTYPE html>
<html>
  <head>
    <script src="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.js"></script>
    <link href="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.css" rel="stylesheet" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet" />
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
<!DOCTYPE html>
<html>
  <head>
    <script src="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.js"></script>
    <link href="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.css" rel="stylesheet" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet" />
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
<!DOCTYPE html>
<html>
  <head>
    <script src="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.js"></script>
    <link href="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.css" rel="stylesheet" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet" />
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
<!DOCTYPE html>
<html>
  <head>
    <script src="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.js"></script>
    <link href="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.css" rel="stylesheet" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet" />
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
<!DOCTYPE html>
<html>
  <head>
    <script src="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.js"></script>
    <link href="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.css" rel="stylesheet" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet" />
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
<!DOCTYPE html>
<html>
  <head>
    <script src="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.js"></script>
    <link href="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.css" rel="stylesheet" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet" />
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
<!DOCTYPE html>
<html>
  <head>
    <script src="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.js"></script>
    <link href="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.css" rel="stylesheet" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet" />
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The markdown recommends using `proximity` to bias results to user location, which can affect user privacy because it uses or infers location data. The document does not include any warning or disclosure to users about collecting or transmitting location-related information to Mapbox.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The direct API integration section instructs implementers to send search queries and related parameters to Mapbox over HTTP, which involves transmitting user-entered data to an external service. The markdown does not warn about this privacy-impacting behavior or advise providing user disclosure.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The example constrains search to `countries = listOf("US")`, which imposes a locale/geography restriction in the skill content. The surrounding markdown does not present this as optional, ask for user preference, or justify the US-only limitation as a region-specific requirement.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The example sets `language: 'en'`, which forces a specific language in the skill guidance without any user opt-in or explanation that the integration is intentionally English-only. This can violate language/locale policy expectations when presented as default implementation guidance.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The example uses `country: 'US'` and the surrounding guidance is English-centric, but the clearer policy issue is that the document's implementation patterns elsewhere normalize English defaults without offering a locale choice. In example-driven guidance, hard-coded locale assumptions should be avoided unless explicitly justified.

Natural-Language Policy Violations

Low
Confidence
98% confidence
Finding
The code sets `language: options.language || 'en'`, making English the implicit default for all users without opt-in. This is a natural-language locale policy concern because it embeds a forced language preference into the sample implementation.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
references/web-search-js.md:271