Back to skill

Security audit

掘金技能集

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent for Juejin publishing and downloading, but it exposes sensitive session-cookie and file-write behavior beyond its declared limits, so users should review it carefully before installing.

Install only if you trust this publisher with your Juejin account session. Use it on a personal machine, avoid shared or CI environments, keep public publishing in draft-only mode unless intentionally confirming publication, and delete ~/.juejin_cookie.json when finished. Treat the lower-level Python API as unsafe for untrusted callers until URL validation, cookie-path constraints, redirect handling, and the vulnerable lxml pin 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
juejin_skill/api.py:18
Finding

Authenticated HTTP Client Can Exfiltrate Session Cookies to Arbitrary Hosts

Content
View full analysis
None: self._cookie = cookie self._timeout = timeout def _build_headers(self, extra: dict[str, str] | None = None) -> dict[str, str]: headers = {**DEFAULT_HEADERS} if self._cookie: headers["Cookie"] = self._cookie if extra: headers.update(extra) return headers def _request( self, method: str, url: str, *, json_body: dict[str, Any] | None = None, params: dict[str, Any] | None = None, extra_headers: dict[str, str] | None = None, ) -> dict[str, Any]: with httpx.Client(timeout=self._timeout) as client: resp = client.request( method, url, headers=self._build_headers(extra_headers), json=json_body, params=params, ) resp.raise_for_status() return resp.json() def get(self, url: str, **kwargs: Any) -> dict[str, Any]: """Send a GET request and return JSON.""" return self._request("GET", url, **kwargs) def post( self, url: str, json_body: dict[str, Any] | None = None, **kwargs: Any, ) -> dict[str, Any]: """Send a POST request and return JSON.""" return self._request("POST", url, json_body=json_body, **kwargs) ``` ### Technical Analysis `JuejinAPI` accepts an unrestricted URL through its public `get()` and `post()` methods. `_request()` sends the request without validating the URL scheme, hostname, port, or destination IP. At the same time, `_build_headers()` unconditionally attaches the Juejin session cookie whenever the client was initialized with one. Consequently, any caller with access to an authenticated `JuejinAPI` instance can cause the Juejin session credential to be sent to an arbitrary HT ...[truncated 1459 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
juejin_skill/downloader.py:640
Finding

Image Domain Allowlist Can Be Bypassed Through HTTP Redirects

Content
View full analysis
bool: """Check if the image URL is from an allowed domain.""" from urllib.parse import urlparse try: parsed = urlparse(url) domain = parsed.netloc.lower() for allowed_domain in ALLOWED_IMAGE_DOMAINS: if domain == allowed_domain or domain.endswith(f".{allowed_domain}"): return True return False except Exception: return False ``` ### Technical Analysis The downloader validates only the hostname in the original Markdown image URL. It then calls `httpx.get()` with `follow_redirects=True`. No validation is performed on intermediate redirect locations or the final response URL. An initially permitted Juejin or B ...[truncated 1846 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
juejin_skill/auth.py:25
Finding

Caller-Controlled Cookie Path Enables Arbitrary File Overwrite and Deletion

Content
View full analysis
None: self._cookie_path = cookie_path self._cookie: str = "" ``` ```python def clear_cookie(self) -> None: """Delete the saved cookie file.""" if os.path.exists(self._cookie_path): os.remove(self._cookie_path) print(f"[JuejinAuth] Cookie file removed: {self._cookie_path}") self._cookie = "" ``` ```python def _save_cookies(self, cookies: list[dict[str, Any]]) -> None: save_json_file(self._cookie_path, cookies) try: os.chmod(self._cookie_path, 0o600) except OSError: pass ``` The supporting helper performs unrestricted directory creation and file truncation: ```python def save_json_file(path: str, data: Any) -> None: """Save data as JSON to a file.""" os.makedirs(os.path.dirname(path) or ".", exist_ok=True) with open(path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) ``` ### Technical Analysis `JuejinAuth` allows callers to supply an arbitrary `cookie_path`, but it does not canonicalize or constrain that path to the declared credential location, `~/.juejin_cookie.json`. After a successful login, `_save_cookies()` passes this path to `save_json_file()`. The helper creates parent directories and opens the destination in write mode, truncating an existing writable file before storing JSON. `clear_cookie()` similarly calls `os.remove()` on the unrestricted path. No protections reject path traversal, paths outside the intended credential location, symbolic links, or sensitive files. Consequently, a caller can use an API intended for Juejin cookie management as an arbitrary overwrite or d ...[truncated 1382 chars]
Remediation
View remediation
Vulnerability Patterns
  • 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
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (17)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
85% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · SKILL.md (reported line 22)May include surrounding context.

md
rotation: 用户可随时通过删除 ~/.juejin_cookie.json 撤销访问;Cookie 过期后需要重新登录。
    hardening:
      - 不要在多人共享的机器或 CI 容器中使用(Cookie 即账号登录态)。
      - 使用完毕立即执行 `rm ~/.juejin_cookie.json` 撤销。
      - 不要将该文件提交到版本库;本仓库已在 `.clawhubignore` / `.gitignore` 中排除。
permissions:
  - network: |

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
85% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · SKILL.md (reported line 85)May include surrounding context.

md
rotation: 用户可随时通过删除 ~/.juejin_cookie.json 撤销访问;Cookie 过期后需要重新登录。
    hardening:
      - 不要在多人共享的机器或 CI 容器中使用(Cookie 即账号登录态)。
      - 使用完毕立即执行 `rm ~/.juejin_cookie.json` 撤销。
      - 不要将该文件提交到版本库;本仓库已在 `.clawhubignore` / `.gitignore` 中排除。
permissions:
  - network: |

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · juejin_skill/publisher.py (reported line 391)May include surrounding context.

python
This matches the ``filesystem_read`` scope declared in SKILL.md and
        prevents the skill from being coerced into reading arbitrary local
        files (e.g. ``~/.ssh/id_rsa``) and exfiltrating them as article
        content.
        """
        safe_path = _validate_markdown_path(filepath)

Known Vulnerable Dependency: lxml==5.3.0 — 2 advisory(ies): CVE-2026-41066 (lxml: Default configuration of iterparse() and ETCompatXMLParser() allows XXE to); CVE-2026-41066 (lxml is a library for processing XML and HTML in the Python language. Prior to 6)

High
Category
Supply Chain
Confidence
97% confidence
Finding

The requirements file pins lxml to version 5.3.0, and the supplied advisory indicates this version is affected by an XXE-related issue in XML parser defaults such as iterparse() and ETCompatXMLParser(). If the skill or its dependencies parse attacker-controlled XML, this can enable unintended external entity resolution, potentially exposing local files, internal network resources, or causing other parser-driven attacks.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

This endpoint is used to create article drafts, which transmits user-provided Markdown content and account-authenticated actions to an external service. Even though this is the skill's intended purpose, it is still a real data-transmission and side-effect surface because local content may be sent off-host under the user's authenticated session.

Content

Scanner excerpt · SKILL.md (reported line 200)May include surrounding context.

md
| 草稿管理 | 支持保存为草稿而不立即发布 |

**API 接口**:
- 创建草稿:`POST https://api.juejin.cn/content_api/v1/article_draft/create`
- 发布文章:`POST https://api.juejin.cn/content_api/v1/article/publish`
- 获取标签:`POST https://api.juejin.cn/tag_api/v1/query_category_tags`

