Back to skill

Security audit

flight-monitor

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly aligned with flight search and monitoring, but it needs review because it creates recurring local automation and stores notification/API keys without strong protection.

Install only if you are comfortable with recurring WorkBuddy automation and with Ctrip, zbape, and configured push providers receiving flight route/date or alert content. Use dedicated, revocable API or push keys, avoid shared machines, check permissions on ~/.workbuddy/flight-monitor, and inspect generated automation tasks before enabling recurring monitors.

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

Warning
Location
scripts/notify.py:31
Finding
Push notification credentials are stored without enforced restrictive file permissions## Vulnerability Details **File Location**: `scripts/notify.py:31-45` **Vulnerability Type**: Plaintext credential storage with insufficient permission enforcement **Risk Level**: Medium ### Vulnerable Code ```python CONFIG_DIR = Path.home() / ".workbuddy" / "flight-monitor" CONFIG_FILE = CONFIG_DIR / "notify_config.json" def load_config() -> dict: if CONFIG_FILE.exists(): try: return json.loads(CONFIG_FILE.read_text(encoding="utf-8")) except Exception: pass return {} def save_config(cfg: dict): CONFIG_DIR.mkdir(parents=True, exist_ok=True) CONFIG_FILE.write_text(json.dumps(cfg, ensure_ascii=False, indent=2), encoding="utf-8") ``` The configuration written by this function includes the user-provided Bark, ServerChan, or PushDeer key: ```python cfg["service"] = service cfg["key"] = key save_config(cfg) ``` ### Technical Analysis Push-service credentials are saved in plaintext at `~/.workbuddy/flight-monitor/notify_config.json`. The code neither creates the directory with an explicit `0700` mode nor creates the credential file with an explicit `0600` mode. It also does not repair permissions on an existing file. Consequently, confidentiality depends entirely on the process umask and existing filesystem permissions. On a multi-user system, permissive defaults or a pre-created configuration file could allow another local account or compromised process to read the push credential. This network credential is necessary for the optional notification feature, but storing it without explicit access controls exceeds the minimum safe privilege model for that feature. ### Attack Path 1. A user runs `notify.py --setup` with a valid push-service key. 2. The key is written in plaintext to `~/.workbuddy/flight-monitor/notify_config.json`. 3. The file inherits permissions from the process umask or retains unsafe permissions if it already exists. ...[truncated 705 chars]
Remediation
## Remediation Suggestions - Create `~/.workbuddy/flight-monitor` with mode `0700`. - Create credential files atomically with mode `0600`, rather than relying on the ambient umask. - After writing, explicitly verify and correct the file mode. - Reject symbolic links and use an atomic temporary-file-and-rename pattern to reduce file replacement risks. - Prefer an operating-system credential store or secret-management facility instead of plaintext JSON. - Avoid passing credentials directly on the command line because process listings and shell history may expose them. - If plaintext storage remains necessary, document the security implications and provide a command that verifies configuration permissions. Example hardening approach: ```python import os import tempfile CONFIG_DIR.mkdir(parents=True, exist_ok=True, mode=0o700) os.chmod(CONFIG_DIR, 0o700) fd, temp_name = tempfile.mkstemp(dir=CONFIG_DIR, prefix=".notify-", text=True) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as handle: json.dump(cfg, handle, ensure_ascii=False, indent=2) os.replace(temp_name, CONFIG_FILE) os.chmod(CONFIG_FILE, 0o600) finally: if os.path.exists(temp_name): os.unlink(temp_name) ```

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/search_flights.py:72
Finding
zbape API key is stored without enforced restrictive file permissions## Vulnerability Details **File Location**: `scripts/search_flights.py:72-82` **Vulnerability Type**: Plaintext API credential storage with insufficient permission enforcement **Risk Level**: Medium ### Vulnerable Code ```python def load_config() -> dict: if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE, "r", encoding="utf-8") as f: return json.load(f) return {} def save_config(cfg: dict): os.makedirs(CONFIG_DIR, exist_ok=True) with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(cfg, f, ensure_ascii=False, indent=2) ``` The setup function places the API key in this configuration: ```python def setup_zbape(key: str): cfg = load_config() cfg["zbape_key"] = key save_config(cfg) print(f"zbape API key 已保存到 {CONFIG_FILE}") ``` ### Technical Analysis The optional zbape API key is persisted in plaintext in `~/.workbuddy/flight-monitor/api_config.json`. The normal `open(..., "w")` operation does not enforce owner-only access and preserves the permissions of an existing file. Directory permissions are similarly left to the environment. The API key is legitimately required when the optional zbape source is enabled, but owner-only access is the minimum privilege needed. Depending on the umask and host configuration, the current implementation can expose the key to other local principals. The key is also accepted as a command-line argument through `--setup-zbape`, which may leave it visible in shell history or transiently visible in process listings. ### Attack Path 1. A user configures zbape by running `search_flights.py --setup-zbape KEY`. 2. The Skill stores the key in `~/.workbuddy/flight-monitor/api_config.json`. 3. The configuration file receives or retains permissions that permit access by another local account or process. 4. The attacker reads the plaintext `zbape_key`. 5. The attacker submits requests using the st ...[truncated 664 chars]
Remediation
## Remediation Suggestions - Set the configuration directory to mode `0700`. - Create `api_config.json` atomically with mode `0600`. - Explicitly correct permissions after replacing or updating an existing file. - Store the key in an OS credential store where available. - Accept the key through protected standard input or an interactive hidden prompt instead of a command-line argument. - Validate that the configuration path is not a symbolic link before writing. - Add permission checks during loading and refuse to use a credential file that is readable by group or other users. Example permission check: ```python mode = os.stat(CONFIG_FILE, follow_symlinks=False).st_mode & 0o777 if mode & 0o077: raise PermissionError( f"Refusing to load credential file with unsafe permissions: {oct(mode)}" ) ```

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/monitor_manager.py:133
Finding
Persistent monitoring prompt may place externally sourced flight data into shell commands## Vulnerability Details **File Location**: `scripts/monitor_manager.py:133-158` **Vulnerability Type**: Potential command injection through unsafe Agent-generated shell arguments **Risk Level**: Medium ### Vulnerable Code ```python notify_step = ( f"4. 若触发低价提醒,运行推送通知:python {SCRIPTS_DIR / 'notify.py'} " f"--title \"机票低价提醒 {dep}→{arr}\" " f"--body \"{dep}→{arr} 出行日期{date} 最低价¥<最低价>,低于阈值¥{threshold}!推荐航班<航班号>\" " f"--url \"https://flights.ctrip.com/itinerary/oneway/{dep.lower()}-{arr.lower()}?depdate={date}\"" ) if threshold else "" prompt = ( f"【机票价格监控】出行日期:{date}(注意:搜索时必须使用此出行日期,不能用今天日期)。" f"请按以下步骤执行:\n" f"1. 运行:python {SCRIPTS_DIR / 'search_flights.py'} --from {dep} --to {arr} --date {date}" + (f" --max-price {threshold}" if threshold else "") + f"\n2. 若脚本 source 为 fallback,使用 search_query 字段执行 web_search(仅一次)," f"搜索词必须包含出行日期 {date}{rt_note}{threshold_note}。" f"\n3. 运行:python {SCRIPTS_DIR / 'price_history.py'} append " f"--from {dep} --to {arr} --date {date} --price <最低价> --flight \"<航班号>\"" + (f" --threshold {threshold}" if threshold else "") + (f"\n{notify_step}" if notify_step else "") ) ``` ### Technical Analysis The monitor manager writes a persistent WorkBuddy automation prompt that directs an Agent to obtain flight prices and flight numbers from Ctrip, zbape, or web-search snippets and substitute them into command-line placeholders. The route and date arguments are protected by strict validation. However, the prompt provides no equivalent validation for the externally sourced price and flight-number values. In particular, wrapping a value in double quotes does not prevent shell command substitution such as `$(command)` or backtick syntax when a shell interprets the final command. The Python script does not directly call `subprocess` or `os.system`; therefore, exploitation depends on the automation Agent executi ...[truncated 1855 chars]
Remediation
## Remediation Suggestions - Do not instruct the Agent to interpolate remotely sourced values into shell command strings. - Execute child programs through a structured process API with an argument array and `shell=False`. - Prefer passing result data as validated JSON over standard input. - Require flight numbers to match a strict allowlist pattern appropriate for supported data, such as `^[A-Z0-9]{2,4}[0-9]{1,4}$`, after normalization. - Require prices and thresholds to be finite, non-negative numbers within a reasonable range. Reject `NaN`, infinity, and arbitrary strings. - Treat web-search snippets and all OTA responses as untrusted data. - Avoid persisting free-form executable prompts. Persist structured task parameters and let trusted code perform each operation. - If Agent-driven execution is unavoidable, require the Agent to serialize values to a file and invoke a fixed command that reads that file without shell interpolation. - Add tests using values containing quotes, semicolons, backticks, `$()`, newlines, and option-like prefixes. A safer architecture would store only structured fields: ```json { "dep": "BJS", "arr": "SYX", "date": "2026-03-25", "threshold": 1500 } ``` A trusted runner should then call the relevant scripts with an argument list: ```python subprocess.run( [ sys.executable, str(price_history_script), "append", "--from", dep, "--to", arr, "--date", travel_date, "--price", str(validated_price), "--flight", validated_flight, ], shell=False, check=True, ) ```
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (26)

