Back to skill

Security audit

文旅公众号信息源

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it advertises, but its subscription mode creates persistent scheduled tasks and has unsafe handling of shell commands and API keys.

Install only if you trust RedFoxHub and need recurring WeChat tourism reports. Avoid enabling subscription mode until the cron shell injection is fixed, API keys are not written into LaunchAgent plist files, generated HTML is escaped, and browser opening is opt-in. Prefer one-off report generation with a scoped output directory and a revocable REDFOX_API_KEY.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (31)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
script_path = os.path.abspath(__file__)
        cron_line = f"0 9 * * * /usr/bin/python3 {script_path} --keyword {keyword} --no-open"
        try:
            subprocess.run(
                f'(crontab -l 2>/dev/null; echo "{cron_line}") | crontab -',
                shell=True, check=True, capture_output=True
            )
Confidence
99% confidence
Finding
This subprocess invocation builds a shell command with shell=True and interpolates the user-controlled keyword into cron_line without escaping. An attacker supplying shell metacharacters in --keyword could inject arbitrary commands during subscription setup, leading to code execution under the current user.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
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
            )
Confidence
91% confidence
Finding
This subprocess call uses shell=True and interpolates script_path into a shell pipeline. While script_path is usually derived from the local filesystem path, unusual characters in the path could break quoting or alter shell behavior, making this a command-construction vulnerability in hostile environments.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares no permissions, yet its documented behavior requires sensitive capabilities including shell execution, network access, environment/config file reads, and file writes. This creates a transparency and consent problem: an agent or reviewer may treat the skill as low-risk while it can access secrets, write files, and invoke external commands.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The stated purpose is content retrieval and reporting, but the skill also documents persistence-like behavior through scheduled tasks, browser launching, subprocess invocation, and propagation of API key material into subscription configuration. That mismatch is dangerous because users may authorize a simple reporting skill without realizing it can modify system scheduler state and store secrets in additional locations.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The skill presents itself as an article search/report generator but also installs persistent scheduled tasks on the host. That hidden expansion from content retrieval into persistence increases risk because users may not expect long-lived background execution and repeated API/network activity.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The code modifies LaunchAgents/crontab and invokes task-management commands, which is more invasive than the manifest's simple reporting purpose suggests. In a skill context, such host-level scheduler modification is risky because it changes system state persistently and can be abused to maintain recurring execution.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The README says users can invoke the skill by 'just describ[ing] what you need in natural language,' while the trigger domain is broad and overlaps with common travel/news requests. This can cause unintended activation and data-fetching or report-generation in contexts where the user did not clearly consent to using this specific external-feed skill.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill advertises 'daily subscription support' and scheduled auto-delivery, but does not clearly warn that this creates an ongoing action beyond the current session. Users may unknowingly enable recurring data pulls or message delivery, creating consent, privacy, and operational risk if the subscription persists without clear confirmation or cancellation guidance.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The README encourages invocation through broad natural-language phrases such as generic requests for tourism/news content, which can overlap with ordinary user conversation and cause the skill to activate unintentionally. In an agent ecosystem, over-broad triggers can route unrelated queries to this skill, leading to unnecessary external data access, unintended subscriptions, or user confusion about why a third-party capability was invoked.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The skill requires automatically opening a generated local HTML report and explicitly forbids using the safer no-open option, but it does not present that as a meaningful side effect requiring consent. Auto-opening local HTML increases risk because generated reports may contain active content or external resource loads, and it unexpectedly triggers client-side actions on the host.

Missing User Warnings

High
Confidence
99% confidence
Finding
The cron subscription setup performs shell execution with unsanitized user input embedded into the command string. Because the keyword ultimately lands inside a shell command, crafted input can execute arbitrary commands immediately and also persist malicious cron entries.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The subscription feature writes a persistent LaunchAgent and may embed the API key into the plist environment, storing sensitive credentials on disk in a scheduled job definition. That creates a local secret exposure and a durable execution mechanism that exceeds normal expectations for a report-generation skill.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
Remote article fields such as title, url, author, and cover URL are inserted directly into HTML attributes and element content without escaping. If the upstream API or a malicious article record returns HTML or JavaScript payloads, opening the generated report can trigger stored script execution in the browser or local file context.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
log_path = str(Path.home() / "Library" / "Logs" / "qoder-cultural-tourism-wechat-feed.log")

        env_section = ""
        api_key = os.environ.get(ENV_KEY)
        if api_key:
            env_section = (
                '\n        <key>EnvironmentVariables</key>'
Confidence
90% confidence
Finding
This code reads the API key from the environment and may write it into the LaunchAgent plist's EnvironmentVariables section, persisting the secret on disk for later scheduled execution. Persisting secrets in plaintext configuration broadens exposure to other local processes, backups, or users with filesystem access.

Unvalidated Output Injection

High
Category
Output Handling
Content
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
            )