External Transmission

Medium
Category
Data Exfiltration
Confidence
84% confidence
Finding

This endpoint performs article publication, an authenticated external write action with potential reputational and account impact if triggered unexpectedly. The skill context lowers suspicion because the behavior is declared and guarded, but misuse could still publish content under the user's identity.

Content

Scanner excerpt · SKILL.md (reported line 201)May include surrounding context.

md
**API 接口**:
- 创建草稿:`POST https://api.juejin.cn/content_api/v1/article_draft/create`
- 发布文章:`POST https://api.juejin.cn/content_api/v1/article/publish`
- 获取标签:`POST https://api.juejin.cn/tag_api/v1/query_category_tags`

**鉴权方式**:Cookie 鉴权(通过 Playwright 浏览器登录获取)

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 26)May include surrounding context.

md
if not self._cookie:
            return False
        try:
            data = self.get("https://api.juejin.cn/user_api/v1/user/get")
            return data.get("err_no") == 0 and data.get("data") is not None
        except Exception:
            return False

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 184)May include surrounding context.

md
if not self._cookie:
            return False
        try:
            data = self.get("https://api.juejin.cn/user_api/v1/user/get")
            return data.get("err_no") == 0 and data.get("data") is not None
        except Exception:
            return False

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 185)May include surrounding context.

md
if not self._cookie:
            return False
        try:
            data = self.get("https://api.juejin.cn/user_api/v1/user/get")
            return data.get("err_no") == 0 and data.get("data") is not None
        except Exception:
            return False

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 186)May include surrounding context.

md
if not self._cookie:
            return False
        try:
            data = self.get("https://api.juejin.cn/user_api/v1/user/get")
            return data.get("err_no") == 0 and data.get("data") is not None
        except Exception:
            return False

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 187)May include surrounding context.

md
if not self._cookie:
            return False
        try:
            data = self.get("https://api.juejin.cn/user_api/v1/user/get")
            return data.get("err_no") == 0 and data.get("data") is not None
        except Exception:
            return False

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 202)May include surrounding context.

md
if not self._cookie:
            return False
        try:
            data = self.get("https://api.juejin.cn/user_api/v1/user/get")
            return data.get("err_no") == 0 and data.get("data") is not None
        except Exception:
            return False

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 217)May include surrounding context.

md
if not self._cookie:
            return False
        try:
            data = self.get("https://api.juejin.cn/user_api/v1/user/get")
            return data.get("err_no") == 0 and data.get("data") is not None
        except Exception:
            return False

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 218)May include surrounding context.

md
if not self._cookie:
            return False
        try:
            data = self.get("https://api.juejin.cn/user_api/v1/user/get")
            return data.get("err_no") == 0 and data.get("data") is not None
        except Exception:
            return False

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · juejin_skill/api.py (reported line 79)May include surrounding context.

python
if not self._cookie:
            return False
        try:
            data = self.get("https://api.juejin.cn/user_api/v1/user/get")
            return data.get("err_no") == 0 and data.get("data") is not None
        except Exception:
            return False

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · juejin_skill/config.py (reported line 19)May include surrounding context.

python
]

# ----------------------------------------------------------------------- #
# Filesystem-write boundary
# ----------------------------------------------------------------------- #
# All article-download writes must stay inside DEFAULT_OUTPUT_ROOT (or under
# the user-overridden $JUEJIN_OUTPUT_ROOT). This is the single source of

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The comments state that DEFAULT_OUTPUT_ROOT is the single source of truth for filesystem writes, but the module also defines COOKIE_FILE_PATH under the user's home directory. This creates a mismatch between the advertised write boundary and actual write behavior, which can mislead reviewers, users, or policy enforcement into underestimating where the skill persists data.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.