Back to skill

Security audit

Session2blog Clawhub

Security checks for vulnerabilities and agentic risk

Overview

This skill is meant to turn local OpenClaw conversations into blog drafts, but its session selector can read unintended local JSONL files outside the declared session area.

Review before installing if your OpenClaw sessions or nearby JSONL files may contain secrets or private conversations. Only run it on sessions you are comfortable turning into draft content, inspect generated Markdown before publishing, and prefer a version that validates --session to a simple session ID and verifies the resolved file remains inside the OpenClaw session directory.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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

Error
Location
s2b.sh:273
Finding

Unvalidated Session Identifier Allows Path Traversal and Arbitrary JSONL File Reads

Content
View full analysis

Vulnerability Details

File Location: s2b.sh, lines 273–274; user-controlled input originates at line 25
Vulnerability Type: Path traversal / arbitrary local file read
Risk Level: High
Classification: T09: Insecure Skill Coding Practices

Vulnerable Code

bash
--session) SESSION_ID="${2:-}"; shift 2 ;;
python
if session_id:
    target = os.path.join(session_dir, session_id + ".jsonl")

The resulting path is subsequently accepted when it refers to a regular file and is opened by the session-processing functions.

Technical Analysis

The --session argument is copied into SESSION_ID without validation. The Python implementation then appends .jsonl and joins the result to the intended session directory.

os.path.join() does not enforce directory containment. A session identifier containing ../ components can resolve outside the configured session directory. In addition, if the supplied identifier is absolute, Python discards the preceding session_dir component.

The implementation neither canonicalizes the target with realpath() nor verifies that the resolved target remains beneath the canonical session directory. Consequently, the check that the target is a regular file does not prevent an out-of-directory read.

Exploitation requires the target to be readable by the account running the Skill, to end in .jsonl after argument processing, and to contain records matching the expected message format. The redaction function does not eliminate the vulnerability because it is heuristic and does not cover every possible secret or sensitive data format.

