Back to skill

Security audit

air-train-ev

Security checks for vulnerabilities and agentic risk

Overview

The skill provides legitimate travel lookup features, but its scripts can send API credentials and travel or location queries to arbitrary configured hosts without validation.

Review before installing. Use only official HTTPS API hosts, avoid setting the *_HOST overrides unless you fully control the endpoint and credentials, and treat EV coordinates and flight/transit searches as externally transmitted data. Avoid --dump for sensitive trips unless you intend to store the raw response locally; rotate keys if errors or logs may have exposed query URLs.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/navitia.py:34
Finding

Navitia credentials can be redirected to an arbitrary or plaintext host

Content
View full analysis
dict: host = os.environ.get("NAVITIA_HOST", "https://api.navitia.io").rstrip("/") token = env("NAVITIA_TOKEN") qs = f"?{urllib.parse.urlencode(query)}" if query else "" url = f"{host}{path}{qs}" req = urllib.request.Request(url, method="GET") # Navitia accepts token as basic auth username, but the docs also show Authorization header usage. # We use Basic auth with token as username and empty password. auth = base64.b64encode(f"{token}:".encode("utf-8")).decode("ascii") req.add_header("Authorization", f"Basic {auth}") try: with urllib.request.urlopen(req, timeout=30) as resp: ``` ### Technical Analysis The Navitia token is Base64-encoded and placed in an HTTP Basic Authorization header. This encoding is required by the Basic authentication protocol and is not encryption. The encoded value is not directly printed to stdout, so the Base64 operation is not itself a covert output channel. The security issue is that `NAVITIA_HOST` is accepted without validating its scheme, hostname, port, or trust level. Consequently, the credential-bearing request may be sent to an arbitrary endpoint, including a plaintext `http://` endpoint. The HTTP client may also follow redirects, creating another opportunity for unintended credential disclosure depending on redirect handling. External network access and transmission of the token to Navitia are necessary for the declared transit-planning functionality. Allowing the token to be sent to any environment-selected destination exceeds the minimum privilege required. ### Attack Path 1. An attacker or compromised runtime configuration sets `NAVITIA_HOST` to an attacker-controlled URL, such as `h ...[truncated 937 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/flight_offers.py:66
Finding

Amadeus client credentials and access tokens can be sent to an untrusted host

