Back to skill

Security audit

redbookskills

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly does what it says, but it controls a logged-in Xiaohongshu account and includes under-disclosed account actions, remote browser control, anti-detection guidance, and a real unsafe profile-deletion bug.

Review this carefully before installing. Use only a test Xiaohongshu account unless you accept that it can publish posts, post comments, read notifications and creator analytics, clear cookies, and manage persistent Chrome profiles. Do not enable remote CDP except through a trusted local tunnel, avoid private/internal media URLs, prefer --preview for posting, and do not use --delete-profile until account-name validation and path-containment checks are fixed.

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

Error
Location
scripts/account_manager.py:139
Finding

Account Name Path Traversal Enables Arbitrary Recursive Directory Deletion

Content
View full analysis
bool: data = _load_accounts() if name in data["accounts"]: return False from datetime import datetime profile_dir = os.path.join(PROFILES_BASE, name) os.makedirs(profile_dir, exist_ok=True) data["accounts"][name] = { "alias": alias or name, "profile_dir": profile_dir, "created_at": datetime.now().isoformat(), } _save_accounts(data) return True ``` ```python def remove_account(name: str, delete_profile: bool = False) -> bool: data = _load_accounts() if name not in data["accounts"]: return False # Don't allow removing the default account if it's the only one if name == data.get("default_account") and len(data["accounts"]) == 1: return False profile_dir = data["accounts"][name].get("profile_dir", "") del data["accounts"][name] # If we removed the default, set a new default if name == data.get("default_account"): data["default_account"] = next(iter(data["accounts"].keys())) _save_accounts(data) # Optionally delete the profile directory if delete_profile and profile_dir and os.path.isdir(profile_dir): try: shutil.rmtree(profile_dir) except Exception: pass return True ``` ### Technical Analysis The account name is incorporated directly into a filesystem path using `os.path.join(PROFILES_BASE, name)`. The value is not constrained to a safe identifier and the resulting canonical path is not checked to ensure that it remains under `PROFILES_BASE`. A name containing parent-directory components, such as `../../target`, can escape the intended profile dire ...[truncated 1714 chars]
Remediation
View remediation
str: if not ACCOUNT_NAME_PATTERN.fullmatch(name): raise ValueError("Invalid account name") return name ``` 2. Canonicalize and verify every generated profile path: ```python base = os.path.realpath(PROFILES_BASE) candidate = os.path.realpath(os.path.join(base, validated_name)) if os.path.commonpath([base, candidate]) != base or candidate == base: raise ValueError("Profile path escapes the profile base directory") ``` 3. Repeat the canonical containment check immediately before recursive deletion. Do not rely solely on the path stored in `accounts.json`. 4. Reject symbolic links and prevent time-of-check/time-of-use substitution where feasible. Profile directories should be created and managed exclusively by this application. 5. Record an application-generated profile identifier instead of accepting filesystem path components from users. 6. Require explicit confirmation before profile deletion and report deletion failures rather than silently suppressing every exception. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/image_downloader.py:94
Finding

Unrestricted Media URL Fetching Permits SSRF and Unbounded Disk Consumption

Content
View full analysis
Remediation
View remediation
limit: raise ValueError("Media exceeds the permitted size") written = 0 for chunk in resp.iter_content(chunk_size=65536): if not chunk: continue written += len(chunk) if written > limit: raise ValueError("Media exceeds the permitted size") f.write(chunk) ``` 6. Validate `Content-Type` against an explicit image or video allowlist. Inspect file signatures after download rather than trusting URL extensions or response headers alone. 7. Delete partial files when validation, timeout, or size checks fail. 8. Apply connection, read, and overall operation deadlines, and configure limits on redirect count. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cdp_publish.py:440
Finding

Remote CDP Control Uses Unauthenticated Plaintext Endpoints and Trusts Attacker-Supplied WebSocket URLs