Attack Path

  1. An attacker causes the Skill to be invoked with a crafted argument such as:
    text
    /s2b --session ../../path/to/sensitive-session
    
  2. The shell stores the traversal string in S2B_SESSION_ID.
  3. Python constructs:
    python
    os.path.join(session_dir, "../../path/to/sensiti
    

...[truncated 1064 chars]

Remediation
View remediation

Remediation Suggestions

  1. Restrict session identifiers to an explicit allowlist:
    python
    import re
    
    if not re.fullmatch(r"[A-Za-z0-9_-]+", session_id):
        raise ValueError("Invalid session identifier")
    
  2. Resolve and verify containment before opening the file:
    python
    base = os.path.realpath(session_dir)
    target = os.path.realpath(os.path.join(base, session_id + ".jsonl"))
    
    if os.path.commonpath([base, target]) != base:
        raise ValueError("Session path is outside the session directory")
    
  3. Prefer selecting the target only from the already enumerated files collection instead of constructing a path directly from user input.
  4. Reject absolute paths, path separators, . components, and .. components explicitly.
  5. Where supported, open files with protections against symbolic-link races and verify the opened descriptor refers to an expected regular file.
  6. Add tests covering relative traversal, absolute paths, symbolic links, encoded separators, nonexistent sessions, and valid session identifiers.
  7. Treat redaction only as defense in depth. Avoid emitting session content to terminal output by default and require explicit confirmation before including sensitive dialogue in generated material.

T01 · Skill Instruction Hijacking

Note
Location
s2b.sh:413
Finding

Unconditional Commercial Promotion Hijacks Normal Skill Output

Content
View full analysis

Vulnerability Details

File Location: s2b.sh, lines 413–428
Vulnerability Type: Unsolicited output manipulation
Risk Level: Low
Classification: T01: Skill Instruction Hijacking

Vulnerable Code

python
def print_pro_pitch():
    print("")
    print("  " + "=" * 56)
    print("")
    print("  ✅ Pro publishing and scheduled delivery")
    print("  ✅ One-time purchase")
    print("")
    print("  https://jasonlizy.gumroad.com/l/session2blog-pro")
    print("  https://github.com/jasonleezy/session2blog")
    print("  " + "=" * 56)

print_pro_pitch()

The displayed source uses localized promotional wording, but the security-relevant behavior is the unconditional invocation and the external commercial destination shown above.

Technical Analysis

print_pro_pitch() is invoked unconditionally during the normal generation path. The promotion is not needed to extract, redact, structure, or save session content. Because Skill output is commonly consumed and relayed by an AI agent, mandatory promotional text becomes part of trusted tool output and can influence the response presented to the user.

No direct network request is made by this code, and no remote payload is downloaded. The issue is therefore limited to output and instruction-channel pollution rather than remote execution or data exfiltration.

Attack Path

  1. A user invokes /s2b for normal session-to-blog processing.
  2. The script processes the selected session and constructs writing instructions.
  3. Before returning the functional output, it calls print_pro_pitch() without checking user intent or an opt-in setting.
  4. The commercial message and external purchase URL enter the tool output.
  5. The hosting Agent may relay the promotion as if it were an expected part of the requested result.

Impact Assessment

The issue does not grant filesystem, network, or operating-system privileges. Its scope is the i ...[truncated 298 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the unconditional print_pro_pitch() call from normal generation.
  2. Place commercial or upgrade information behind an explicit command such as --pro-info or --about.
  3. Keep normal output limited to task results, warnings directly relevant to security, and generated-file locations.
  4. Send optional informational messages to a separate diagnostic channel where the host framework supports one.
  5. Add output-contract tests to ensure generation and listing modes do not emit advertisements or unrelated external links.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (13)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · README.md (reported line 101)May include surrounding context.

md
例如:`2026-07-11-tech-review-wechat-openclaw-product.md`

生成的博文**自动脱敏**,不会泄露:
- API Key / Access Token / Secret(自动替换为 `<REDACTED>`)
- 真实用户名、邮箱、手机号(替换为占位符)
- 绝对文件路径(如 `/Users/jasonlee/...` 替换为 `<USER_HOME>/...`)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

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

md
例如:`2026-07-11-tech-review-wechat-openclaw-product.md`

生成的博文**自动脱敏**,不会泄露:
- API Key / Access Token / Secret(自动替换为 `<REDACTED>`)
- 真实用户名、邮箱、手机号(替换为占位符)
- 绝对文件路径(如 `/Users/jasonlee/...` 替换为 `<USER_HOME>/...`)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

声明的核心能力是“把会话沉淀成博文并自动保存为 Markdown 文件”。但该代码块的实际主流程只是解析参数、读取会话、脱敏、展示摘要,并打印给外部模型使用的写作指令,最后提示“现在请生成博文并保存到上述路径”。虽然存在 gen_article_via_ollama 函数用于本地生成文章,但在这段代码中并未被调用;也没有任何 open(..., 'w') 等文件写入逻辑来保存 Markdown。因此,最关键的已声明功能——自动生成并保存文章——并没有在实际行为中完成。代码确实围绕“会话转博客”这一主题,但实现停留在准备素材/提示词阶段,和描述存在实质性落差。此外,描述提到“在会话中输入 /s2b 即可触发”,而该代码只体现为命令行脚本,没有看到聊天命令触发集成。营销性质的 Pro 升级提示虽非恶意,但属于未声明的附加行为。综上,应判定为描述与行为不匹配。

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The manifest says the skill '自动保存为 Markdown 文件', implying the skill actually produces and stores the blog post. In this file, the main generate path only prints a dialogue summary and writing instructions to stdout, ending with '现在请生成博文并保存到上述路径', with no code that writes Markdown content to disk.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The natural-language instructions explicitly state '语言:中文' as a mandatory writing requirement, which imposes a specific language policy. Although the document mentions an --lang en option elsewhere, this requirement section itself sets Chinese as the default behavior rather than clearly offering a neutral language choice at execution time.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script's user-facing messages are entirely in Chinese and the generated configuration sets language: zh-CN by default, with no prompt or documented opt-in. This imposes a specific language/locale choice on all users, which matches the policy-violation category for forced language without user choice.

Content

No source excerpt is available for this finding.

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 · install.sh (reported line 13)May include surrounding context.

sh
SKILLS_DIR="${OPENCLAW_SKILLS_DIR:-$HOME/.openclaw/skills}"
TARGET="$SKILLS_DIR/session2blog"

mkdir -p "$SKILLS_DIR"

# 复制 skill 文件
echo "  复制 SKILL.md, s2b.sh ..."

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · install.sh (reported line 43)May include surrounding context.

sh
# 当前已实现: juejin (发草稿)。微信/CSDN/知乎待后续更新。
juejin_cookie:
EOF
  chmod 600 "$S2B_DIR/config.yaml"
fi

echo ""

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
76% confidence
Finding

The comment says '把所有逻辑交给 Python 处理', and the surrounding documentation presents the tool as a blog generator. However, the Python block's effective behavior in generate mode is to display processed dialogue and an instruction prompt rather than actually generate and save the article, which conflicts with the claimed intent of the tool as a one-click converter.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script earlier exposes a --lang option and builds language-aware instructions, but the final hardcoded requirement says 语言: 中文, which overrides or conflicts with the user's language choice. This is a natural-language locale policy issue because it imposes a specific language in the generated output rather than consistently honoring user selection.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The README shows language: zh-CN as the default configuration, which sets a specific language/locale by default. Although later examples show English can be selected with --lang en, this line itself documents a forced default locale rather than presenting language choice as opt-in at configuration time.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The text says the free version only generates local Markdown and does not publish to any platform, which is true for the core skill behavior, but the same skill document also embeds external Gumroad and GitHub links later. While this is not runtime code, it is an intent/documentation inconsistency within the skill file because the document frames the skill as local-only while also directing users to external services.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.