Back to skill

Security audit

Aviasales Flight Search

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent flight-search integration, but it uses an API token and travel details with external services and has an unsafe shared temporary cache that should be reviewed before installation.

Review this skill before installing. It needs a Travelpayouts API token and will send flight search details to Travelpayouts/Aviasales. Prefer installing only if you are comfortable with that data flow, and consider fixing the cache to use a private per-user directory instead of /tmp/airlines_cache.json.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/search_flights.py:28
Finding

Predictable Shared Temporary Cache Enables Cache Poisoning and Symlink-Based File Overwrite

Content
View full analysis

Vulnerability Details

File Location: scripts/search_flights.py, lines 28-29 and 69-96
Vulnerability Type: Unsafe temporary-file handling
Risk Level: Medium

Vulnerable Code

python
AIRLINES_CACHE = "/tmp/airlines_cache.json"
AIRLINES_CACHE_TTL = 86400  # 24 часа
python
def load_airlines(token: str) -> dict:
    """Загружает маппинг авиакомпаний с кэшированием."""
    cache_path = Path(AIRLINES_CACHE)

    # Проверяем кэш
    if cache_path.exists():
        age = time.time() - cache_path.stat().st_mtime
        if age < AIRLINES_CACHE_TTL:
            try:
                with open(cache_path, encoding="utf-8") as f:
                    return json.load(f)
            except Exception:
                pass

    # Загружаем с API
    try:
        url = f"{BASE_URL}/data/ru/airlines.json"
        resp = requests.get(url, params={"token": token}, timeout=10)
        resp.raise_for_status()
        data = resp.json()
        # data — список объектов [{code, name, ...}]
        mapping = {}
        for item in data:
            code = item.get("code") or item.get("iata")
            name = item.get("name") or item.get("name_translations", {}).get("ru", "")
            if code and name:
                mapping[code] = name
        # Сохраняем кэш
        with open(cache_path, "w", encoding="utf-8") as f:
            json.dump(mapping, f, ensure_ascii=False)
        return mapping
    except Exception:
        # Возвращаем встроенный маппинг
        return AIRLINES_FALLBACK

Technical Analysis

The airline cache uses the fixed and globally predictable path /tmp/airlines_cache.json. Shared temporary directories are normally writable by other local users. The implementation does not verify that the cache is a regular file owned by the current user, does not reject symbolic links, and does not use secure or atomic file-creation flags.

The ...[truncated 2590 chars]

Remediation
View remediation

Remediation Suggestions

  1. Store the cache in a private, per-user cache directory rather than directly under the shared /tmp directory. Create the directory with permissions 0700.
  2. Create cache files with permissions 0600 and reject symbolic links. On supported Unix systems, use os.open() with O_NOFOLLOW, appropriate creation flags, and an explicit mode.
  3. Before reading an existing cache, use lstat() or equivalent checks to confirm that it is a regular file, is not a symbolic link, and is owned by the current effective user.
  4. Write updates to a securely created temporary file inside the private cache directory, flush and close it, and then atomically replace the cache with os.replace().
  5. Validate cached JSON before use. Require a dictionary whose keys and values match expected airline-code and display-name formats, and impose reasonable size limits.
  6. Consider using platformdirs to select an operating-system-appropriate per-user cache location or tempfile for secure temporary-file creation.
  7. Handle ownership or type-check failures by ignoring the unsafe cache rather than opening or replacing it.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (18)

