Back to skill

Security audit

Rednote Mac

Security checks for vulnerabilities and agentic risk

Overview

This skill is a real RedNote automation tool, but it gets Review because it can take live social-account actions and capture private UI content with limited built-in confirmation or scoping.

Install only if you are comfortable giving an agent Accessibility-backed control of a live RedNote account. Use a dedicated macOS user or test account, keep the app supervised, require your own confirmation before any DM/comment/delete/follow action, avoid screenshot/OCR on private conversations unless necessary, and do not use the video-download helper unless the publisher clearly documents and scopes network behavior.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

other

Warning
Location
xhs_controller.py:1505
Finding
Undeclared Network-Capable Video Download Functionality<![CDATA[ ## Vulnerability Details **File Location**: `xhs_controller.py:1505-1531` and `xhs_controller.py:1599-1614` **Vulnerability Type**: Undeclared network-capable functionality **Risk Level**: Medium ### Vulnerable Code ```python def download_note_video(url: str, output_dir: str = "/tmp/xhs_video") -> dict: """Use yt-dlp to download a Xiaohongshu note video.""" import os, glob, json as _json result = {"success": False, "video_path": None, "title": None, "error": None} try: os.makedirs(output_dir, exist_ok=True) ytdlp = "/opt/homebrew/bin/yt-dlp" if not os.path.exists(ytdlp): import shutil ytdlp = shutil.which("yt-dlp") or "yt-dlp" cmd = [ytdlp, "-o", f"{output_dir}/%(id)s.%(ext)s", "--write-info-json", "--no-playlist", url] r = subprocess.run(cmd, capture_output=True, text=True, timeout=120) for ext in ("mp4", "mov", "webm", "flv", "m4v"): files = glob.glob(f"{output_dir}/*.{ext}") if files: result["video_path"] = files[-1] break json_files = glob.glob(f"{output_dir}/*.info.json") if json_files: with open(json_files[-1]) as f: info = _json.load(f) result["title"] = info.get("title", "") result["success"] = result["video_path"] is not None if not result["success"]: result["error"] = r.stderr[-500:] if r.stderr else "Video file not found" except Exception as e: result["error"] = str(e) return result ``` ```python def extract_current_note_video(output_dir: str = "/tmp/xhs_video") -> dict: """Extract the current note video by obtaining its URL, downloading it, and extracting frames.""" result = {"url": "", "success": False, "video_path": None, "frames": [], "video_info": {}, "error": None} try: url = get_note_url() result["url"] = url if not url: ...[truncated 2750 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the video-download and extraction functions if they are not part of the declared Skill functionality. 2. If downloading is intended, document the network behavior and expose it only through a distinct, user-consented tool. 3. Accept only `https` URLs and validate the normalized hostname against an explicit allowlist of expected RedNote domains. 4. Reject redirects to non-allowlisted hosts where technically possible. 5. Use a private directory created with `tempfile.TemporaryDirectory()` rather than a shared, predictable `/tmp` path. 6. Apply file-size, download-time, media-duration, and disk-usage limits. 7. Run media parsers with reduced privileges or sandboxing when processing remotely supplied files. 8. Return clear network and file-system effects to the user before initiating the download. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
server.py:33
Finding
Unsafe Temporary File Creation for Sensitive Screenshots<![CDATA[ ## Vulnerability Details **File Location**: `server.py:33-48` **Vulnerability Type**: Insecure temporary file creation **Risk Level**: Medium ### Vulnerable Code ```python def _screenshot_b64() -> str | None: """Capture a screenshot and return it as base64, or None on failure.""" import tempfile, os path = tempfile.mktemp(suffix=".png") try: xhs.screenshot(path) with open(path, "rb") as f: return base64.b64encode(f.read()).decode() except Exception: return None finally: try: os.unlink(path) except Exception: pass ``` ### Technical Analysis `tempfile.mktemp()` generates a pathname but does not atomically create or reserve the corresponding file. Between pathname generation and the calls to `screencapture` and `open()`, another local process operating as the same user can create, replace, or redirect that path. The captured RedNote window may contain direct messages, usernames, profile information, or other private content. The function subsequently reads the pathname and returns its contents as MCP image data without verifying that it is a regular file, that it is owned by the current user, or that it is the file created by the screenshot process. Base64 encoding itself is appropriate for the MCP image transport and is not evidence of covert exfiltration. The vulnerability arises from the unsafe temporary-file lifecycle, not the encoding. ### Attack Path 1. The server generates a temporary pathname with `tempfile.mktemp()`. 2. A malicious local process monitoring the temporary directory identifies or races the generated name. 3. The attacker creates a symlink or substitutes a file at that pathname before screenshot creation or reading. 4. The screenshot utility writes through the attacker-controlled path, or the server reads attacker-substituted content. 5. The server returns the manipulated data as an image to the invoking agent, or an unintended ...[truncated 772 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace `tempfile.mktemp()` with `tempfile.mkstemp()` or `NamedTemporaryFile(delete=False)`. 2. Create the file atomically with restrictive permissions and close its descriptor before invoking `screencapture`. 3. Before reading it, use `os.lstat()` to verify that the path is a regular file rather than a symbolic link. 4. Confirm that the file is owned by the current effective user. 5. Place temporary screenshots in a private directory created with `tempfile.TemporaryDirectory()`. 6. Preserve cleanup in a `finally` block and explicitly handle failures from `xhs.screenshot()`. 7. Consider capturing into a securely created file descriptor or in-memory buffer if the platform utility supports it. ]]>

T08 · Insecure Dependencies

Note
Location
install.sh:22
Finding
Unpinned Third-Party Dependencies Installed with Accessibility-Enabled Privileges<![CDATA[ ## Vulnerability Details **File Location**: `install.sh:22-45`, `pyproject.toml:6-11`, and `requirements.txt:3-7` **Vulnerability Type**: Insecure dependency management **Risk Level**: Low ### Vulnerable Code ```bash # 1. Check cliclick echo "" echo "[1/4] Checking cliclick..." CLICLICK_PATH="/opt/homebrew/bin/cliclick" if [ -x "$CLICLICK_PATH" ]; then echo " ✓ Found at $CLICLICK_PATH" else echo " Not found. Installing via Homebrew..." if command -v brew &>/dev/null; then brew install cliclick else echo " Homebrew not found. Install cliclick manually." exit 1 fi fi # 2. Install Python deps echo "" echo "[2/4] Installing Python dependencies..." if command -v uv &>/dev/null; then cd "$SKILL_DIR" && uv sync else pip install "atomacos>=3.3.0" \ "pyobjc-framework-Quartz>=12.1" \ "pyobjc-framework-ApplicationServices>=12.1" \ "mcp>=1.26.0" fi ``` ```toml dependencies = [ "atomacos>=3.3.0", "pyobjc-framework-quartz>=12.1", "pyobjc-framework-applicationservices>=12.1", "mcp>=1.26.0", ] ``` ```text atomacos>=3.3.0 pyobjc-framework-Quartz>=12.1 pyobjc-framework-ApplicationServices>=12.1 mcp>=1.26.0 ``` ### Technical Analysis The installation process retrieves `cliclick` through Homebrew and Python packages through `uv` or `pip`. Python dependencies use open-ended minimum-version constraints, and the reviewed project structure does not include a lockfile or integrity hashes. The dependency names are internally consistent, and no suspicious custom package index or typosquatted package was identified. The risk is that future package versions are accepted without project review, making installation non-reproducible and exposing users to compromised registry releases, compromised maintainer accounts, or unexpectedly breaking dependency changes. This is particularly relevant because the installed packages are imported by a process operating with macOS Accessibility permissi ...[truncated 1319 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin dependencies to reviewed exact versions rather than open-ended minimum versions. 2. Commit and enforce a generated lockfile for the `uv` installation path. 3. Use hash verification for Python distributions where supported. 4. Ensure the fallback `pip` path consumes the same locked dependency set instead of resolving independently. 5. Pin or otherwise document a reviewed Homebrew formula version for `cliclick`. 6. Add a controlled dependency-update process that reviews changelogs, package ownership, integrity, and transitive dependency changes. 7. Perform installation before granting Terminal Accessibility permission whenever possible. 8. Document the trusted package registries and avoid implicit custom indexes inherited from user environment configuration. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (50)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description presents a limited-purpose RedNote controller, but the observed behavior is materially broader: screenshot capture, clipboard access, URL extraction, media downloading/processing, and additional account actions. This mismatch is dangerous because users and policy systems may grant Accessibility and local execution trust based on an incomplete description, while the skill can access sensitive on-screen content, manipulate user state, and exfiltrate or process data beyond expected comment/DM workflows.

Memory Manipulation

High
Category
Memory Poisoning
Content
**Root cause:** The search bar AX element only exists reliably on the home/discover screen. Inside a note detail page, the search bar isn't present.

**Fix — always reset context before searching:**
```
xhs_back()
xhs_back()
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Missing User Warnings

High
Confidence
98% confidence
Finding
The skill can send direct messages automatically with no user-facing warning, preview, or confirmation. In a social-media automation context, this can be abused to impersonate the user, spam contacts, or send sensitive information to third parties with a single tool call.

Missing User Warnings

High
Confidence
98% confidence
Finding
Automated comment posting performs an externally visible action on behalf of the user without confirmation. Because comments are public or semi-public user communications, this can damage accounts, spread spam, or create reputational and compliance issues if triggered unintentionally or by a malicious prompt chain.

Missing User Warnings

High
Confidence
99% confidence
Finding
Comment deletion is a destructive user action with no confirmation at the skill layer. In a UI automation tool, mis-targeting or malicious invocation could silently delete user-generated content and there may be little recourse if the platform does not support recovery.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The file adds video URL extraction, downloading, frame extraction, and metadata parsing, which exceeds the manifest's stated purpose of controlling the RedNote Mac app via Accessibility APIs. This scope drift is dangerous because users or integrators may grant permissions expecting only local GUI automation, while the code also performs content acquisition and processing with external tools.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
Using `yt-dlp` introduces a network-capable external downloader unrelated to the declared Accessibility-control purpose. This materially increases the attack surface, enables bulk content retrieval, and can surprise users who did not consent to outbound requests or local media storage.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill metadata does include an OS/app/permission declaration, but it does not clearly scope higher-risk capabilities such as shell execution and file access that are implied by the setup and referenced tooling. In a skill that requires Accessibility permission and can drive a desktop app, undeclared execution and file interaction increase the chance of reviewers underestimating its effective privileges and approving overly broad automation.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This documentation describes a tool that can send real direct messages to actual contacts but does not clearly warn users that the action is externally visible, irreversible in practice, and may affect real accounts. In an accessibility-driven native-app automation context, the risk is higher because there is no API sandbox or dry-run boundary, so an agent or user can easily message the wrong person if identity verification is skipped.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The documented workaround instructs users to capture screenshots of comment panels and send them to an OCR/image-analysis tool, but it does not warn that screenshots may contain sensitive personal data such as usernames, profile details, private messages, or comment content. In the context of a desktop automation skill, this increases the risk of unintended collection, retention, or onward transmission of third-party content.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The guidance recommends disabling the screensaver and preventing sleep to keep the app visible for automation, but it omits any warning that this weakens workstation privacy and physical security. On a shared or unattended Mac, this can expose unlocked application content and increase the chance of unauthorized viewing or misuse.

Session Persistence

Medium
Category
Rogue Agent
Content
**Fix:** Keep RedNote visible. For automated tasks:
```bash
caffeinate -di &         # prevent sleep
defaults write com.apple.screensaver idleTime 0   # disable screensaver
```

---
Confidence
90% confidence
Finding
The command modifies a persistent system preference (`com.apple.screensaver idleTime 0`) that can outlive the session and leave the machine without normal inactivity locking behavior. Because this is documented as an automation step without rollback guidance, users may unknowingly leave their workstation in a less secure state after the task completes.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The documentation recommends `xhs_follow_author()` as a navigation workaround even though the action may actually follow the target account, causing an unintended state-changing side effect. In a social-media automation skill that operates through macOS Accessibility without API-level safeguards, users may trigger real follows on live accounts while only intending to inspect profile stats.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The screenshot function captures the current RedNote app window and returns the image data without any explicit privacy notice, redaction, or consent checkpoint. Because the app may display private messages, profile data, or other sensitive content, this creates a risk of unintended exposure of personal or confidential information to the calling agent or downstream systems.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The skill exposes powerful account-interaction capabilities beyond passive reading, including posting comments, replying, deleting comments, opening DMs, and sending messages. In a macOS Accessibility-driven tool with no fine-grained permission checks or explicit confirmation gates, this materially increases the risk of unauthorized actions on a user's social account if the tool is invoked unexpectedly or misused by an agent.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The DM tools allow opening private conversations and sending messages with no explicit privacy warning, confirmation flow, or data-minimization controls. In the context of a social-media automation skill using Accessibility permissions, this is especially dangerous because it enables access to sensitive correspondence and account actions that could leak private data or impersonate the user.

Session Persistence

Medium
Category
Rogue Agent
Content
echo "This script will:"
echo "  1. Check for cliclick (required binary for mouse control)"
echo "  2. Install Python deps: atomacos, pyobjc-framework-Quartz, mcp"
echo "  3. Create symlink: ~/.openclaw/extensions/rednote-mac -> $SKILL_DIR"
echo "  4. Print the two commands needed to enable the plugin"
echo ""
read -p "Continue? [y/N] " confirm
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.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The helper returns a screenshot by default after many actions, which can capture private content from the RedNote UI such as DMs, profile data, comments, and other on-screen information. Because this occurs automatically and silently, sensitive user data may be exfiltrated to the calling client or model without clear user awareness or need-to-know limitation.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The server exposes capabilities substantially broader than the stated description: beyond reading/replying to comments, sending DMs, and getting stats, it can navigate, search, like, collect, follow, post comments, and delete comments. This is a scope/consent mismatch that can mislead users or orchestrators into granting a high-privilege automation skill more trust than intended, increasing the chance of unauthorized account actions.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill can send direct messages immediately from provided text without any secondary confirmation, preview, recipient verification, or warning that private communications are being transmitted. In an agent setting, this raises the risk of accidental, policy-violating, or socially harmful outbound messages being sent on the user's behalf.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def is_running() -> bool:
    r = subprocess.run(["pgrep", "-x", PROCESS_NAME], capture_output=True, text=True)
    return bool(r.stdout.strip())
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def is_running() -> bool:
    r = subprocess.run(["pgrep", "-x", PROCESS_NAME], capture_output=True, text=True)
    return bool(r.stdout.strip())
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def is_screen_locked() -> bool:
    r = subprocess.run(
        ["python3", "-c",
         "import Quartz; s=Quartz.CGSessionCopyCurrentDictionary(); "
         "print(s.get('CGSSessionScreenIsLocked', 0) or s.get('kCGSSessionScreenIsLocked', 0))"],
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""将 rednote 置前,确保屏幕已解锁且窗口可交互"""
    if is_screen_locked():
        raise RuntimeError("屏幕已锁定,无法操作。请先解锁 Mac。")
    subprocess.run(["open", "-a", "rednote"])
    time.sleep(1.5)
    subprocess.run(["osascript", "-e",
        'tell application "System Events" to tell process "discover" to set frontmost to true'])
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
raise RuntimeError("屏幕已锁定,无法操作。请先解锁 Mac。")
    subprocess.run(["open", "-a", "rednote"])
    time.sleep(1.5)
    subprocess.run(["osascript", "-e",
        'tell application "System Events" to tell process "discover" to set frontmost to true'])
    time.sleep(0.3)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.