Back to skill

Security audit

文旅公众号信息源

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a disclosed WeChat tourism reporting tool, but its subscription mode makes persistent system changes and handles secrets unsafely.

Review carefully before installing. Use one-time report generation only, avoid --subscribe until the publisher removes shell injection risk and stops storing API keys in scheduler files, and prefer an environment-injected or keychain-managed API key over plaintext config.

Vulnerability Patterns
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/cultural_tourism_report.py:530
Finding

Shell Command Injection in Cron Subscription Installation

Content
View full analysis
/dev/null; echo "{cron_line}") | crontab -', shell=True, check=True, capture_output=True ) ``` ### Technical Analysis The `keyword` value comes from the user-controlled `--keyword` command-line argument. It is concatenated into `cron_line`, which is then interpolated into a command executed with `shell=True`. No shell escaping, input validation, or safe argument serialization is applied. An attacker can include a double quote, command substitution, command separator, redirection operator, or other shell metacharacters in the keyword. This can escape the intended `echo` argument and cause the shell to execute additional commands. The use of `shell=True` is unnecessary for the declared reporting functionality and exposes the full privileges of the user running the Skill. ### Attack Path 1. An attacker supplies a crafted keyword and requests subscription installation. 2. The value reaches `args.keyword` without validation. 3. `install_subscription(args.keyword)` incorporates it into `cron_line`. 4. The resulting string is embedded in a larger shell pipeline. 5. `subprocess.run(..., shell=True)` passes the string to the system shell. 6. Shell metacharacters in the keyword cause attacker-selected commands to execute. 7. The commands run with the permissions of the Agent or user invoking the Skill. For example, a keyword containing a terminating quote and shell separator could alter the generated command rather than being treated solely as search text. ### Impact Assessment Successful exploitation provides arbitrary command execution ...[truncated 466 chars]
Remediation
View remediation

T06 · System Persistence

Error
Location
scripts/cultural_tourism_report.py:468
Finding

Cross-Session Persistence Through LaunchAgent and Crontab Registration

Content
View full analysis
/dev/null; echo "{cron_line}") | crontab -', shell=True, check=True, capture_output=True ) ``` ### Technical Analysis The `--subscribe` feature creates either: - A macOS LaunchAgent in `~/Library/LaunchAgents`, loaded using `launchctl`; or - A Unix crontab entry that executes the script every day. These tasks survive the original Skill invocation and cause future authenticated network requests and report generation. The behavior is disclosed as a daily subscription feature and is only entered when `--subscribe` is selected, so it is related to declared functionality. Nevertheless, it establishes persistent execution beyond what is necessary for a one-time report query. The scheduled task executes the script directly from its current project path. If that path or script can later be modified, the scheduler will execute the modified content automatically. ### Attack Path 1. A subscription request causes the Skill to inv ...[truncated 1066 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/cultural_tourism_report.py:479
Finding

API Key Stored in Plaintext LaunchAgent Configuration