Tp4

High
Category
MCP Tool Poisoning
Confidence
87% confidence
Finding
声明描述的是一个完整的“机票查询与价格监控”技能,包含查询、提醒、定时监控和多种推送通知能力。但该代码块的实际职责更窄,主要是管理监控任务配置文件:校验参数、生成 task_id、写入 automation.toml、列出/暂停/删除任务,以及在 run 时返回手动执行 search_flights.py 的提示。代码没有直接执行航班搜索,也没有实际调用通知服务;所谓通知仅体现在写入的 prompt 中让外部自动化系统未来执行 notify.py。此外,虽然声明支持单程/往返查询,本代码对返程仅做元数据保存和提示文案拼接,没有生成真正的往返搜索命令。因此,实际行为与声明相比存在明显能力缺口和功能范围差异,应判定为描述与代码不完全匹配。

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
该代码与“机票价格监控”主题相关,但实际只是一个本地价格历史文件读写工具,不会连接任何航班或票价数据源,也没有执行查询、抓取、监控调度或推送通知。它支持阈值字段的存储,并在输出文本中显示‘低价提醒’,但这不是声明中的手机推送提醒能力。代码还提供‘list’列出监控路线,与“查看所有机票监控任务”部分部分吻合。总体上,声明描述的是一个完整的查询+监控+通知技能,而代码只覆盖其中很小一部分,因此属于描述与实际行为不符。

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
声明描述的是一个完整的“查询+监控+通知”技能,但提供的代码块只是一个查询脚本。它支持单程/往返参数和 max-price 条件,并生成给 AI 的 web_search 指令以及携程/飞猪/去哪儿/Google Flights 链接,这与“机票查询”部分基本一致。但代码中没有任何持久化、定时调度、后台轮询、价格变化检测、消息推送接口调用,或监控任务增删查改逻辑,因此无法支持描述中关于价格监控、低价提醒、定时监控、推送通知和查看监控任务的核心能力。故描述与实际行为存在明显不匹配。

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
声明描述将该技能定位为“机票查询与价格监控”系统,核心包含监控、提醒、任务管理和推送通知。但提供的代码块实际只覆盖了查询层:解析出发/到达城市,调用携程或 zbape 接口获取航班/最低价,按 max_price 做一次性过滤,并输出 Markdown 或 JSON 结果;如果查询失败则构造一个搜索关键词和预订链接。代码中没有任何定时器、后台任务、任务持久化结构、价格变化比较、通知发送 HTTP 调用(Bark/Server酱/PushDeer)或“查看所有监控任务”的实现。因此,声明对技能能力有明显夸大,属于描述与实际行为不一致。

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
```

notify.py will automatically use whichever push service is configured.
If no push service is configured, it will print instructions to set one up.

### Step 5 — Set Up Monitoring (when user requests recurring checks)
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Credential Access

High
Category
Privilege Escalation
Content
def validate_task_id(task_id: str) -> bool:
    """
    Whitelist check: only allow IDs of the form flight-XXX-XXX-YYYY-MM-DD.
    Prevents path traversal (e.g. '../../etc/passwd') and shell metacharacters.
    """
    return bool(_TASK_ID_RE.match(task_id))
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill requests or instructs use of shell, filesystem, and network-capable operations but does not declare any explicit tool scope or permissions boundary. That makes the effective trust boundary unclear and increases the chance an agent executes higher-risk actions than users or reviewers expect, especially because the workflow includes writing files, storing credentials, and contacting external services.

Session Persistence

Medium
Category
Rogue Agent
Content
### Option C — PushDeer (open source, Android & iOS)

1. Install PushDeer app or use web version at https://www.pushdeer.com/
2. Create a device and copy the push key
3. Configure: `python scripts/notify.py --setup pushdeer --key <YOUR_KEY>`

Configuration is saved to `~/.workbuddy/flight-monitor/notify_config.json`.
Confidence
89% confidence
Finding
The skill stores push notification credentials in a persistent config file under the user's home directory. Persistent secret storage without documented permission controls, encryption, or rotation guidance increases the risk of credential theft from local compromise, backups, or accidental sharing of the config directory.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
python scripts/search_flights.py --setup-zbape <YOUR_KEY>
# Get a free key at: https://api.zbape.com/doc/54
```
Confidence
80% confidence
Finding
The skill directs users to obtain and configure an API key for a third-party service, implying external transmission of queries and possibly route/date data outside the local environment. In a travel-monitoring context this is expected, but it still creates a data-sharing boundary that should be explicitly disclosed and constrained because itinerary searches can reveal user habits and plans.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module usage block labels `python monitor_manager.py run --id ...` as a 'manual trigger', which suggests the command will actually run the monitor. In reality, `run_monitor` only returns a formatted message with a suggested `search_flights.py` command, and `main()` does not even print that return value, so no monitoring action is performed.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file defines frequency labels entirely in Chinese and throughout the script returns Chinese-only status and error text. This imposes a specific language on users without any opt-in, which matches the language/locale policy violation criteria.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The remove_monitor function unlinks all files in the task directory and removes the directory, which is a destructive operation. There is no confirmation prompt or user-facing warning in this function or its surrounding CLI flow indicating that running remove will permanently delete task files.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The `run_monitor` docstring says the function will 'Show the monitoring prompt for a task.' However, in `main()` the `run` branch calls `run_monitor(args.id)` without printing or otherwise returning its result, so the user sees nothing. This directly contradicts the function's documented behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
"""
    t = urllib.parse.quote(title, safe="")
    b = urllib.parse.quote(body, safe="")
    api_url = f"https://api.day.app/{key}/{t}/{b}"
    if url:
        api_url += "?url=" + urllib.parse.quote(url, safe="")
    try:
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
The script presents operational prompts and status messages in Chinese, such as setup guidance and notification status text, while the CLI description is otherwise in English. This creates a language/locale policy concern because users are not given an opt-in choice or a documented reason that the skill must operate in Chinese.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/usr/bin/env python3
"""
price_history.py — Read and write flight price history records.

