Back to skill

Security audit

juejin-publisher-pro

Security checks for vulnerabilities and agentic risk

Overview

This skill largely does what it says, but it stores account cookies insecurely and a crafted article filename could run unintended PowerShell commands on Windows.

Install only if you are comfortable using unofficial Juejin/Zhihu automation with your logged-in accounts. Keep `.juejin.env` out of git and cloud sync, restrict its permissions, rotate/revoke the session if exposed, avoid running `zhihu` on Markdown files with untrusted filenames, and pin/review dependencies before use.

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)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/publish.py:578
Finding
Excessive Authentication Cookie Collection and Insecure Plaintext Storage<![CDATA[ ## Vulnerability Details **File Location**: `scripts/publish.py:578-610` **Vulnerability Type**: Excessive credential collection and plaintext credential storage **Risk Level**: High ### Vulnerable Code ```python def cookie_header(cookies: list[dict], want: list[str]) -> tuple[str, list[str]]: pairs = [f"{c['name']}={c['value']}" for c in cookies if c.get("name") and c.get("value")] names = {c["name"] for c in cookies} return "; ".join(pairs), [w for w in want if w not in names] def save_env(cookie_header_str: str) -> None: ENV_FILE.write_text(f"JUEJIN_COOKIE={cookie_header_str}\n", encoding="utf-8") ``` The collected cookies are subsequently persisted during login: ```python cs = tab.cookies(["https://juejin.cn", "https://api.juejin.cn"]) sid = next((c for c in cs if c["name"] == "sessionid"), None) if sid: header, _ = cookie_header(cs, ["sessionid"]) save_env(header) ``` ### Technical Analysis The publishing functionality legitimately requires an authenticated Juejin session, and sending an authentication cookie to `api.juejin.cn` is consistent with the declared functionality. However, the implementation exceeds the minimum required credential scope. Although the login routine checks specifically for `sessionid`, `cookie_header()` serializes every nonempty cookie returned for the Juejin domains. The complete cookie header is then written to `.juejin.env` as plaintext using default filesystem permissions. This creates two separate security issues: 1. **Excessive collection:** Cookies unrelated to the required authenticated API session are retained unnecessarily. 2. **Insecure storage:** Authentication material is stored without owner-only permission enforcement, encryption, secure credential storage, or atomic creation. The repository snapshot also does not contain a `.gitignore`, despite `juejin.env.example` stating that `.juejin.env` is excluded from version control. This increases the risk of accidental credenti ...[truncated 1371 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Persist only an explicit allowlist of cookies required by the API: ```python REQUIRED_COOKIES = {"sessionid"} def cookie_header(cookies: list[dict]) -> str: return "; ".join( f"{c['name']}={c['value']}" for c in cookies if c.get("name") in REQUIRED_COOKIES and c.get("value") ) ``` 2. Create the credential file with owner-only permissions, such as mode `0600` on POSIX systems. 3. Write credentials atomically through a securely created temporary file and then replace the destination. 4. Prefer operating-system credential storage, such as Windows Credential Manager, macOS Keychain, or a Linux Secret Service provider. 5. Add a project `.gitignore` containing at least: ```gitignore .juejin.env .cdp-profile/ _wf/ ``` 6. Clearly document the exact cookies collected, their storage location, retention period, and revocation procedure. 7. Advise users to revoke or rotate the Juejin session immediately if `.juejin.env` may have been exposed. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/publish.py:821
Finding
PowerShell Command Injection Through a Crafted Article Filename<![CDATA[ ## Vulnerability Details **File Location**: `scripts/publish.py:821-830` **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```python def zhihu_clipboard(post: dict) -> None: out = WF / f"zhihu_发布_{post['file'].stem}.md" content = f"# {post['title_zhihu']}\n\n{post['body']}" out.write_text(content, encoding="utf-8") try: subprocess.run( ["powershell", "-NoProfile", "-Command", f"Get-Content -LiteralPath '{out}' -Raw -Encoding UTF8 | Set-Clipboard"], check=True, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0), ) ``` ### Technical Analysis The source Markdown filename is user-controlled. Its stem is incorporated into `out`, and the resulting path is interpolated directly into a PowerShell `-Command` script inside single quotes. Using an argument array for `subprocess.run()` prevents shell interpretation by the parent process, but it does not make the PowerShell script safe. PowerShell receives the entire interpolated value following `-Command` and parses it as source code. A single quote in the filename can terminate the quoted `-LiteralPath` value, after which PowerShell syntax embedded in the filename can be executed. The `-LiteralPath` option prevents wildcard expansion but does not protect a path that has already been inserted into PowerShell source code. This path is reached by `zhihu_clipboard()` when the CDP-based Zhihu workflow falls back after a supported CDP error. It may also be invoked directly from that fallback routine with an attacker-supplied or downloaded Markdown file. ### Attack Path 1. An attacker provides a Markdown article with a filename containing a single quote followed by PowerShell syntax. 2. The victim invokes the `zhihu` command for that article. 3. The CDP channel fails with a handled `RuntimeError` or `OSError`, causing the script to call `zhihu_clipboard()`. 4. The malicious filename becomes part of th ...[truncated 884 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not interpolate filenames or article content into PowerShell source code. A safer approach is to read the content in Python and send it through standard input to a constant PowerShell command: ```python content = out.read_text(encoding="utf-8") subprocess.run( [ "powershell", "-NoProfile", "-NonInteractive", "-Command", "$text = [Console]::In.ReadToEnd(); Set-Clipboard -Value $text", ], input=content, text=True, check=True, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0), ) ``` Additional hardening should include: 1. Use a native clipboard API or a reviewed clipboard library instead of constructing shell commands. 2. Normalize generated filenames and reject control characters or shell metacharacters if filenames must cross an interpreter boundary. 3. Create `_wf` before writing and restrict its filesystem permissions where supported. 4. Catch `OSError` as well as `subprocess.CalledProcessError` in the fallback routine so missing PowerShell does not terminate unexpectedly. 5. Add regression tests using filenames containing apostrophes, semicolons, spaces, Unicode characters, and PowerShell metacharacters. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:11
Finding
Unpinned Third-Party Dependencies Create a Mutable Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:11` and `README.md:19` **Vulnerability Type**: Unpinned dependency installation **Risk Level**: Medium ### Vulnerable Instructions ```text uv add websockets pillow ``` The same unversioned installation instruction appears in the quick-start documentation: ```bash uv add websockets pillow ``` ### Technical Analysis The setup procedure installs `websockets` and `pillow` without pinned versions, hashes, or a committed lockfile. The package names correspond to modules used by the code and there is no evidence of typosquatting in the reviewed instructions. Nevertheless, the effective dependency code is mutable after this Skill has been audited. A future release, compromised package account, malicious source distribution, or unsafe package-index configuration could cause users to install code different from the version reviewed by the Skill author. Dependency code may execute during build or installation, and imported package initialization code executes at runtime. Because no lockfile or integrity data is included, separate users or installations can receive materially different dependency versions. ### Attack Path 1. The user follows the documented setup procedure. 2. `uv` resolves the latest versions available from the configured package index. 3. A compromised, malicious, or unexpectedly altered package release is selected. 4. Package build logic, installation hooks, or imported runtime code executes in the user's environment. 5. The dependency gains the same local privileges as the publishing script. ### Impact Assessment A compromised dependency could obtain arbitrary code execution in the user's environment. This could expose: - Juejin authentication cookies. - The dedicated Chromium CDP profile. - Article drafts and local project files. - Zhihu browser sessions. - Any other files and credentials accessible to the invoking user. The repository does not itself contain a confirmed mali ...[truncated 102 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin dependency versions that have been reviewed and tested, for example: ```bash uv add "websockets==<reviewed-version>" "pillow==<reviewed-version>" ``` 2. Commit the generated `uv.lock` file so installations resolve to the same artifacts. 3. Use hash verification or artifact integrity checking where supported. 4. Configure and document trusted package indexes rather than inheriting arbitrary environment-specific indexes. 5. Add automated dependency vulnerability scanning and update dependencies through a controlled review process. 6. Document the minimum and maximum compatible versions so users do not silently receive untested releases. ]]>
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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The README explicitly advertises automatic harvesting of long-lived cookies from a real account and device identifiers, but provides no warning about credential sensitivity, storage risks, or misuse implications. In the context of a publishing automation skill that reverse-engineers unofficial interfaces and bypasses WAF checks, these cookies function like persistent authentication tokens and could enable account takeover or unauthorized publishing if exposed.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill advertises capabilities that imply file, environment, shell, and network access but does not declare any explicit tool scope or permission boundaries. In a skill that performs browser-driven login, captures cookies, writes local credential files, and publishes content over undocumented endpoints, the lack of least-privilege constraints materially increases the chance of credential exposure, unintended local file access, or abuse of the host environment.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill instructs users to scan-login and automatically capture platform cookies, then store them in a local `.juejin.env` file with a long stated validity period, but it does not prominently warn that cookies are bearer credentials equivalent to account access. Because the same workflow also automates publishing actions, compromise of that file could enable account takeover, unauthorized posting, and abuse of linked platform sessions.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script starts a browser with --remote-debugging-port=9222 and a dedicated profile without an explicit warning about the security implications. Any local process able to reach that debug port can inspect pages, steal cookies, inject JavaScript, and drive authenticated sessions, which is especially sensitive here because the tool later harvests login state from that browser.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
f"--user-data-dir={CDP_PROFILE}",
            "--no-first-run", "--no-default-browser-check", "--restore-last-session=false",
            url]
    subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    for _ in range(50):
        if cdp_alive():
            return
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script harvests authenticated cookies from the browser and writes them to a local .juejin.env file for later API use, but it provides no explicit security warning, permission hardening, or protection for that credential material. Anyone with local access, malware, backups, or accidental repository inclusion could reuse the persisted session cookie to act as the user on the target platform.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
content = f"# {post['title_zhihu']}\n\n{post['body']}"
    out.write_text(content, encoding="utf-8")
    try:
        subprocess.run(
            ["powershell", "-NoProfile", "-Command",
             f"Get-Content -LiteralPath '{out}' -Raw -Encoding UTF8 | Set-Clipboard"],
            check=True, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
Confidence
97% confidence
Finding
This builds a PowerShell -Command string using a file path derived from user input, and the path is interpolated inside single quotes without robust escaping. A crafted filename containing a single quote or PowerShell metacharacters could break out of the quoted literal and execute unintended commands when the clipboard fallback runs.

Static analysis

No suspicious patterns detected.