Back to skill

Security audit

Bear Blog Publisher

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly does what it claims, but it handles blog-account passwords and public publishing in ways users should review carefully before installing.

Review this before installing if you plan to give it real Bear Blog credentials. Prefer isolated credentials, avoid passing passwords in chat or command-line arguments, do not store passwords in plaintext config unless you accept that risk, and require a manual confirmation before publishing. Treat AI generation as sending prompts to OpenAI or Kimi, and avoid using the diagram feature with untrusted text until the HTML escaping, temp-file handling, and browser sandboxing are fixed.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/publish.py:367
Finding
Unescaped HTML Injection in Unsandboxed Browser with Unsafe Temporary Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/publish.py:367-388` **Vulnerability Type**: Unescaped HTML injection, weakened browser isolation, and unsafe predictable temporary files **Risk Level**: High ### Vulnerable Code ```python <div class="container"> <div class="title">{title}</div> <div class="diagram"> {''.join(f'<div class="box"><div class="box-title">{comp}</div></div>' for comp in components)} </div> </div> </body> </html> """ html_path = '/tmp/diagram.html' with open(html_path, 'w') as f: f.write(html_content) with sync_playwright() as p: browser = p.chromium.launch(headless=True, args=['--no-sandbox']) page = browser.new_page(viewport={'width': 1100, 'height': 400}) page.goto(f'file://{html_path}') page.wait_for_timeout(500) screenshot_path = '/tmp/diagram.png' page.screenshot(path=screenshot_path, full_page=True) browser.close() ``` ### Technical Analysis The diagram title and component values are interpolated directly into an HTML document without HTML escaping or validation. Because this document is subsequently loaded by Chromium, a caller capable of controlling `title` or `components` can inject active HTML, including scripts, event handlers, resource-loading elements, or malformed markup. Chromium is explicitly launched with `--no-sandbox`. This does not by itself grant injected JavaScript operating-system access, but it removes an important containment layer and increases the impact of any browser vulnerability or renderer compromise. The generated HTML and PNG also use globally predictable paths in the shared `/tmp` directory. Opening `/tmp/diagram.html` with the default Python write mode follows symbolic links. A local attacker may therefore pre-create that path as a symlink to another file writable by the publisher process. Similar race and content-integrity concerns affect the predictable screenshot path. ### Attack Path 1. An attacker supplies a diagr ...[truncated 1431 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every untrusted value before inserting it into HTML: ```python from html import escape safe_title = escape(str(title), quote=True) safe_components = [escape(str(component), quote=True) for component in components] ``` 2. Prefer DOM text insertion, such as `textContent`, rather than constructing HTML through string concatenation. 3. Validate input length and type to prevent excessively large or malformed documents. 4. Remove `--no-sandbox` under normal execution. If a container requires special handling, configure the container and Chromium permissions so that the browser sandbox remains enabled. 5. Block all browser network requests during local rendering: ```python page.route("**/*", lambda route: ( route.continue_() if route.request.url.startswith("file://") else route.abort() )) ``` 6. Use a private temporary directory and unpredictable files: ```python import tempfile from pathlib import Path with tempfile.TemporaryDirectory(prefix="bear-diagram-") as directory: html_path = Path(directory) / "diagram.html" screenshot_path = Path(directory) / "diagram.png" ``` 7. Ensure temporary files are cleaned up after rendering. If the screenshot must survive the method, securely copy it to a caller-selected destination. 8. Do not run the Skill as root or another privileged account. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/publish.py:398
Finding
Bear Blog Password Exposed Through Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/publish.py:398-413` **Vulnerability Type**: Sensitive information exposure through process arguments and shell history **Risk Level**: Medium ### Vulnerable Code ```python if len(sys.argv) < 3: print("Usage: python publish.py <title> <content_file> [email] [password]") print("") print("Authentication methods (in priority order):") print(" 1. Command line arguments: email password") print(" 2. Environment variables: BEAR_BLOG_EMAIL, BEAR_BLOG_PASSWORD") print(" 3. Config file: ~/.openclaw/openclaw.json") print("") print("For AI content generation, set OPENAI_API_KEY or KIMI_API_KEY") sys.exit(1) title = sys.argv[1] with open(sys.argv[2], 'r') as f: content = f.read() email = sys.argv[3] if len(sys.argv) > 3 else None password = sys.argv[4] if len(sys.argv) > 4 else None ``` ### Technical Analysis The CLI explicitly supports passing the Bear Blog password as a positional command-line argument. Process arguments are commonly visible through process inspection facilities, monitoring software, audit logs, job schedulers, diagnostic output, and command history. Although exposure details depend on operating-system policy, command-line arguments are not an appropriate secret transport mechanism. The risk is unnecessary because the project already supports environment variables and configuration-based credential resolution. ### Attack Path 1. A user follows the displayed CLI instructions and invokes the script with a plaintext password. 2. The shell may save the entire command in history. 3. While the process is running, a local user or monitoring process with sufficient visibility reads the command line from process-management tools or operating-system process metadata. 4. Alternatively, CI logs, wrappers, schedulers, or diagnostic systems record the complete invocation. 5. The observer recovers the Bear Blog email and password and uses them to authenticate ...[truncated 522 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove password support from positional command-line arguments. 2. Prompt interactively without echoing: ```python from getpass import getpass password = getpass("Bear Blog password: ") ``` 3. For automated environments, obtain the password from a dedicated secret manager or a narrowly scoped environment variable injected only into the target process. 4. Support reading the secret from an already-open file descriptor where appropriate, rather than from a command-line path containing the secret. 5. Update the usage text and documentation so they never demonstrate plaintext passwords in command invocations. 6. Advise users who previously used this interface to clear relevant shell and CI logs and rotate exposed credentials. ]]>

T08 · Insecure Dependencies

Warning
Location
package.json:22
Finding
Unpinned Python and Chromium Dependencies Create Mutable Supply-Chain Exposure<![CDATA[ ## Vulnerability Details **File Location**: `package.json:22-39` **Vulnerability Type**: Unpinned third-party packages and browser artifacts **Risk Level**: Medium ### Vulnerable Code ```json "openclaw": { "requires": { "bins": ["python3"], "python": ["requests", "playwright"] }, "install": [ { "id": "playwright", "kind": "python", "package": "playwright", "label": "Install Playwright Python package" }, { "id": "playwright-browsers", "kind": "script", "command": "playwright install chromium", "label": "Download Playwright browsers (~100MB)" } ], ``` ### Technical Analysis The manifest requests `requests` and `playwright` without exact versions or verified hashes. The Playwright installer also downloads a Chromium artifact without an explicitly reviewed and pinned browser revision in the project manifest. As a result, two installations of the same Skill version may retrieve different package and browser code. A compromised upstream release, dependency account, package index, mirror, or artifact-distribution channel could therefore alter the effective code executed by the Skill after this source package was audited. No malicious package or compromised source was identified in the reviewed project. The finding is the absence of dependency immutability and artifact verification. ### Attack Path 1. An attacker compromises an applicable package publication account, package index, dependency, mirror, or browser artifact channel. 2. The attacker publishes or substitutes a release accepted by the unpinned dependency declarations. 3. A user installs the Skill after the compromised release becomes available. 4. The installer downloads the changed Python package or Chromium artifact. 5. Malicious package installation logic, imported Python code, or browser code executes with the privileges of the account installing or running the Skill. ### Impact Assessment A compromised dep ...[truncated 439 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin exact versions of all direct dependencies. 2. Maintain a lock file or hash-verified requirements file, for example: ```text requests==<reviewed-version> --hash=sha256:<reviewed-hash> playwright==<reviewed-version> --hash=sha256:<reviewed-hash> ``` 3. Pin the Playwright version and its corresponding Chromium revision as a reviewed pair. 4. Install packages only from trusted, explicitly configured indexes. 5. Verify downloaded browser artifacts using publisher signatures or cryptographic hashes where supported. 6. Run dependency installation as an unprivileged user and isolate installation from production secrets. 7. Use automated dependency scanning, but review and deliberately update the lock data rather than accepting mutable versions at installation time. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
README.md:19
Finding
Documentation Incorrectly Claims Credentials Are Never Persisted<![CDATA[ ## Vulnerability Details **File Location**: `README.md:19-36,101-104` **Vulnerability Type**: Misleading security documentation for plaintext credential storage **Risk Level**: Low ### Vulnerable Documentation ```markdown ### Method 1: OpenClaw Config File Edit `~/.openclaw/openclaw.json`: ```json { "skills": { "bear-blog-publisher": { "email": "your@email.com", "password": "yourpassword" } } } ``` Set secure permissions: ```bash chmod 600 ~/.openclaw/openclaw.json ``` ``` The same README later states: ```markdown ## Security Notes - **No persistent credential storage** - credentials only exist in memory during execution - **Session-only authentication** - no tokens stored between runs - **Config file permission check** - warns if readable by others - **Priority**: Runtime > Environment > Config ``` ### Technical Analysis The README instructs users to persist a plaintext Bear Blog password in `~/.openclaw/openclaw.json`, but later states that credentials only exist in memory and are not persistently stored. These claims are contradictory. Restrictive file permissions reduce exposure to other local users but do not make the secret nonpersistent. The password remains accessible to the account owner, privileged processes, backups, malware operating as that user, and any software allowed to read the configuration file. `SKILL.md` separately acknowledges the plaintext configuration option, but the conflicting README statement can still cause users to make decisions based on an inaccurate security guarantee. ### Attack Path 1. A user relies on the claim that credentials are never persistently stored. 2. The user selects the configuration-file authentication method. 3. The Bear Blog password is written in plaintext to `~/.openclaw/openclaw.json`. 4. A process running as the same user, a privileged administrator, compromised backup system, or malware with access to the home directory reads the file. 5. The recovered creden ...[truncated 515 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the inaccurate claim with an explicit statement that configuration-file authentication stores the password persistently in plaintext. 2. Recommend an operating-system keychain, secret manager, or short-lived secret injection mechanism as the preferred approach. 3. Retain the `chmod 600` recommendation as defense in depth, not as a guarantee against credential persistence or compromise. 4. Explain the exposure to same-user processes, privileged users, backups, and endpoint compromise. 5. Keep credential-handling guidance consistent across `README.md`, `SKILL.md`, and the CLI help. 6. Consider removing plaintext password support from the OpenClaw configuration schema if secure secret references are available. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (23)

Tainted flow: 'api_key' from os.environ.get (line 133, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
if not api_key:
            raise ValueError("OPENAI_API_KEY environment variable not set")
        
        response = requests.post(
            "https://api.openai.com/v1/chat/completions",
            headers={
                "Authorization": f"Bearer {api_key}",
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'api_key' from os.environ.get (line 133, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
if not api_key:
            raise ValueError("KIMI_API_KEY environment variable not set")
        
        response = requests.post(
            "https://api.moonshot.cn/v1/chat/completions",
            headers={
                "Authorization": f"Bearer {api_key}",
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README promotes publishing user-provided and AI-generated content to Bear Blog but does not clearly warn that submitted content is sent to an external platform and, once published, becomes publicly accessible. In a skill that may process pasted private notes, drafts, or sensitive AI-generated material, this omission can lead to unintended data disclosure by users who do not realize the action is externally transmitted and public.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
Set secure permissions:
```bash
chmod 600 ~/.openclaw/openclaw.json
```

### Method 2: Environment Variables
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
Set secure permissions:
```bash
chmod 600 ~/.openclaw/openclaw.json
```

### Method 2: Environment Variables
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The AI content generation section instructs users to provide OpenAI or Kimi API keys and generate content, but it does not warn that prompts, source text, or related inputs may be transmitted to third-party AI providers. If users supply sensitive drafts or proprietary material, this omission increases the risk of unintentional exposure to external services beyond Bear Blog itself.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill documentation describes capabilities that require access to environment variables, files, network, and shell-like execution, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates an over-privileged and ambiguous execution model where a host agent may grant broader access than necessary, increasing the risk of credential exposure, arbitrary file access, or unintended outbound requests.

Session Persistence

Medium
Category
Rogue Agent
Content
## Introduction

[Bear Blog](https://bearblog.dev/) is a privacy-focused, no-tracking blogging platform that lets you write without distractions. But what if you want to automate your publishing workflow? 

Enter **Bear Blog Publisher** — an OpenClaw skill that enables automatic blog publishing with optional AI content generation and diagram creation. In this guide, I'll walk you through how to set it up and share my real-world experience integrating it with Feishu (Lark) assistant.
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.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The document explicitly describes a workflow where a chat assistant asks users to provide Bear Blog email and password, but it does not warn that chat platforms, bot logs, middleware, or operators may retain or expose those credentials. In the context of a publishing skill integrated with Feishu/OpenClaw, encouraging password submission over chat materially increases the risk of credential theft, accidental logging, and cross-system compromise.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The activation condition and examples are broad enough that the skill may be selected for generic requests to write or create blog content, not just explicit requests to publish to Bear Blog. In this skill's context, that matters because the skill has publishing capability plus access to optional credentials and API keys, so over-broad routing could cause unintended external posting or credential use when the user only wanted drafting assistance.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The manifest describes a blog publishing skill that supports user-provided and AI-generated content, but it does not state that the skill will inspect process environment variables or local configuration files for secrets. Reading local secret stores is an additional credential-access capability beyond the user-facing publishing purpose and should be explicitly declared if intended.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# Check file permissions (should be 600)
                    file_stat = config_path.stat()
                    if file_stat.st_mode & stat.S_IRWXG or file_stat.st_mode & stat.S_IRWXO:
                        print("Warning: Config file is readable by others. Run: chmod 600 ~/.openclaw/openclaw.json")
                    return config_email, config_password
            except (json.JSONDecodeError, KeyError):
                pass
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Session Persistence

Medium
Category
Rogue Agent
Content
Generate blog content using AI.
        
        Args:
            topic: What to write about
            provider: LLM provider ('openai' or 'kimi')
            tone: Writing style (professional, casual, technical)
            length: short, medium, or long
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.

External Transmission

Medium
Category
Data Exfiltration
Content
if not api_key:
            raise ValueError("OPENAI_API_KEY environment variable not set")
        
        response = requests.post(
            "https://api.openai.com/v1/chat/completions",
            headers={
                "Authorization": f"Bearer {api_key}",
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if not api_key:
            raise ValueError("OPENAI_API_KEY environment variable not set")
        
        response = requests.post(
            "https://api.openai.com/v1/chat/completions",
            headers={
                "Authorization": f"Bearer {api_key}",
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if not api_key:
            raise ValueError("OPENAI_API_KEY environment variable not set")
        
        response = requests.post(
            "https://api.openai.com/v1/chat/completions",
            headers={
                "Authorization": f"Bearer {api_key}",
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
raise ValueError("OPENAI_API_KEY environment variable not set")
        
        response = requests.post(
            "https://api.openai.com/v1/chat/completions",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if not api_key:
            raise ValueError("KIMI_API_KEY environment variable not set")
        
        response = requests.post(
            "https://api.moonshot.cn/v1/chat/completions",
            headers={
                "Authorization": f"Bearer {api_key}",
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
raise ValueError("KIMI_API_KEY environment variable not set")
        
        response = requests.post(
            "https://api.moonshot.cn/v1/chat/completions",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The upload_image method reads a local file and transmits it to Bear Blog over HTTP after authenticating, but there is no visible warning, confirmation, or user-facing disclosure inside this method about sending file contents off-device. The docstring describes the functional outcome but does not clearly warn about the privacy-impacting network transfer of a local file.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The publish flow performs authenticated requests to Bear Blog using stored credentials and submits content for publication, but the method has no confirmation prompt or explicit user-facing disclosure before the irreversible publish action. Although the docstring says 'Publish a blog post,' it does not clearly warn that executing this function will immediately log in and publish content to a live remote service.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The manifest says the skill supports auto-generated diagrams, but the code implements this by spawning a headless Chromium instance via Playwright with --no-sandbox to render and screenshot HTML. Launching a browser process is a substantially broader capability than simple blog publishing and should be explicitly scoped because it introduces execution behavior unrelated to core post submission.

Missing User Warnings

Low
Confidence
84% confidence
Finding
The generate_content flow sends the user-supplied topic and constructed prompt to either OpenAI or Kimi via external API calls, but there is no explicit user-facing warning that the input will be transmitted to third-party services. The docstrings identify the providers but do not clearly disclose the privacy implication of sharing prompt content externally.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/publish.py:33

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
SKILL.md:175