Content
View full analysis
str: client_id = env("AMADEUS_CLIENT_ID") client_secret = env("AMADEUS_CLIENT_SECRET") token_url = f"{host}/v1/security/oauth2/token" j = post_form( token_url, { "grant_type": "client_credentials", "client_id": client_id, "client_secret": client_secret, }, ) token = j.get("access_token") ``` ```python def main() -> int: args = parse_args() host = os.environ.get("AMADEUS_HOST", "https://api.amadeus.com").rstrip("/") token = get_access_token(host) travel_class = (args.travel_class or "").strip().upper() travel_class_map = { "ECO": "ECONOMY", "ECONOMY": "ECONOMY", "PREMIUM_ECO": "PREMIUM_ECONOMY", "PREMIUM_ECONOMY": "PREMIUM_ECONOMY", "BUSINESS": "BUSINESS", "FIRST": "FIRST", } if travel_class not in travel_class_map: raise RuntimeError(f"Unsupported travel class: {args.travel_class} (try ECONOMY, PREMIUM_ECONOMY, BUSINESS, FIRST)") params = { "originLocationCode": args.origin, "destinationLocationCode": args.destination, "departureDate": args.departure, "adults": str(args.adults), "travelClass": travel_class_map[travel_class], "nonStop": args.non_stop, "max": str(args.max), } if args.return_date: params["returnDate"] = args.return_date if args.included_airlines.strip(): params["includedAirlineCodes"] = args.included_airlines.strip() url = f"{host}/v2/shopping/flight-offers?{urllib.parse.urlencode(params)}" j = get_json(url, headers={"Authorization": f"Bearer {token}"}) ``` ### Technical Analysis The value of `A ...[truncated 2067 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/ev_charge_points.py:106
Finding

Open Charge Map API key and precise coordinates can be redirected to an arbitrary host

Content
View full analysis
int: args = parse_args() host = os.environ.get("OPENCHARGEMAP_HOST", "https://api.openchargemap.io").rstrip("/") key = env("OPENCHARGEMAP_API_KEY") compact = "false" if args.verbose else "true" verbose = "true" if args.verbose else "false" params = { "output": "json", "latitude": f"{args.lat}", "longitude": f"{args.lon}", "distance": f"{args.km}", "distanceunit": "KM", "maxresults": str(args.max), "compact": compact, "verbose": verbose, "key": key, } if args.countrycode: params["countrycode"] = args.countrycode if args.operators: params["operatorid"] = args.operators if args.usage: params["usagetypeid"] = args.usage url = f"{host}/v3/poi/?{urllib.parse.urlencode(params)}" data = get_json(url) ``` ### Technical Analysis The script places the Open Charge Map key and user-supplied latitude and longitude in a query URL whose origin is controlled by `OPENCHARGEMAP_HOST`. The host is not checked for HTTPS or restricted to the documented service domain. Transmission of coordinates to Open Charge Map is inherent to nearby charging-station search. However, unrestricted destination selection permits both the API key and potentially sensitive location information to be sent to an attacker-controlled server. Plain HTTP configuration would also expose this information to passive network observers. ### Attack Path 1. An attacker or compromised runtime sets `OPENCHARGEMAP_HOST` to a server under attacker control. 2. The user invokes the charging-point search with latitude and longitude. 3. The script adds the API key and coordinates to the request query. 4. The request ...[truncated 603 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/ev_charge_points.py:41
Finding

Open Charge Map API key is disclosed in HTTP error output

Content
View full analysis
`. When an HTTP error occurs, `get_json` includes the complete URL in a `RuntimeError`. The top-level exception handler then prints that exception to stderr and re-raises it. As a result, the key and exact coordinates can appear in Agent output, terminal history, process diagnostics, centralized logs, monitoring services, or retained error traces. Query-string credentials may also be recorded by proxies and web servers even when the request succeeds. ### Attack Path 1. The user invokes the EV charging-point search. 2. The script constructs a URL containing the API key and coordinates. 3. The remote API, proxy, or attacker-controlled endpoint returns an HTTP error. 4. The exception message incorporates the complete URL. 5. The top-level handler writes the message to stderr and re-raises the exception. 6. ...[truncated 519 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Tainted flow: 'req' from os.environ.get (line 41, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The request destination is derived from the NAVITIA_HOST environment variable and is used directly to construct the outbound URL while also attaching the NAVITIA_TOKEN in an Authorization header. If an attacker can influence the environment, they can redirect the client to an arbitrary host and exfiltrate the API token or force requests to internal services, making this a real SSRF/credential-leak risk.

Content

Scanner excerpt · scripts/navitia.py (reported line 49)May include surrounding context.

python
req.add_header("Authorization", f"Basic {auth}")

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            raw = resp.read().decode("utf-8")
    except urllib.error.HTTPError as e:
        txt = e.read().decode("utf-8", errors="replace")

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill description says to use the skill when 'Alessandro asks' for broad categories of travel and mobility tasks, which creates a person-specific trigger with unclear boundaries. This can cause the agent to invoke external API-backed functionality too eagerly for a particular user, increasing the chance of unintended data sharing, unnecessary API calls, or inappropriate tool use outside explicit user intent.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The markdown states that output formatting is fixed to DD/MM/YY HH:MM and that EUR prices use €, which imposes a specific locale/formatting convention. Because no user opt-in or region-specific justification is provided, this is a natural-language locale policy concern.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/ev_charge_points.py (reported line 5)May include surrounding context.

python
"""Find nearby EV charge points via Open Charge Map.

Docs: https://openchargemap.org/site/develop/api
API: GET https://api.openchargemap.io/v3/poi/

Env:
- OPENCHARGEMAP_API_KEY (required)

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The --dump option writes the full JSON response to disk, which is a file write operation. While the argument name suggests output, there is no confirmation prompt, warning print, or explanatory comment/docstring near the write to disclose that potentially sensitive travel data will be persisted locally.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.