Confidence
88% confidence
Finding
The crontab removal logic constructs a shell pipeline with interpolated path content and feeds the result back into crontab. Even though the path is not directly user-entered in normal flows, path-derived shell injection and brittle text filtering can corrupt scheduler state or execute unintended shell syntax.

Session Persistence

Medium
Category
Rogue Agent
Content
cron_line = f"0 9 * * * /usr/bin/python3 {script_path} --keyword {keyword} --no-open"
        try:
            subprocess.run(
                f'(crontab -l 2>/dev/null; echo "{cron_line}") | crontab -',
                shell=True, check=True, capture_output=True
            )
            info(f"订阅成功! 每天 09:00 自动生成「{keyword}」文旅公众号日报 (crontab)")
Confidence
96% confidence
Finding
The code installs a recurring cron job, creating persistence that causes the script to run daily without further user interaction. In the context of a content-reporting skill, this is a meaningful security-relevant behavior because it establishes long-lived background execution and recurring network access.

Session Persistence

Medium
Category
Rogue Agent
Content
# ─── 订阅机制 ──────────────────────────────────────────────────────────────────────
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")
Confidence
96% confidence
Finding
This function begins the process of installing a persistent scheduled task on macOS via LaunchAgents. In a skill advertised for article reporting, host persistence increases the attack surface and can surprise users by enabling recurring execution.

Session Persistence

Medium
Category
Rogue Agent
Content
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")
Confidence
95% confidence
Finding
The code computes the path of a LaunchAgent plist under the user's LaunchAgents directory, which is part of establishing persistence. In context, this is security-relevant because it prepares a durable auto-start mechanism beyond simple report generation.

Session Persistence

Medium
Category
Rogue Agent
Content
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")
Confidence
95% confidence
Finding
The code computes the path of a LaunchAgent plist under the user's LaunchAgents directory, which is part of establishing persistence. In context, this is security-relevant because it prepares a durable auto-start mechanism beyond simple report generation.

Session Persistence

Medium
Category
Rogue Agent
Content
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")
Confidence
95% confidence
Finding
The code computes the path of a LaunchAgent plist under the user's LaunchAgents directory, which is part of establishing persistence. In context, this is security-relevant because it prepares a durable auto-start mechanism beyond simple report generation.

Session Persistence

Medium
Category
Rogue Agent
Content
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")
Confidence
95% confidence
Finding
The code computes the path of a LaunchAgent plist under the user's LaunchAgents directory, which is part of establishing persistence. In context, this is security-relevant because it prepares a durable auto-start mechanism beyond simple report generation.

Session Persistence

Medium
Category
Rogue Agent
Content
'\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>
Confidence
95% confidence
Finding
This line constructs plist content for a LaunchAgent, a persistence artifact that will survive script termination and future sessions. Such durable task creation is a notable behavioral expansion for this skill and can be abused if the script or environment is later modified.

Session Persistence

Medium
Category
Rogue Agent
Content
)

        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>
Confidence
95% confidence
Finding
The plist document declaration is part of building a LaunchAgent persistence file. Within this skill's context, that contributes to scheduled, repeated execution on the host, which is more invasive than expected for one-off reporting.

Session Persistence

Medium
Category
Rogue Agent
Content
)

        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>
Confidence
95% confidence
Finding
The plist document declaration is part of building a LaunchAgent persistence file. Within this skill's context, that contributes to scheduled, repeated execution on the host, which is more invasive than expected for one-off reporting.

Session Persistence

Medium
Category
Rogue Agent
Content
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>
    <string>{PLIST_LABEL}</string>
Confidence
95% confidence
Finding
This line assigns the LaunchAgent label inside the persistence plist. It is part of creating a durable scheduler entry that can continue invoking the script across sessions.

Static analysis

No suspicious patterns detected.