Back to skill

Security audit

Huo15 Xiaohongshu

Security checks for vulnerabilities and agentic risk

Overview

The skill is mainly a Xiaohongshu content assistant, but it asks for sensitive account/browser access and contains unsafe credential and browser-session handling that needs review before use.

Review this skill carefully before installing. Use it only if you are comfortable giving it access to your own Xiaohongshu cookie or authenticated browser profile, avoid storing or sharing xsec_token values, do not follow the CLAUDE.md publishing-credential path, and prefer an isolated environment with pinned dependencies. The content-writing features are coherent, but the browser bridge and credential handling should be tightened before routine use.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
CLAUDE.md:152
Finding
Project Instructions Direct the Agent to Private Credential Memory<![CDATA[ ## Vulnerability Details **File Location**: `CLAUDE.md:152` **Vulnerability Type**: Least-privilege violation through access to private credential storage **Risk Level**: High ### Vulnerable Code ```markdown **发布凭据**:见 `~/CLAUDE.md` §2 或 `~/.claude/projects/-Users-jobzhao/memory/publish_credentials.md`。 ``` English translation: “For publishing credentials, see section 2 of `~/CLAUDE.md` or `~/.claude/projects/-Users-jobzhao/memory/publish_credentials.md`.” ### Technical Analysis The project-level agent instructions explicitly direct the agent to read credentials from files outside the project directory. One location is a global agent instruction file, while the other is a persistent agent-memory file containing publishing credentials. Access to these files is not required for the Skill’s declared content-writing, analysis, or coaching functions. The instruction therefore crosses the minimum-permission boundary and creates a path through which a project can cause an agent to inspect or use unrelated private state. The referenced development workflow also includes repository pushes and Skill publication. Consequently, an agent following the instructions could retrieve reusable publication credentials and use them for operations beyond ordinary content assistance. ### Attack Path 1. An agent loads the project and treats `CLAUDE.md` as project instructions. 2. The agent is asked to perform or troubleshoot the documented publication workflow. 3. The instruction at line 152 directs the agent outside the project root. 4. The agent reads `~/CLAUDE.md` or the persistent memory file. 5. Reusable publication credentials may be exposed in agent context or used to publish or modify artifacts. 6. If task input or later instructions are attacker-controlled, the credentials could be disclosed or used for unauthorized repository or package operations. ### Impact Assessment Potential impact includes: - Disclosure of reusable repository or Skill-publication ...[truncated 446 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove all instructions that tell the agent to inspect global configuration or persistent memory for credentials. 2. Require publishing credentials through a narrowly scoped environment variable, operating-system credential manager, or authenticated publication tool. 3. Explicitly state that the Skill must not read files outside its project and documented application-data directories. 4. Separate development and publication instructions from runtime Skill instructions. 5. Use short-lived, least-privileged publication tokens rather than reusable credentials. 6. Require an explicit user confirmation immediately before any repository push or publication operation. 7. Add automated checks that reject package documentation containing references to agent-memory paths, home-directory credential files, or unrelated global instructions. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/browser_bridge.py:585
Finding
Search Input Is Injected into JavaScript Executed in an Authenticated Browser<![CDATA[ ## Vulnerability Details **File Location**: `scripts/browser_bridge.py:585-627` **Vulnerability Type**: JavaScript injection through unsafe CDP expression construction **Risk Level**: High ### Vulnerable Code ```python def cmd_search(keyword: str): """搜索笔记。""" _human_delay() print(f"搜索: {keyword}") # 导航到搜索页 import urllib.parse search_url = f"https://www.xiaohongshu.com/search_result?keyword={urllib.parse.quote(keyword)}" _cdp_request(f"/json/new?{search_url}", "PUT") time.sleep(4) result = _cdp_execute(""" JSON.stringify((function() { var notes = []; var links = document.querySelectorAll('a[href*=\"/explore/\"]'); var seen = {}; for (var i = 0; i < links.length; i++) { var a = links[i]; var text = a.innerText.trim(); var href = a.href; if (text.length > 4 && text.length < 200 && !seen[href]) { seen[href] = true; var parent = a.closest('section, div[class*=note], div[class*=card]'); var likes = ''; var author = ''; if (parent) { var likeEl = parent.querySelector('[class*=like], [class*=count]'); if (likeEl) likes = likeEl.innerText.trim(); var authorEl = parent.querySelector('[class*=author], [class*=name], [class*=nickname]'); if (authorEl) author = authorEl.innerText.trim(); } notes.push({title: text.substring(0, 100), url: href, likes: likes, author: author}); } } return {notes: notes.slice(0, 20), keyword: '""" + keyword + """'}; })()) """) ``` The resulting expression is passed to Chrome DevTools Protocol: ```python ws.send(json.dumps({ "id": 1, "method": "Runtime.evaluate", "params": {"expres ...[truncated 2206 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never concatenate untrusted data into JavaScript source. 2. Serialize the value as a JavaScript-safe literal: ```python keyword_js = json.dumps(keyword, ensure_ascii=False) expression = f""" JSON.stringify((function() {{ // ... return {{notes: notes.slice(0, 20), keyword: {keyword_js}}}; }})()) """ ``` 3. Prefer removing the keyword from the evaluated expression entirely if it is only needed for output labeling. 4. Validate the keyword’s type and impose a reasonable maximum length. 5. Add regression tests containing quotes, backslashes, line terminators, template-literal characters, and Unicode separators. 6. Run browser extraction in a separate non-authenticated or minimally privileged profile whenever authentication is unnecessary. 7. Restrict CDP operations to fixed, reviewed expressions rather than expressions assembled from command-line input. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/browser_bridge.py:317
Finding
Persistent Authenticated Chrome Is Launched with an Overly Permissive CDP Configuration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/browser_bridge.py:317-365` **Vulnerability Type**: Unsafe browser-debugging configuration and broad process termination **Risk Level**: Medium ### Vulnerable Code ```python def cmd_start(): """启动 CDP Chrome 并打开小红书。""" _check_circuit_breaker() _check_nighttime() os.makedirs(CHROME_PROFILE, exist_ok=True) # 检查是否已运行 try: ver = _cdp_request("/json/version") print(f"CDP Chrome 已在运行 ({ver.get('Browser', '?')})。") _cdp_request("/json/new?https://www.xiaohongshu.com/explore", "PUT") print("已打开小红书探索页。") return except Exception: pass # 关闭旧 CDP 进程(不影响用户正常 Chrome) subprocess.run(["pkill", "-f", "Google Chrome.*remote-debugging"], capture_output=True) time.sleep(2) subprocess.Popen([ CHROME_BIN, f"--remote-debugging-port={CDP_PORT}", "--remote-allow-origins=*", f"--user-data-dir={CHROME_PROFILE}", "--no-first-run", "--no-default-browser-check", "https://www.xiaohongshu.com/explore", ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) time.sleep(5) try: ver = _cdp_request("/json/version") print(f"CDP Chrome 已启动 ({ver.get('Browser', '?')})。") except Exception: print("启动失败,请手动尝试。") print(f"手动启动命令: \"{CHROME_BIN}\" --remote-debugging-port={CDP_PORT} --remote-allow-origins='*' --user-data-dir=\"{CHROME_PROFILE}\" https://www.xiaohongshu.com/explore") sys.exit(1) def cmd_stop(): """关闭 CDP Chrome。""" subprocess.run(["pkill", "-f", "Google Chrome.*remote-debugging"], capture_output=True) print("CDP Chrome 已关闭。") ``` Related configuration: ```python CDP_PORT = 9222 CDP_HOST = "127.0.0.1" CHROME_PROFILE = os.path.expanduser("~/.claude/chrome-xhs-profile") ``` ### Technical Analysis The browser uses a fixed debugging port and a persistent profile containing the user’s Xiaohongshu login state. ...[truncated 1669 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `--remote-allow-origins=*`. If an origin exception is unavoidable, allow only the exact trusted origin. 2. Allocate a random available loopback port for each run rather than using fixed port 9222. 3. Verify that the CDP endpoint belongs to the process started by this application before connecting. 4. Record the `subprocess.Popen` PID in a protected state file and terminate only that PID. 5. Use a dedicated temporary or minimally persistent browser profile. Store only the authentication state required by the declared task. 6. Restrict the profile directory to owner-only permissions. 7. Shut down the debugging listener as soon as the requested operation completes. 8. Consider an authenticated intermediary that exposes only fixed read-only operations instead of raw CDP. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/track_post.py:60
Finding
Reusable Xiaohongshu Access Tokens Are Persisted in Plaintext<![CDATA[ ## Vulnerability Details **File Location**: `scripts/track_post.py:60-116` **Vulnerability Type**: Plaintext storage of sensitive session-related tokens **Risk Level**: Medium ### Vulnerable Code ```python DEFAULT_LOG = "~/.xiaohongshu/posts.jsonl" DEFAULT_SNAPSHOTS = "~/.xiaohongshu/snapshots.jsonl" def _append_jsonl(path: str, entry: Dict[str, Any]) -> None: p = Path(os.path.expanduser(path)) p.parent.mkdir(parents=True, exist_ok=True) with p.open("a", encoding="utf-8") as f: f.write(json.dumps(entry, ensure_ascii=False) + "\n") def _rewrite_jsonl(path: str, entries: List[Dict[str, Any]]) -> None: p = Path(os.path.expanduser(path)) p.parent.mkdir(parents=True, exist_ok=True) with p.open("w", encoding="utf-8") as f: for e in entries: f.write(json.dumps(e, ensure_ascii=False) + "\n") def cmd_register(args: argparse.Namespace) -> int: posts = _read_jsonl(args.log) found = False for p in posts: if p.get("post_uid") == args.uid: p["note_id"] = args.note_id p["xsec_token"] = args.xsec_token or "" p["published_at"] = dt.datetime.now().isoformat(timespec="seconds") found = True break if not found: print(f"❌ 没找到 post_uid={args.uid},请先用 publish_helper.py 准备并 --log", file=sys.stderr) return 1 _rewrite_jsonl(args.log, posts) print(f"✓ 已关联 note_id={args.note_id}") return 0 ``` The stored token is subsequently reused: ```python rc = _do_snapshot( client, p["note_id"], p.get("xsec_token", ""), args.snapshots ) ``` ### Technical Analysis The `xsec_token` value is written directly into `~/.xiaohongshu/posts.jsonl`. The code uses normal file creation and does not explicitly set owner-only permissions, encrypt the value, redact it from records, or remove it after use. Although an `xsec_token` is not equivalent to the full `XHS_COOKIE`, it is a reusable request parameter associa ...[truncated 1162 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not persist `xsec_token` unless continued storage is strictly required. 2. Request the token at execution time or derive it from a user-supplied URL held only in memory. 3. If persistence is unavoidable, store the token in the operating system’s credential manager and retain only a reference identifier in JSONL. 4. Create sensitive files with owner-only permissions, such as mode `0600`, independent of the user’s umask. 5. Separate sensitive token data from ordinary analytics and post-history logs. 6. Add automatic expiration and deletion based on the expected token lifetime. 7. Redact tokens in diagnostic output, exported reports, backups, and exceptions. 8. Document the sensitivity, scope, and retention policy for all stored account-related values. ]]>

T08 · Insecure Dependencies

Warning
Location
README.md:16
Finding
Runtime Dependencies Are Unpinned and the Browser Dependency Is Incompletely Declared<![CDATA[ ## Vulnerability Details **File Location**: `README.md:16-21`, `SKILL.md:13-20`, and `scripts/browser_bridge.py:249-250` **Vulnerability Type**: Unpinned and incomplete third-party dependency specification **Risk Level**: Medium ### Vulnerable Code Installation instructions in `README.md`: ```markdown ### 1.1 安装依赖 ```bash pip install requests pip install jieba pandas anthropic # 全部可选 ``` ``` Declared dependencies in `SKILL.md`: ```yaml dependencies: python-packages: - requests - jieba # 可选 - pandas # 可选 - anthropic # 可选 — LLM 增强 ``` Undeclared browser dependency in `scripts/browser_bridge.py`: ```python def _cdp_execute(expression: str, tab_url_contains: str = "xiaohongshu", timeout: int = 15, scroll: bool = True) -> dict: """在匹配的标签页中执行 JavaScript 并返回结果。含风控检测 + 拟人滚动。""" import websocket ``` The project changelog identifies the intended package as `websocket-client`, but it is absent from the primary dependency declaration and installation instructions. ### Technical Analysis All documented package installations resolve the latest available package versions at installation time. There is no lockfile, exact version constraint, integrity hash, or reviewed package index. This makes installations non-reproducible and allows future dependency releases to alter the effective code executed by the Skill. The browser bridge imports the module named `websocket`, while the intended distribution is `websocket-client`. Because the package is not declared in the normal dependency list, users may guess the package name and install a similarly named or unintended package. This creates a dependency-confusion and package-selection risk. No evidence was found that the currently named packages are themselves malicious. The vulnerability is the unsafe and incomplete dependency-management process. ### Attack Path 1. A user follows the README and runs unpinned `pip install` commands. 2. The resolver downloads whatever ...[truncated 943 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add a dependency lockfile containing exact versions and cryptographic hashes. 2. Explicitly declare `websocket-client` as the distribution providing the `websocket` module. 3. Keep optional dependencies in named extras so each feature’s requirements are unambiguous. 4. Install with an isolated virtual environment and `python -m pip`. 5. Use hash-enforced installation, for example through a generated requirements file and `--require-hashes`. 6. Review direct and transitive dependencies with a software-composition analysis tool. 7. Define supported version ranges only when necessary, while deploying from a reviewed lockfile. 8. Add CI tests that build the project in a clean environment using only declared dependencies. 9. Avoid instructing users to infer package names from Python import names. ]]>
Vulnerability Patterns
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (194)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
Structured extraction of notes, user profiles, search previews, and media metadata from raw HTML demonstrates a generalized data-extraction pipeline. That creates clear dual-use potential for harvesting and repurposing platform data beyond the declared writing-assistance function.

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/assistant.py preset allen
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Static analysis

No suspicious patterns detected.