Storage layout:
  ~/.workbuddy/flight-monitor/{DEP}-{ARR}-{DATE}.json
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
This code returns validation errors exclusively in Chinese, and later output formatting also uses Chinese strings. For a general-purpose CLI skill, forcing a specific language without user opt-in matches the language/locale policy violation criteria.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The history report headings, labels, and alert text are all hardcoded in Chinese with no alternative locale path. This is a natural-language policy concern because the skill enforces one language for user-visible behavior without explicit user choice or documented regional scope.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The request headers explicitly set Accept-Language to zh-CN, and much of the user-facing output is hardcoded in Chinese, which imposes a locale choice on users. The file does not present this as an opt-in setting or justify it as a region-specific skill.

External Transmission

Medium
Category
Data Exfiltration
Content
# ── Source 2: zbape.com cheapest-price API ───────────────────────────────────

ZBAPE_API = "https://api.zbape.com/api/plane/query"

def fetch_zbape(dep_name: str, arr_name: str, zbape_key: str) -> dict:
    """
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

Medium
Confidence
95% confidence
Finding
The script persists the zbape API key in a plaintext JSON file under the user's home directory without warning, consent, or file-permission hardening. If the host is shared, backed up, or compromised, the credential can be recovered and abused to consume the third-party API or expose account-linked usage.

Natural-Language Policy Violations

Low
Confidence
72% confidence
Finding
The manifest description is written entirely in Chinese and presents only Chinese trigger examples, which can imply a fixed language/locale for invocation. The file does contain some English elsewhere, but there is no explicit statement that users may interact in their preferred language or that Chinese is optional.

Missing User Warnings

Low
Confidence
86% confidence
Finding
The skill instructs users to configure third-party push notification keys and send travel-alert content through external services, but it does not provide an explicit warning about credential sensitivity, storage, or disclosure of itinerary information to those providers. This can lead users to expose notification credentials or personal travel data without informed consent.

Natural-Language Policy Violations

Low
Confidence
79% confidence
Finding
This markdown file is natural-language content, so locale policy applies. The document is centered on Chinese-language city naming and bilingual labels but does not clarify whether this language choice is optional, user-selected, or mandated for the skill's operation.

Natural-Language Policy Violations

Low
Confidence
85% confidence
Finding
The usage examples, city mappings, airline labels, generated search text, and formatted output are centered on Chinese-language inputs and responses, with no natural-language indication that users may choose another language or locale. This can violate language/locale policy when a skill effectively mandates one language without explicit opt-in or documented regional scope.

Static analysis

No suspicious patterns detected.