Content
View full analysis
list[dict]: """Get list of available browser targets (tabs). Retries once on failure.""" url = f"http://{self.host}:{self.port}/json" for attempt in range(2): try: resp = requests.get(url, timeout=5) resp.raise_for_status() return resp.json() ``` ```python # Create a new tab resp = requests.put( f"http://{self.host}:{self.port}/json/new?{XHS_CREATOR_URL}", timeout=5, ) if resp.ok: ws_url = resp.json().get("webSocketDebuggerUrl", "") if ws_url: return ws_url # Fallback: use first available page if pages: return pages[0]["webSocketDebuggerUrl"] raise CDPError("No browser tabs available.") def connect(self, target_url_prefix: str = "", reuse_existing_tab: bool = False): """Connect to a Chrome tab via WebSocket.""" ws_url = self._find_or_create_tab( target_url_prefix=target_url_prefix, reuse_existing_tab=reuse_existing_tab, ) if not ws_url: raise CDPError("Could not obtain WebSocket URL for any tab.") print(f"[cdp_publish] Connecting to {ws_url}") self.ws = ws_client.connect(ws_url) print("[cdp_publish] Connected to Chrome tab.") ``` ```python def _send(self, method: str, params: dict | None = None) -> dict: """Send a CDP command and return the result.""" if not self.ws: raise CDPError("Not connected. Call connect() first.") self._msg_id += 1 msg = {"id": self._msg_id, "method": method} if params: msg["params"] = params self.ws.send(json.dumps(msg)) # Wait for the ma ...[truncated 3611 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (38)

Natural-Language Policy Violations

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

This line mandates evasion-oriented behavior to avoid system detection, directly encouraging concealment of automated actions from the target platform. Because the skill's purpose is automated content publishing, such anti-detection guidance materially enables abuse at scale, including spam campaigns, account farming, and bypass of platform enforcement controls.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill is presented as a publishing helper, yet it also performs content search, extracts result lists and note details, applies filters, and reads frontend state unrelated to publishing. This broadens the data-collection scope beyond user expectations and can bypass trust decisions based on the manifest's narrower stated purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill is presented as a publishing helper, yet it also performs content search, extracts result lists and note details, applies filters, and reads frontend state unrelated to publishing. This broadens the data-collection scope beyond user expectations and can bypass trust decisions based on the manifest's narrower stated purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The skill is presented as a publishing helper, yet it also performs content search, extracts result lists and note details, applies filters, and reads frontend state unrelated to publishing. This broadens the data-collection scope beyond user expectations and can bypass trust decisions based on the manifest's narrower stated purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The skill is presented as a publishing helper, yet it also performs content search, extracts result lists and note details, applies filters, and reads frontend state unrelated to publishing. This broadens the data-collection scope beyond user expectations and can bypass trust decisions based on the manifest's narrower stated purpose.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The manifest description limits scope to publishing and test-browser startup, but the body documents capabilities for searching content, retrieving note details, posting comments, accessing notifications, and exporting analytics. Such scope deception is dangerous because users or orchestrators may grant access under a narrower trust assumption than the skill's real behavior warrants.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The script’s implemented capabilities significantly exceed the stated skill scope of publishing content or launching a test browser. It can search feeds, retrieve feed details, post comments, scrape notification mentions, and export creator analytics, which enables account data harvesting and active interaction on behalf of the user without clear disclosure; in an automation skill advertised as simple publishing, this scope mismatch materially increases the risk of covert data access and misuse.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The guidance explicitly instructs implementers to add randomness and simulate human interaction to avoid platform detection, which is an evasion tactic rather than a functional requirement for legitimate publishing. In the context of an automation skill for posting to Xiaohongshu, this increases the likelihood of stealthy abuse, policy circumvention, and harder-to-detect automated account misuse.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The README states that the skill publishes content to a third-party platform and requires account login, but it does not clearly warn users that running the skill can transmit content externally and perform real actions on their account. In an automation skill that posts to XHS, this omission can cause users to underestimate the risk of unintended publication, credential exposure through browser automation, or account misuse.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill exposes shell, file read/write, network, and environment-backed capabilities but does not declare any explicit tool scope or permission boundaries. This creates an over-privileged integration surface where an invoking agent may use sensitive capabilities without clear user-visible constraints or least-privilege enforcement.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The trigger phrase '发布内容到小红书' is broad and does not constrain whether the skill may also search, comment, inspect notifications, or manage accounts once activated. Overbroad triggers can cause accidental invocation in contexts where the user intended only drafting or discussion, increasing the risk of live-account actions.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The instructions frame the skill as a publishing assistant that acts after user confirmation, but the routing logic also diverts into retrieval and interaction workflows. This weakens user expectations around confirmation and can lead to unintended searches, data access, or comments under an authenticated account.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill states that if a user provides only a webpage URL, it will fetch page content and media to build a draft, but it does not warn that user-supplied URLs and retrieved data will be transmitted over the network and processed externally. This can expose private URLs, internal resources, or sensitive page contents if users assume local-only handling.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The publishing commands are documented as automatically posting content to a live authenticated XHS account, but the skill description does not prominently warn users that execution may cause immediate public actions. In an account-automation context, lack of a clear live-posting warning materially increases the risk of accidental publication and reputational harm.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The title and all user-facing instructions are written in Chinese, and the workflow is specifically framed around publishing to 小红书 without any indication that users may choose another language or locale. Under the policy, a skill that imposes a specific language/locale without opt-in should be flagged unless the restriction is explicitly justified.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The CLI exposes a --delete-profile option that permanently removes the Chrome profile directory via shutil.rmtree, which can delete cookies and local browser data for that account. Although the flag name is somewhat descriptive, there is no stronger user-facing warning, confirmation prompt, or explicit notice at execution time about the irreversible data loss.

