Back to skill

Security audit

Entur Travel

Security checks for vulnerabilities and agentic risk

Overview

This skill is a straightforward Norway transit helper that sends trip and stop queries to Entur's public API, with some input-validation weaknesses but no hidden persistence, credential access, or destructive behavior.

Install only if you are comfortable sending Norway transit searches, stop IDs, and trip details to Entur. Prefer normal place names and small result limits; avoid using untrusted raw stop IDs or unusual mode/time strings until input validation is tightened.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/entur.py:87
Finding
GraphQL Injection Through Unescaped CLI and Geocoder Inputs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/entur.py:87-100`, `scripts/entur.py:166-181`, `scripts/entur.py:219-234`, and `scripts/entur.py:259-267` **Vulnerability Type**: GraphQL injection caused by unsafe string interpolation **Risk Level**: Medium ### Vulnerable Code ```python # scripts/entur.py:87-100 if args.modes: mode_list = [m.strip() for m in args.modes.split(",")] transport_modes = ", ".join( f'{{transportMode: {m}}}' for m in mode_list ) modes_filter = f"modes: {{transportModes: [{transport_modes}]}}" time_clause = "" if args.time: time_clause = f'dateTime: "{args.time}"' arrive_clause = "" if args.arrive: arrive_clause = "arriveBy: true" ``` ```python # scripts/entur.py:166-181 query = f""" {{ stopPlace(id: "{args.stop_id}") {{ id name estimatedCalls(timeRange: 3600, numberOfDepartures: {args.limit or 10}) {{ realtime aimedDepartureTime expectedDepartureTime destinationDisplay {{ frontText }} serviceJourney {{ line {{ publicCode name transportMode authority {{ name }} }} }} ``` ```python # scripts/entur.py:219-234 query = f""" {{ stopPlace(id: "{args.stop_id}") {{ id name transportMode description latitude longitude quays {{ id name publicCode description }} }} }} """ ``` ```python # scripts/entur.py:259-267 def _place_arg(place: dict) -> str: if "id" in place: s = f'place: "{place["id"]}"' else: c = place["coords"] s = f'coordinates: {{latitude: {c["lat"]}, longitude: {c["lon"]}}}' if "name" in place: s += f', name: "{place["name"]}"' return s ``` ### Technical Analysis The script constructs GraphQL documents by directly interpolating values originating from command-line arguments or geocoder responses. The affected values include: - `--time` - `--modes` - Stop IDs used by the ` ...[truncated 1999 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define static GraphQL documents and pass every dynamic value through GraphQL variables. This should include stop IDs, timestamps, place names, coordinates, mode filters, result counts, and departure limits. 2. Validate `--modes` against an explicit allowlist: - `bus` - `rail` - `tram` - `metro` - `water` - `air` - `coach` 3. Validate direct stop and place IDs with a strict format appropriate for Entur identifiers. Reject unexpected quotes, whitespace, braces, comments, or control characters. 4. Parse `--time` with a strict ISO-8601 parser and transmit the normalized value through a GraphQL variable. 5. Treat geocoder output as untrusted remote data. Place names and IDs returned by the service must also be passed through GraphQL variables rather than inserted into query source. 6. Check GraphQL `errors` in API responses and return a controlled error rather than silently treating malformed or rejected operations as empty results. 7. Add tests containing quotes, braces, GraphQL comments, Unicode control characters, and invalid mode names to verify that input cannot modify query structure. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/entur.py:96
Finding
Unbounded API Result Parameters Permit Resource Abuse<![CDATA[ ## Vulnerability Details **File Location**: `scripts/entur.py:96`, `scripts/entur.py:169`, `scripts/entur.py:282`, and `scripts/entur.py:285` **Vulnerability Type**: Missing bounds validation for externally submitted result counts **Risk Level**: Low ### Vulnerable Code ```python # scripts/entur.py:96 numTripPatterns: {args.results or 3} ``` ```python # scripts/entur.py:169 estimatedCalls(timeRange: 3600, numberOfDepartures: {args.limit or 10}) {{ ``` ```python # scripts/entur.py:282 t.add_argument("--results", type=int, default=3) ``` ```python # scripts/entur.py:285 d.add_argument("--limit", type=int, default=10) ``` ### Technical Analysis The `--results` and departure `--limit` options are parsed as integers, but no minimum or maximum values are enforced. The values are inserted into requests sent to the Entur journey-planner API. Integer parsing prevents direct string-based GraphQL injection through these two parameters, but it does not prevent extremely large or negative values. A large positive value can request excessive numbers of trip patterns or departures, potentially increasing server-side computation, response size, network use, memory consumption, and JSON-processing time. The Entur service may independently enforce limits, but the client should not depend exclusively on remote validation to prevent abusive or accidental requests. ### Attack Path 1. An attacker or untrusted caller invokes the Skill with an unusually large `--results` or `--limit` value. 2. `argparse` accepts the value because it is a valid integer. 3. The script inserts it into the GraphQL request without clamping or rejecting it. 4. The Entur API attempts to process the request or rejects it after consuming request-handling resources. 5. Repeated requests can degrade the Skill's responsiveness, consume bandwidth, or trigger API rate limiting. ### Impact Assessment This issue can cause excessive remote API work and local resource consumption while receiving a ...[truncated 299 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce conservative minimum and maximum values when parsing arguments. For example, use a custom bounded-integer parser or validate immediately after parsing. 2. Reject zero and negative values rather than relying on expressions such as `args.results or 3`, which only substitutes the default for zero. 3. Select limits according to Entur's published API constraints. If no limits are documented, use small application-level caps appropriate for interactive use. 4. Pass numeric values through GraphQL variables instead of query interpolation for consistency and type enforcement. 5. Add response-size safeguards where practical and handle HTTP 429, timeout, and oversized-response failures with controlled JSON errors. 6. Add automated tests covering zero, negative, maximum, and above-maximum values. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (5)

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill documents and invokes a Python script that performs network access to the Entur API, but the manifest does not declare any explicit tool scope such as allowed network permissions. This creates a least-privilege and governance gap: reviewers and runtime policy systems cannot easily verify or constrain the skill's external communication behavior, which could enable unintended outbound requests if the script is modified or misused.

External Transmission

Medium
Category
Data Exfiltration
Content
import urllib.parse
from datetime import datetime, timezone

GEOCODER_URL = "https://api.entur.io/geocoder/v1/autocomplete"
JOURNEY_URL = "https://api.entur.io/journey-planner/v3/graphql"
CLIENT_NAME = "openclaw-entur-travel"
HEADERS = {
Confidence
60% 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
import urllib.parse
from datetime import datetime, timezone

GEOCODER_URL = "https://api.entur.io/geocoder/v1/autocomplete"
JOURNEY_URL = "https://api.entur.io/journey-planner/v3/graphql"
CLIENT_NAME = "openclaw-entur-travel"
HEADERS = {
Confidence
60% 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

Low
Confidence
89% confidence
Finding
The script sends user-supplied search text, origin/destination inputs, and stop identifiers to Entur's remote geocoding and journey-planner APIs via HTTP requests. While networking is core to the tool's purpose, the file does not include any explicit notice that user-entered travel/location data will be transmitted to an external service.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The geocoding request hard-codes `lang` to `en`, which enforces a specific language/locale regardless of user preference. The file does not provide a language selection option or explain why English-only responses are required.