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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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。此外,虽然声明支持单程/往返查询,本代码对返程仅做元数据保存和提示文案拼接,没有生成真正的往返搜索命令。因此,实际行为与声明相比存在明显能力缺口和功能范围差异,应判定为描述与代码不完全匹配。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

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

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

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

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

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

Content

No source excerpt is available for this finding.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · SKILL.md (reported line 144)May include surrounding context.

text

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)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/monitor_manager.py (reported line 93)May include surrounding context.

python
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))

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · SKILL.md (reported line 199)May include surrounding context.

md
### 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`.

External Transmission

Medium
Category
Data Exfiltration
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.

Content

Scanner excerpt · SKILL.md (reported line 298)May include surrounding context.

bash
python scripts/search_flights.py --setup-zbape <YOUR_KEY>
# Get a free key at: https://api.zbape.com/doc/54

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

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/notify.py (reported line 60)May include surrounding context.

python
"""
    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:

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · scripts/price_history.py (reported line 3)May include surrounding context.

python
#!/usr/bin/env python3
"""
price_history.py — Read and write flight price history records.

Storage layout:
  ~/.workbuddy/flight-monitor/{DEP}-{ARR}-{DATE}.json

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

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/search_flights.py (reported line 339)May include surrounding context.

python
# ── 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:
    """

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.