Back to skill

Security audit

Contact Map Bm

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it can expose Odoo contact addresses and generated contact details to third-party map/geocoding services at significant scale.

Install only if you are comfortable giving the skill read access to Odoo contacts and sending contact address data to external geocoding/map services. Prefer a read-only Odoo API key, avoid storing credentials in a skill-local .env for shared or backed-up workspaces, and consider using stored coordinates, an approved internal geocoder, bundled Leaflet assets, or SRI before using it with sensitive contacts.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (2)

other

Warning
Location
scripts/generate_map.py:72
Finding
Contact addresses are disclosed to an external geocoding service<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_map.py:45-51, 72-109` **Vulnerability Type**: Third-Party Personal Data Disclosure **Risk Level**: Medium ### Vulnerable Code ```python def geocode_address(q, headers): params = {'q': q, 'format': 'json', 'limit': 1, 'countrycodes': 'de'} r = requests.get('https://nominatim.openstreetmap.org/search', params=params, headers=headers, timeout=15) if r.status_code == 200: data = r.json() if data: return float(data[0]['lat']), float(data[0]['lon']) return None, None ``` ```python fields = ['id', 'name', 'street', 'street2', 'zip', 'city', 'country_id', 'email', 'phone'] partners = models.execute_kw(db, uid, secret, 'res.partner', 'search_read', [domain], {'fields': fields, 'limit': 10000}) headers = {'User-Agent': 'OpenClaw/contact-map-bm/1.0'} entries = [] for pidx, p in enumerate(partners, 1): parts = [] if p.get('street'): parts.append(p.get('street')) if p.get('street2'): parts.append(p.get('street2')) if p.get('zip'): parts.append(p.get('zip')) if p.get('city'): parts.append(p.get('city')) addr = ', '.join([x for x in parts if x]) # attempt to find coordinate-like custom fields lat = p.get('x_partner_lat') or p.get('x_lat') or p.get('latitude') or p.get('lat') lon = p.get('x_partner_lng') or p.get('x_lng') or p.get('longitude') or p.get('lng') if lat and lon: try: entries.append({'id': p['id'], 'name': p.get('name'), 'lat': float(lat), 'lon': float(lon), 'addr': addr, 'email': p.get('email'), 'phone': p.get('phone'), 'city': p.get('city')}) continue except Exception: pass if addr: q = addr + ', Germany' latv, lonv = geocode_address(q, headers) if latv and lonv: entries.append({'id': p['id'], 'name': p.get('name'), 'lat': latv, 'lon': lonv, 'addr': addr, 'email': p.get('email'), 'phone': p.get('phone'), 'city': p.g ...[truncated 2370 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Query available Odoo model fields before retrieving contacts and explicitly include supported coordinate fields in `search_read`. 2. Prefer stored coordinates and geocode only contacts that lack valid latitude and longitude values. 3. Require explicit operator consent before transmitting contact addresses to an external geocoder. 4. Clearly identify the destination service, transferred fields, retention considerations, and expected volume before processing begins. 5. Provide an option to use a locally hosted or organization-approved geocoding endpoint. 6. Apply data minimization where exact street-level precision is unnecessary, such as geocoding only postal code and city. 7. Add an option to disable external geocoding entirely and omit records without stored coordinates. 8. Implement a persistent local cache so the same address is not repeatedly disclosed during subsequent runs. 9. Ensure the processing and selected provider comply with applicable contractual, privacy, and data-protection requirements. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/generate_map.py:119
Finding
Generated map executes CDN-hosted JavaScript without integrity verification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_map.py:119-122` **Vulnerability Type**: Unverified Remote Browser Dependency **Risk Level**: Medium ### Vulnerable Code ```python html_parts.append('<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>') html_parts.append('<style>html,body,#map{height:100%;margin:0;padding:0}</style>') html_parts.append('</head><body><div id="map"></div>') html_parts.append('<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>') ``` ### Technical Analysis The generated HTML document imports and executes Leaflet JavaScript directly from `unpkg.com`. Although the URL specifies version `1.9.4`, the script is not protected with a Subresource Integrity hash and is not bundled as a locally reviewed artifact. The resulting page contains contact names, addresses, email addresses, phone numbers, coordinates, and links to Odoo records. Any JavaScript executing in that page can inspect this content and issue outbound network requests. Therefore, the CDN-hosted dependency is inside the trust boundary for sensitive contact information. If the CDN, its delivery infrastructure, or the remotely served package artifact is compromised, the response can contain modified JavaScript. The victim's browser will execute that response when the generated map is opened. HTTPS does not mitigate compromise of the trusted remote source itself. ### Attack Path 1. An attacker compromises the CDN, its delivery path, or the remote package artifact used by the generated page. 2. The attacker causes the Leaflet URL to return modified JavaScript. 3. An operator generates and opens `odoo_contacts_germany_map.html` while connected to the network. 4. The browser downloads the modified script from `unpkg.com`. 5. Because no integrity hash is present, the browser accepts and executes the modified content. 6. The malicious script reads contact details and Odoo record URLs embedded in the page. 7 ...[truncated 871 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bundle a reviewed Leaflet JavaScript and CSS release with the skill and reference those files locally. 2. If CDN delivery must be retained, add verified Subresource Integrity values to both JavaScript and CSS resources. 3. Add `crossorigin="anonymous"` when using Subresource Integrity with cross-origin assets. 4. Pin dependencies to reviewed immutable artifacts and establish a controlled process for dependency updates. 5. Add a restrictive Content Security Policy that permits scripts only from explicitly required sources and limits outbound connections. 6. Avoid permitting arbitrary inline scripts where practical; move map-generation logic into a locally bundled script and authorize it using a hash or nonce. 7. Review all other external resources, including map tiles, and document the metadata they receive when the map is opened. 8. Consider generating a self-contained or locally served map for environments where contact information is sensitive. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

Credential Access

High
Category
Privilege Escalation
Content
- The skill declares trigger phrases so OpenClaw can surface it when you ask to see contacts on a map. If you want this to be the *default* handler for that UI action, I can enable the builtin mapping in OpenClaw config (requires confirmation).

Security
- Do not commit ODOO passwords or API keys. The script reads the environment first and falls back to a local .env in the skill folder if present.

Files
- scripts/generate_map.py — main generator script.
Confidence
89% confidence
Finding
The documented fallback to a local .env file means the skill is designed to load Odoo credentials from plaintext material in the skill folder. Even though the text warns not to commit secrets, this pattern increases the chance of credential exposure through accidental commits, local file disclosure, backups, or overly broad workspace access.

Credential Access

High
Category
Privilege Escalation
Content
Usage:
  python3 generate_map.py [--city CITY]

The script reads ODOO_* from environment or a .env file next to the script.
"""
import xmlrpc.client, os, sys, time, argparse, requests, html
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
Usage:
  python3 generate_map.py [--city CITY]

The script reads ODOO_* from environment or a .env file next to the script.
"""
import xmlrpc.client, os, sys, time, argparse, requests, html
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
Usage:
  python3 generate_map.py [--city CITY]

The script reads ODOO_* from environment or a .env file next to the script.
"""
import xmlrpc.client, os, sys, time, argparse, requests, html
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def get_config():
    # prefer process env, then .env in skill folder
    cfg = dict(os.environ)
    skill_env = os.path.join(os.path.dirname(__file__), '..', '.env')
    cfg.update({k:v for k,v in load_env(skill_env).items() if k not in cfg})
    return cfg
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Credential Access

High
Category
Privilege Escalation
Content
def get_config():
    # prefer process env, then .env in skill folder
    cfg = dict(os.environ)
    skill_env = os.path.join(os.path.dirname(__file__), '..', '.env')
    cfg.update({k:v for k,v in load_env(skill_env).items() if k not in cfg})
    return cfg
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The README proposes registering this skill as the default handler for the broad phrase "show contacts on map," which is common natural language a user might say in many contexts. Overly broad routing can cause unintended invocation of this skill, exposing contact data or triggering map generation when the user did not explicitly intend to use this particular skill.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill advertises capabilities that access environment secrets, write files, and use the network, but it does not declare an explicit permission or allowed-tools scope. That weakens reviewability and runtime containment, making it easier for a user or orchestrator to invoke a skill with broader access than expected.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill states that it geocodes contact addresses via Nominatim, but it does not clearly warn that contact location data may be transmitted to an external third-party service. Because the data concerns Odoo contacts, this can expose personal or business address information and create privacy, compliance, and confidentiality risks.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The script sends contact address data to the external Nominatim geocoding service for any record lacking coordinates, which discloses potentially sensitive customer or business location information to a third party. In this skill context, the data being mapped comes from Odoo contacts, so the privacy risk is directly tied to personal or confidential business records rather than generic public data.

Context-Inappropriate Capability

Low
Confidence
80% confidence
Finding
The skill is described as generating an interactive map of Odoo contacts, but the implementation also harvests configuration from all environment variables and a sibling .env file. While Odoo authentication is expected for this purpose, broad environment ingestion is not itself part of the user-facing map capability and introduces access to potentially unrelated secrets.

Static analysis

No suspicious patterns detected.