Back to skill

Security audit

Douban Self Taste Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is purpose-aligned, but it needs review because it uses login cookies and saves personal Douban history locally without strong safeguards.

Install only if you are comfortable giving the skill a Douban session cookie and storing your ratings, comments, shelves, and analysis files locally. Use a dedicated Douban-only cookie export, keep the .local/douban-self-taste directory private, do not commit or sync it, and delete cookies/cache files when no longer needed.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/crawl_douban_self_history.py:300
Finding
Unrestricted Request Destinations Combined with Unvalidated Cookie Domains<![CDATA[ ## Vulnerability Details **File Location**: `scripts/crawl_douban_self_history.py:131-137, 261-281, 300-310` **Vulnerability Type**: Unvalidated outbound destinations and cookie scope **Risk Level**: Medium ### Vulnerable Code ```python def parse_next_page_url(html: str, current_url: str) -> str | None: soup = BeautifulSoup(html, "lxml") next_el = soup.select_one("span.next a") or soup.select_one("a.next") if not next_el: return None href = next_el.get("href", "").strip() return urljoin(current_url, href) if href else None ``` ```python def crawl_category(client: httpx.Client, uid: str, category: str, interval: float, page_limit: int | None) -> dict[str, Any]: items: list[Item] = [] page_count = 0 for status in STATUSES: next_url = build_start_url(uid, category, status) while next_url: page_count += 1 resp = client.get(next_url) resp.raise_for_status() html = resp.text if looks_like_login_or_auth_problem(html, str(resp.url)): raise RuntimeError(f"Authentication failed while fetching {resp.url}") source_name = f"{category}-{status}-page-{page_count}.html" items.extend(parse_page(category, html, status, str(resp.url), source_name)) if page_limit and page_count >= page_limit: next_url = None else: next_url = parse_next_page_url(html, str(resp.url)) if next_url: time.sleep(interval) ``` ```python def make_client(cookie_file: Path, timeout: float) -> httpx.Client: raw = load_cookies(cookie_file) client = httpx.Client(timeout=timeout, follow_redirects=True, headers={"User-Agent": "Mozilla/5.0 OpenClaw DoubanSelfTasteSkill"}) for item in raw: name = item.get("name") value = item.get("value") domain = item.get("domain") path = item.get("path", "/") if not name or value ...[truncated 2634 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict imported cookies to an explicit domain allowlist, such as: - `douban.com` - `.douban.com` - the exact required Douban subdomains 2. Reject cookies with missing, malformed, unrelated, or public-suffix domains. 3. Permit only HTTPS requests to an explicit hostname allowlist, for example: - `www.douban.com` - `movie.douban.com` - `book.douban.com` - `music.douban.com` - required Douban authentication hosts, if strictly necessary 4. Disable automatic redirects with `follow_redirects=False`. Validate each `Location` header before following it. 5. Validate every pagination URL after `urljoin` and before `client.get()`: ```python ALLOWED_HOSTS = { "www.douban.com", "movie.douban.com", "book.douban.com", "music.douban.com", } def validate_url(url: str) -> str: parsed = urlparse(url) if parsed.scheme != "https" or parsed.hostname not in ALLOWED_HOSTS: raise RuntimeError(f"Refusing unexpected outbound URL: {url}") return url ``` 6. Prefer a dedicated cookie jar containing only the minimum Douban cookies required for authentication. 7. Document that users must not provide an unrestricted whole-browser cookie export. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/crawl_douban_self_history.py:288
Finding
Sensitive Cookies and Private Profile History Lack Local Permission Hardening<![CDATA[ ## Vulnerability Details **File Location**: `scripts/crawl_douban_self_history.py:288-292, 342-344`; `references/storage-layout.md:5-19`; `SKILL.md:24-28, 61-68` **Vulnerability Type**: Plaintext sensitive-data storage without explicit access controls **Risk Level**: Low ### Vulnerable Code and Configuration ```python def ensure_dirs() -> None: (BASE_DIR / "cookies").mkdir(parents=True, exist_ok=True) CACHE_DIR.mkdir(parents=True, exist_ok=True) (BASE_DIR / "analysis").mkdir(parents=True, exist_ok=True) ``` ```python data = crawl_category(client, args.uid, category, args.interval, args.page_limit) out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") results[category] = data ``` The documented cookie storage format is plaintext JSON: ```markdown Store Douban cookies at: `.local/douban-self-taste/cookies/douban_cookies.json` Expected format: a JSON array of browser-style cookie objects. ``` The persisted cache intentionally includes private behavioral fields such as: ```json { "status": "done", "rating": 5, "date": "2024-08-03", "comment": "Short, but incisive." } ``` ### Technical Analysis The Skill requires reusable authentication cookies to be stored in a predictable local path and writes account history, ratings, dates, tags, and comments to plaintext JSON cache files. Neither the implementation nor its documentation enforces restrictive filesystem permissions. Directories created with `Path.mkdir()` and files created with `Path.write_text()` inherit permissions from the process environment and current umask. On a system with permissive defaults, other local users or processes may be able to read the data. The project also provides no retention, secure deletion, backup-exclusion, or repository-exclusion guidance. Plaintext cache storage is consistent with the declared functionality, but retaining authentication material and pri ...[truncated 1308 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create sensitive directories with owner-only permissions: ```python cookie_dir = BASE_DIR / "cookies" cookie_dir.mkdir(parents=True, exist_ok=True, mode=0o700) cookie_dir.chmod(0o700) CACHE_DIR.mkdir(parents=True, exist_ok=True, mode=0o700) CACHE_DIR.chmod(0o700) ``` 2. Create cookie and cache files with mode `0600`. Use low-level creation flags or an atomic temporary-file workflow that establishes restrictive permissions before writing sensitive content. 3. Verify existing file permissions before reading cookies. Refuse or warn when a cookie file is readable by group or other users. 4. Add `.local/douban-self-taste/` to `.gitignore` and document that cookie and cache files must never be committed. 5. Recommend an operating-system credential store or encrypted secret storage for session cookies rather than persistent plaintext JSON. 6. Add documented retention and deletion controls for: - expired cookies, - stale collection caches, - generated profile summaries. 7. Warn users that cache files contain private comments and behavioral history and may be included in backups or workspace synchronization. 8. Avoid recording unnecessary sensitive metadata in cache output. For example, omit the cookie-file path if it is not needed by downstream analysis. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The code is clearly an offline analysis script, not a collector/refresher. Its primary function is to build a taste-profile summary from existing normalized Douban JSON files. It does analyze ratings, tags, comments, creators, years, and recent activity by category, which partially matches the declared analysis portion. However, the declared description prominently includes capabilities for collecting, refreshing, normalizing, checking cache freshness, re-crawling logged-in data with cookies, and storing refreshed results locally. None of those behaviors appear in the code: there is no network access, authentication, cookie use, crawling logic, cache inspection, or write-to-disk persistence beyond printing output. Because these are substantial declared capabilities and resource expectations absent from the implementation, the description does not accurately represent what this code chunk actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The code is a local extraction/normalization utility for previously saved self-history HTML pages. Its docstring explicitly says it does not crawl the public web by itself. Functionally, it reads HTML files from disk, parses collection entries across several Douban media types, infers some metadata, deduplicates records, and emits normalized JSON. This matches only a narrow subset of the declared description ('normalize' and user's own Douban history), but materially omits major declared behaviors such as refresh logic, cache validation, cookie-based re-crawling, local persistence of refreshed data, and analytical/recommendation use. Therefore the declared description overstates the implemented capability enough to be a mismatch.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill directs the agent to read and write local files, use cookies, and perform network crawling, but it declares no explicit tool/permission scope. That creates an authorization ambiguity where an agent may overreach or execute sensitive actions without a clear least-privilege contract, especially given the use of authenticated session cookies and local persistence.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly instructs use of logged-in cookies and storage of account-derived Douban data on disk, but it does not require a user-facing privacy warning, consent checkpoint, retention policy, or handling guidance for sensitive session material. This is dangerous because cookies can grant account access and cached personal media history may reveal sensitive preferences or personal data if stored insecurely or reused unexpectedly.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The schema explicitly records a local path to a cookie file used for logged-in crawling, which normalizes handling of authentication material without any accompanying warning, minimization guidance, or protection requirements. In this skill's context, that is more dangerous because the workflow is specifically about reusing logged-in session data to scrape private account history, so weak treatment of cookie artifacts can lead to session theft, unauthorized account access, or accidental exposure through logs, caches, or downstream tools.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The file explicitly instructs storing live Douban authentication cookies in a predictable local JSON path, but provides no safeguards such as restrictive file permissions, encryption, lifecycle limits, or alternatives to persistent storage. In this skill's context, those cookies enable logged-in scraping of the user's private account data, so theft or accidental exposure of the file could allow session hijacking and unauthorized access to personal Douban history.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
This code emits natural-language preference hints only in Chinese via fixed string literals such as "高频标签" and "重复出现的创作者". Because the script provides no user opt-in, locale selection, or justification for a Chinese-only output policy, it creates a language/locale policy concern.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Content
import httpx
from bs4 import BeautifulSoup

RATING_RE = __import__("re").compile(r"(?:rating|allstar)(\d)(?:0)?(?:-t)?")
DATE_RE = __import__("re").compile(r"\d{4}-\d{2}-\d{2}")
TITLE_COUNT_RE = __import__("re").compile(r"\((\d+)\)")
Confidence
75% confidence
Finding
Dynamic __import__() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Content
from bs4 import BeautifulSoup

RATING_RE = __import__("re").compile(r"(?:rating|allstar)(\d)(?:0)?(?:-t)?")
DATE_RE = __import__("re").compile(r"\d{4}-\d{2}-\d{2}")
TITLE_COUNT_RE = __import__("re").compile(r"\((\d+)\)")

BASE_DIR = Path(".local/douban-self-taste")
Confidence
75% confidence
Finding
Dynamic __import__() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Content
RATING_RE = __import__("re").compile(r"(?:rating|allstar)(\d)(?:0)?(?:-t)?")
DATE_RE = __import__("re").compile(r"\d{4}-\d{2}-\d{2}")
TITLE_COUNT_RE = __import__("re").compile(r"\((\d+)\)")

BASE_DIR = Path(".local/douban-self-taste")
COOKIE_FILE = BASE_DIR / "cookies" / "douban_cookies.json"
Confidence
75% confidence
Finding
Dynamic __import__() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Missing User Warnings

Low
Confidence
83% confidence
Finding
This code outputs the user's normalized Douban history, including comments, tags, dates, and source filenames, directly as JSON. While the module docstring explains the input scope, there is no runtime disclosure or warning that the output may contain personal reading/viewing history and should be redirected or handled carefully.

Static analysis

No suspicious patterns detected.