Content

No source excerpt is available for this finding.

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/cdp_publish.py (reported line 493)May include surrounding context.

python
return pages[0]["webSocketDebuggerUrl"]

        # Create a new tab
        resp = requests.put(
            f"http://{self.host}:{self.port}/json/new?{XHS_CREATOR_URL}",
            timeout=5,
        )

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code fetches notification mentions using the logged-in browser session and prints the resulting payload, which may expose private account notifications, usernames, and interaction metadata to downstream logs or calling systems. In a skill marketed for publishing, silent retrieval and output of account notifications is especially risky because users may not expect private inbox-like data to be collected.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The code contains like/collect engagement actions unrelated to the stated purpose of publishing to Xiaohongshu. Even if not currently wired into the CLI, retaining hidden account-interaction primitives creates unnecessary capability for unauthorized engagement, reputation manipulation, or later abuse if exposed through other code paths.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The fill command is described as non-publishing, but in video mode it still performs a real file upload submission to the platform. This mismatch can cause unintended external side effects, consume account/platform resources, and violate user expectations about what is a safe dry-run operation.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/chrome_launcher.py (reported line 155)May include surrounding context.

python
print(f"  profile dir: {user_data_dir}")
    print(f"  debug port : {port}")

    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
70% confidence
Finding

Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Content

Scanner excerpt · scripts/chrome_launcher.py (reported line 192)May include surrounding context.

python
# Strategy 1: CDP Browser.close
    try:
        import requests
        resp = requests.get(f"http://127.0.0.1:{port}/json/version", timeout=2)
        if resp.ok:
            ws_url = resp.json().get("webSocketDebuggerUrl")
            if ws_url:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/chrome_launcher.py (reported line 227)May include surrounding context.

python
# Strategy 3: Windows taskkill by port (fallback)
    if sys.platform == "win32" and is_port_open(port):
        try:
            result = subprocess.run(
                ["netstat", "-ano"],
                capture_output=True, text=True, timeout=5
            )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

The code kills whatever process is listening on the configured port by parsing netstat output and invoking taskkill on the discovered PID, without verifying that the process is actually the Chrome instance launched by this tool. If the port is attacker-influenced or occupied by another legitimate local service, this can terminate unrelated processes and cause local denial of service.

Content

Scanner excerpt · scripts/chrome_launcher.py (reported line 234)May include surrounding context.

python
for line in result.stdout.splitlines():
                if f":{port}" in line and "LISTENING" in line:
                    pid = line.strip().split()[-1]
                    subprocess.run(
                        ["taskkill", "/F", "/PID", pid],
                        capture_output=True, timeout=5
                    )

Tainted flow: 'filepath' from requests.get (line 140, network input) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/image_downloader.py (reported line 107)May include surrounding context.

python
filename = f"{uuid.uuid4().hex[:12]}{ext}"
        filepath = os.path.join(self.temp_dir, filename)

        with open(filepath, "wb") as f:
            for chunk in resp.iter_content(chunk_size=8192):
                f.write(chunk)

Static analysis

No suspicious patterns detected.