Tainted flow: 'token' from os.environ.get (line 60, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/search_flights.py (reported line 84)May include surrounding context.

python
# Загружаем с API
    try:
        url = f"{BASE_URL}/data/ru/airlines.json"
        resp = requests.get(url, params={"token": token}, timeout=10)
        resp.raise_for_status()
        data = resp.json()
        # data — список объектов [{code, name, ...}]

Tainted flow: 'params' from os.environ.get (line 334, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/search_flights.py (reported line 212)May include surrounding context.

python
params["direct"] = "true"

    try:
        resp = requests.get(
            f"{BASE_URL}/aviasales/v3/prices_for_dates",
            params=params,
            timeout=15,

Tainted flow: 'params' from os.environ.get (line 334, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/search_flights.py (reported line 251)May include surrounding context.

python
}
    # month в формате YYYY-MM — передаём как фильтр (API не фильтрует напрямую, берём всё)
    try:
        resp = requests.get(
            f"{BASE_URL}/aviasales/v3/grouped_prices",
            params=params,
            timeout=15,

Tainted flow: 'params' from os.environ.get (line 334, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/search_flights.py (reported line 304)May include surrounding context.

python
params["destination"] = args.to

    try:
        resp = requests.get(
            f"{BASE_URL}/aviasales/v3/get_latest_prices",
            params=params,
            timeout=15,

Tainted flow: 'params' from os.environ.get (line 334, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/search_flights.py (reported line 339)May include surrounding context.

python
"token": token,
    }
    try:
        resp = requests.get(
            f"{BASE_URL}/v1/city-directions",
            params=params,
            timeout=15,

Chaining Abuse

High
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Content

Scanner excerpt · references/airlines.md (reported line 7)May include surrounding context.

md
| Код IATA | Название |
|----------|----------|
| SU | Аэрофлот |
| S7 | S7 Airlines |
| DP | Победа |
| UT | ЮТэйр |

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill documents capabilities that require environment access, network access, and likely local caching/file writes, but it declares no explicit tool scope or permission boundaries. That creates an unnecessary trust gap: an orchestrator may permit broader execution than users expect, and reviewers cannot verify least-privilege constraints from the manifest alone.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger description includes broad phrases like 'fly to' and 'book a ticket', plus generic multilingual travel terms, which can cause the skill to activate on ordinary conversation rather than explicit user intent to query an external flight service. In this skill's context, accidental invocation matters because it can transmit itinerary data to third-party APIs and consume authenticated network capability.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill description explains functionality but does not warn users that origin, destination, dates, and related trip data will be sent to external Travelpayouts/Aviasales services. This is a real privacy and transparency issue because travel plans can be sensitive, and users may not realize their queries leave the local agent environment.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · SKILL.md (reported line 105)May include surrounding context.

md
| IB | Iberia |

## Примечание
Актуальный маппинг загружается автоматически с `https://api.travelpayouts.com/data/ru/airlines.json`
и кэшируется в `/tmp/airlines_cache.json` на 24 часа.
Данный файл служит справочным материалом и фоллбэком при недоступности API.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · SKILL.md (reported line 106)May include surrounding context.

md
| IB | Iberia |

## Примечание
Актуальный маппинг загружается автоматически с `https://api.travelpayouts.com/data/ru/airlines.json`
и кэшируется в `/tmp/airlines_cache.json` на 24 часа.
Данный файл служит справочным материалом и фоллбэком при недоступности API.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · SKILL.md (reported line 107)May include surrounding context.

md
| IB | Iberia |

## Примечание
Актуальный маппинг загружается автоматически с `https://api.travelpayouts.com/data/ru/airlines.json`
и кэшируется в `/tmp/airlines_cache.json` на 24 часа.
Данный файл служит справочным материалом и фоллбэком при недоступности API.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · SKILL.md (reported line 108)May include surrounding context.

md
| IB | Iberia |

## Примечание
Актуальный маппинг загружается автоматически с `https://api.travelpayouts.com/data/ru/airlines.json`
и кэшируется в `/tmp/airlines_cache.json` на 24 часа.
Данный файл служит справочным материалом и фоллбэком при недоступности API.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · references/airlines.md (reported line 53)May include surrounding context.

md
| IB | Iberia |

## Примечание
Актуальный маппинг загружается автоматически с `https://api.travelpayouts.com/data/ru/airlines.json`
и кэшируется в `/tmp/airlines_cache.json` на 24 часа.
Данный файл служит справочным материалом и фоллбэком при недоступности API.

Ae4

Medium
Category
analysis-evasion
Confidence
80% confidence
Finding

Suspicious Unicode normalization or mixed-script content

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The entire file is written in Russian and does not indicate that the language is optional or limited to a justified region-specific use case. Under the policy, forcing a specific language without user opt-in can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

Lines L53-L54 state that current mapping data is automatically downloaded from an external API and cached to a local file in /tmp. This is behavior that affects network/privacy and the local filesystem, but the description presents it as a factual note rather than a user-facing warning or disclosure.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

This code file contains user-facing docstrings, CLI descriptions, help text, and API locale settings in Russian throughout. Under the stated policy, forcing a specific language or locale without offering a choice can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.