Content
View full analysis
EnvironmentVariables' '\n ' f'\n {ENV_KEY}' f'\n {api_key}' '\n ' ) ``` The generated value is subsequently written to the LaunchAgent file: ```python plist_path.write_text(plist_content, encoding="utf-8") ``` ### Technical Analysis When the API key is available through `REDFOX_API_KEY`, subscription installation copies the credential into a persistent XML plist. The secret is stored as plaintext, and the code does not explicitly enforce restrictive file permissions. This also conflicts with the documented security guidance that API keys should not be exposed in output files. Environment variables are normally process-scoped, while embedding the key in a LaunchAgent turns it into a long-lived credential artifact. The API key is also inserted without XML escaping. Special XML characters could corrupt the plist, although API key formats commonly use a restricted character set. ### Attack Path 1. The user exports `REDFOX_API_KEY`. 2. The user or Agent invokes the Skill with `--subscribe`. 3. `install_subscription` reads the key from the environment. 4. The key is interpolated into `plist_content`. 5. The plist is written under `~/Library/LaunchAgents`. 6. A local process, backup system, diagnostic tool, or user with access to the file can recover the plaintext credential. 7. The recovered key can be used for unauthorized RedFox API requests until it is revoked or expires. ### Impact Assessment Exposure can permit unauthorized use of the RedFox API under the victim's account or quota. The precise server-side capabil ...[truncated 216 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/cultural_tourism_report.py:318
Finding

Unescaped External Data in Automatically Opened HTML Report

Content
View full analysis
' ) articles_html += ( '
' + cover_html + '
' + '' + title + '' + '
' + '' + author + '' + '' + '📖 ' + reads + '' + time_html + '
' ) ``` ```python category_cards = generate_category_cards(clusters) html = template if keyword: keyword_badge = '
「' + keyword + '」
' else: keyword_badge = '' html = html.replace("{{KEYWORD_BADGE}}", keyword_badge) html = html.replace("{{KEYWORD}}", keyword or "全部") html = html.replace("{{CATEGORY_CARDS}}", category_cards) ``` The generated file is automatically opened later in the main flow: ```python if not args.no_open: file_path = str(output_path) if sys.platform == "darwin": subprocess.Popen(["open", file_path]) elif sys.platform == "linux": subprocess.Popen(["xdg-open", file_path]) ``` ### Technical Analysis Article titles, author names, category labels, article URLs, cover URLs, and the user-supplied keyword are concatenated directly into HTML. There is no context-aware escaping for text nodes or attribute valu ...[truncated 1693 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
SKILL.md:99
Finding

Unpinned Runtime Dependency Installation

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (46)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The published description frames the skill as a content search/reporting tool, but the behavior includes persistent scheduled execution, reading local credentials, and invoking system commands to open browsers and manage cron/launchd tasks. This mismatch is dangerous because users may consent to a simple search utility without realizing it will install persistence or interact with local system state.

Content

No source excerpt is available for this finding.

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · assets/report_template.html (reported line 8)May include surrounding context.

html
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文旅公众号信息源 - {{KEYWORD}} - {{DATE}}</title>

<!-- Fonts: Archivo Black (display) + Space Grotesk (body) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
99% confidence
Finding

This is a strong tool-parameter abuse case because a shell command is assembled with interpolated values and executed to rewrite crontab. In an agent/skill setting, even partially user-influenced values such as script path or keyword can become a bridge to arbitrary shell execution and persistent task manipulation.

Content

Scanner excerpt · scripts/cultural_tourism_report.py (reported line 562)May include surrounding context.

python
else:
        script_path = os.path.abspath(__file__)
        try:
            subprocess.run(
                f'crontab -l 2>/dev/null | grep -v "{script_path}" | crontab -',
                shell=True, check=True, capture_output=True
            )

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The README states users can invoke the skill with unrestricted natural language and 'no commands to memorize,' which creates overly broad activation conditions. In an agent ecosystem, vague triggers increase the chance of accidental or context-inappropriate invocation, causing unneeded external API calls, data retrieval, report generation, or subscription setup without sufficiently explicit user intent.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The sample intents use broad everyday phrasing such as 'Show me the latest...' and 'Subscribe...' without documenting scope checks, disambiguation rules, or consent requirements. This makes it more likely an orchestrating agent will over-trigger the skill from loosely related travel or news queries, and could perform recurring actions like subscriptions based on ambiguous language.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

All user-facing instructions, examples, and invocation guidance are presented only in Chinese, and the file does not offer any language/locale choice or explain that the skill is intentionally limited to Chinese-speaking users. This may violate language/locale policy where skills should not force a specific language without user opt-in or documented justification.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README tells users to invoke the skill with broad natural-language phrases and says they do not need to remember commands, but it does not define clear activation boundaries, confirmation requirements, or constraints on when data retrieval/subscription actions should occur. In an agent setting, this can cause unintended tool activation or overbroad execution from ambiguous user input, especially because the skill supports scheduled subscription behavior and external data fetching.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill requires broad capabilities including network access, file read/write, shell execution, and environment access, but it does not declare any explicit tool scope or permissions boundary. This increases the chance of over-privileged execution and makes it harder for users or the platform to understand and constrain what the skill can do.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The trigger phrases are broad common terms related to tourism, which can cause accidental invocation in normal conversation. Unintended activation is more concerning here because the skill can perform network requests, write local HTML files, open the browser, and potentially configure recurring tasks.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The credential setup instructions recommend exporting an API key directly in the shell and writing it to a plaintext JSON file without warning about shell history, process exposure, or local file protection. This can lead to accidental credential disclosure, especially on shared systems or in logged terminal sessions.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
91% confidence
Finding

The skill instructs users to persist the API key in a local configuration file under the home directory, creating session persistence of sensitive credentials. Persistent plaintext secrets increase the blast radius of local compromise, accidental backup leakage, or unintended reuse by other processes.

Content

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

export REDFOX_API_KEY=ak_xxxx

方式二:配置文件

mkdir -p ~/.qoder/apis echo '{"api_key":"ak_xxxx"}' > ~/.qoder/apis/redfox.json

text

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The skill requires automatic browser opening and local HTML report generation but does not present these side effects as a clear user warning in the description. Hidden local side effects can surprise users, expose report contents on-screen, and create artifacts on disk without informed consent.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The HTML template hard-codes the document language as "zh-CN", which imposes a specific locale on all generated reports. Under the policy, locale-specific behavior should either offer user choice or be clearly documented and justified as region-specific.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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/cultural_tourism_report.py (reported line 39)May include surrounding context.

python
PAGE_SIZE = 200

DEFAULT_OUTPUT_DIR = Path.home() / "Downloads" / "QoderReports"
PLIST_LABEL = "com.qoder.cultural-tourism-wechat-feed"
PLIST_DIR = Path.home() / "Library" / "LaunchAgents"

# ─── 终端颜色 ──────────────────────────────────────────────────────────────────────

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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/cultural_tourism_report.py (reported line 40)May include surrounding context.

python
PAGE_SIZE = 200

DEFAULT_OUTPUT_DIR = Path.home() / "Downloads" / "QoderReports"
PLIST_LABEL = "com.qoder.cultural-tourism-wechat-feed"
PLIST_DIR = Path.home() / "Library" / "LaunchAgents"

# ─── 终端颜色 ──────────────────────────────────────────────────────────────────────

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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/cultural_tourism_report.py (reported line 548)May include surrounding context.

python
PAGE_SIZE = 200

DEFAULT_OUTPUT_DIR = Path.home() / "Downloads" / "QoderReports"
PLIST_LABEL = "com.qoder.cultural-tourism-wechat-feed"
PLIST_DIR = Path.home() / "Library" / "LaunchAgents"

# ─── 终端颜色 ──────────────────────────────────────────────────────────────────────

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
99% confidence
Finding

The code only performs minimal title cleanup for Markdown-like rendering, but later inserts title, author, URL, and coverUrl directly into HTML without escaping. Because these values come from a remote API, malicious content could inject script or dangerous markup into the generated report, and the script then auto-opens that report in the user's browser.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill is presented as a content search/report tool, but it also installs and removes persistent scheduled jobs on the host. That behavior exceeds typical expectations for a reporting skill and creates a persistence capability that could be abused for repeated execution, especially in an agent context where users may not understand host-side changes.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code modifies LaunchAgents/crontab and invokes system management commands unrelated to the core task of fetching and rendering articles. In a skill ecosystem, this expands the blast radius from data processing into host configuration changes and persistence, making misuse more dangerous than the advertised functionality suggests.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
97% confidence
Finding

The install_subscription function begins the workflow for creating persistent LaunchAgent execution on macOS. In the context of a content-report skill, embedding persistence logic is an unnecessary expansion of privilege and operational scope.

Content

Scanner excerpt · scripts/cultural_tourism_report.py (reported line 473)May include surrounding context.

python
# ─── 订阅机制 ──────────────────────────────────────────────────────────────────────
def install_subscription(keyword):
    if sys.platform == "darwin":
        PLIST_DIR.mkdir(parents=True, exist_ok=True)
        plist_path = PLIST_DIR / f"{PLIST_LABEL}.plist"
        script_path = os.path.abspath(__file__)
        log_path = str(Path.home() / "Library" / "Logs" / "qoder-cultural-tourism-wechat-feed.log")

Session Persistence

Medium
Category
Rogue Agent
Confidence
97% confidence
Finding

Constructing a plist path under ~/Library/LaunchAgents is part of establishing user-level persistence on macOS. This enables recurring execution outside the immediate user action that invoked the reporting skill.

Content

Scanner excerpt · scripts/cultural_tourism_report.py (reported line 474)May include surrounding context.

python
def install_subscription(keyword):
    if sys.platform == "darwin":
        PLIST_DIR.mkdir(parents=True, exist_ok=True)
        plist_path = PLIST_DIR / f"{PLIST_LABEL}.plist"
        script_path = os.path.abspath(__file__)
        log_path = str(Path.home() / "Library" / "Logs" / "qoder-cultural-tourism-wechat-feed.log")

Session Persistence

Medium
Category
Rogue Agent
Confidence
97% confidence
Finding

Constructing a plist path under ~/Library/LaunchAgents is part of establishing user-level persistence on macOS. This enables recurring execution outside the immediate user action that invoked the reporting skill.

Content

Scanner excerpt · scripts/cultural_tourism_report.py (reported line 474)May include surrounding context.

python
def install_subscription(keyword):
    if sys.platform == "darwin":
        PLIST_DIR.mkdir(parents=True, exist_ok=True)
        plist_path = PLIST_DIR / f"{PLIST_LABEL}.plist"
        script_path = os.path.abspath(__file__)
        log_path = str(Path.home() / "Library" / "Logs" / "qoder-cultural-tourism-wechat-feed.log")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

When subscription is enabled on macOS, the API key from the environment is copied into the LaunchAgent plist, creating a persistent on-disk credential artifact. Users are not clearly warned that their secret will be stored this way, increasing the chance of inadvertent exposure through local file access, backups, or diagnostics.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
96% confidence
Finding

Building plist content for a LaunchAgent is a direct step toward persistent execution. In a skill that mainly fetches content, this host-level persistence capability materially increases risk if the script or its inputs are later abused.

Content

Scanner excerpt · scripts/cultural_tourism_report.py (reported line 489)May include surrounding context.

python
'\n        </dict>'
            )

        plist_content = f'''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>

Session Persistence

Medium
Category
Rogue Agent
Confidence
96% confidence
Finding

The plist declaration is part of creating a persistent LaunchAgent configuration. While not dangerous in isolation, it contributes directly to unattended recurring execution on the host.

Content

Scanner excerpt · scripts/cultural_tourism_report.py (reported line 490)May include surrounding context.

python
)

        plist_content = f'''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>