Back to skill

Security audit

OpenClaw Flomo Skill

Security checks for vulnerabilities and agentic risk

Overview

This flomo skill mostly does what it says, but it handles authenticated account access and memo writes in ways that need careful review before installation.

Install only if you are comfortable giving this skill access to your logged-in flomo account and private memos. Avoid setting FLOMO_API_BASE, review any command before running write or verify, and assume webhook writes and verification can create real memos in your account.

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

Warning
Location
scripts/flomo_tool.py:173
Finding
Bearer Token Can Be Transmitted to an Arbitrary API Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/flomo_tool.py`, lines 173-202 **Vulnerability Type**: Unrestricted credential destination **Risk Level**: Medium ### Vulnerable Code ```python def _api_get(path: str, extra_params: dict | None = None) -> dict: base = os.getenv("FLOMO_API_BASE", "https://flomoapp.com/api/v1").rstrip("/") params = { "api_key": "flomo_web", "app_version": _get_app_version(), "platform": os.getenv("FLOMO_PLATFORM", "mac"), "timestamp": int(time.time()), "webp": "1", } if extra_params: params.update(extra_params) params["sign"] = _sign_params(params.copy()) url = f"{base}{path}?{urllib.parse.urlencode(params, doseq=True)}" return _curl_json( "GET", url, headers={ "Accept": "application/json, text/plain, */*", "Authorization": f"Bearer {_get_access_token()}", "platform": "Mac", "device-model": "Mac", }, ) ``` ### Technical Analysis The `FLOMO_API_BASE` environment variable can replace the default API origin with an unrestricted URL. Regardless of the selected origin, `_api_get()` attaches the bearer token obtained from `FLOMO_ACCESS_TOKEN` or the local flomo desktop configuration. Consequently, configuration or launch-environment manipulation can redirect an authenticated request to an attacker-controlled HTTP or HTTPS server. Customizing the credential-bearing API host is not required for the Skill's ordinary flomo read/write functionality and exceeds the minimum privilege needed for the declared behavior. The English and Chinese README files explicitly document the unrestricted `FLOMO_API_BASE` override, making this behavior part of the supported configuration rather than an unreachable implementation detail. ### Attack Path 1. An attacker or compromised orchestration component influences the Skill's launch environment. 2. `FLOMO_API_BASE` is set to an ...[truncated 893 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `FLOMO_API_BASE` if custom API origins are not essential. 2. Otherwise, parse and validate the URL before issuing any request: - Require HTTPS. - Require the exact approved hostname, such as `flomoapp.com`. - Reject embedded credentials, unexpected ports, IP literals, fragments, and deceptive subdomains. 3. Maintain a strict allowlist of paths that may receive the bearer token. 4. Prevent authenticated requests from following redirects to a different origin. If redirects are needed, validate every destination before retaining the authorization header. 5. Do not attach credentials to any endpoint until its scheme, hostname, port, and path have been validated. 6. Remove the unrestricted override from both README files or document only an explicitly safe, allowlisted development mode that never uses production credentials. 7. Add tests confirming that attacker-controlled hosts and cross-origin redirects are rejected before credential retrieval or network transmission. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/flomo_tool.py:52
Finding
Bearer Token, Webhook Secret, and Memo Content Are Exposed Through Process Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/flomo_tool.py`, lines 52-62 **Additional Locations**: `scripts/flomo_tool.py`, lines 194-202 and 339-340 **Vulnerability Type**: Sensitive information exposure through subprocess arguments **Risk Level**: Medium ### Vulnerable Code ```python def _curl_json(method: str, url: str, headers: dict[str, str] | None = None, data_json: dict | None = None) -> dict: cmd = ["curl", "-sS", "-X", method.upper(), url] if headers: for k, v in headers.items(): cmd.extend(["-H", f"{k}: {v}"]) if data_json is not None: cmd.extend(["-H", "Content-Type: application/json", "-d", json.dumps(data_json, ensure_ascii=False)]) raw = _run_curl(cmd) return json.loads(raw) ``` Authenticated requests supply the bearer token through this function: ```python return _curl_json( "GET", url, headers={ "Accept": "application/json, text/plain, */*", "Authorization": f"Bearer {_get_access_token()}", "platform": "Mac", "device-model": "Mac", }, ) ``` Webhook writes supply both the secret webhook URL and private memo content: ```python resp = _curl_json("POST", webhook_url, data_json={"content": content}) ``` ### Technical Analysis The function builds a `curl` argument vector containing: - The bearer token in a `-H "Authorization: Bearer ..."` argument. - The full incoming webhook URL in the URL argument. - User memo content in the `-d` argument. Although the returned JSON masks the webhook URL, that masking occurs only after `curl` has already been launched with the complete secret in its process arguments. Process inspection, diagnostic tools, crash collection, command instrumentation, or local monitoring software may therefore capture these values while the subprocess exists. Using a subprocess is not inherently unsafe, and `subprocess.run()` with a list avoids shell injection. The vulnerability is specifically the placement of ...[truncated 1299 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer an in-process HTTPS client so secrets do not enter a separate process argument vector. 2. If `curl` must remain in use: - Send request bodies through standard input with `--data-binary @-`. - Avoid placing authorization headers directly in command-line arguments. - Supply sensitive curl configuration through a protected file descriptor or a temporary configuration file with mode `0600`. - Delete temporary files immediately after use and ensure cleanup also occurs on exceptions. 3. Consider retrieving and using the webhook path without exposing the complete URL to diagnostic output. 4. Ensure exceptions, debug logs, telemetry, and crash reports redact authorization headers, webhook URLs, and memo bodies. 5. Avoid retaining sensitive subprocess commands in reusable variables or logging them during troubleshooting. 6. Add automated tests that inspect generated subprocess arguments and fail if bearer tokens, webhook secrets, or memo content appear in them. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/flomo_tool.py:402
Finding
Documented Dry Health Check Performs a Persistent Remote Write<![CDATA[ ## Vulnerability Details **File Location**: `scripts/flomo_tool.py`, lines 402-413 **Related Documentation**: `SKILL.md`, lines 39-42 **Vulnerability Type**: Misleading verification behavior causing unexpected data modification **Risk Level**: Low ### Vulnerable Documentation ```markdown - One-shot health check (read + write-url-scheme dry flow): ```bash python3 scripts/flomo_tool.py verify --try-webhook --query "#openclaw" ``` ``` ### Vulnerable Code ```python if args.try_webhook: try: webhook = args.webhook or os.getenv("FLOMO_WEBHOOK_URL") or get_incoming_webhook_url() nonce = f"ocv{int(time.time())}" content = (args.content or "").strip() if nonce not in content: content = f"{content} {nonce}".strip() out = write_webhook(content, webhook) verify_q = nonce time.sleep(1.2) verify_window = min(args.since_seconds, 3600) if args.since_seconds > 0 else 3600 check = read_remote(limit=40, query=verify_q, tz=args.tz, since_seconds=verify_window) result["checks"].append({"name": "write_webhook", "ok": True, "status": out.get("status"), "readback_hits": check.get("count")}) ``` ### Technical Analysis The authoritative Skill instructions describe the health check as a URL-scheme “dry flow,” but the documented command uses `--try-webhook`. The implementation resolves the user's incoming webhook and calls `write_webhook()`, which creates a real memo in the user's remote flomo account. The generated verification memo is not deleted after readback. Therefore, the operation is neither a dry run nor a URL-scheme-only check. Although `README.md` more accurately identifies verification as an end-to-end webhook write, the inconsistency in `SKILL.md` may cause an Agent or user to perform a persistent state-changing operation without informed consent. ### Attack Path 1. An Agent loads `SKILL.md` and follows the documented one-shot health-check command. 2. The Ag ...[truncated 888 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Correct `SKILL.md` to state clearly that `verify --try-webhook` creates a real, persistent remote memo. 2. Require explicit user confirmation before an Agent invokes any verification option that writes data. 3. Provide a genuinely non-writing health check that validates: - Local configuration availability. - Token presence. - API connectivity. - Webhook discovery, without posting content. 4. If end-to-end write verification remains necessary, use an explicitly named option such as `--perform-test-write`. 5. Consider deleting the verification memo after successful readback if the API supports deletion, while clearly disclosing that temporary remote data creation still occurs. 6. Keep `README.md`, `README.zh-CN.md`, and `SKILL.md` consistent regarding all state-changing behavior. 7. Add a confirmation requirement or `--yes` flag when verification would create a memo, especially in Agent-driven execution. ]]>
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (24)

Credential Access

High
Category
Privilege Escalation
Content
你可以通过环境变量覆盖默认行为:

- `FLOMO_CONFIG_PATH`:覆盖本机 flomo 配置文件路径
- `FLOMO_ACCESS_TOKEN`:手动指定 access token(不推荐;优先使用本机 flomo 登录态)
- `FLOMO_APP_VERSION`:覆盖 app version
- `FLOMO_SIGN_SECRET`:覆盖 sign secret(仅在 flomo 签名机制变更时使用)
- `FLOMO_API_BASE`:覆盖 API base(默认 `https://flomoapp.com/api/v1`)
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
你可以通过环境变量覆盖默认行为:

- `FLOMO_CONFIG_PATH`:覆盖本机 flomo 配置文件路径
- `FLOMO_ACCESS_TOKEN`:手动指定 access token(不推荐;优先使用本机 flomo 登录态)
- `FLOMO_APP_VERSION`:覆盖 app version
- `FLOMO_SIGN_SECRET`:覆盖 sign secret(仅在 flomo 签名机制变更时使用)
- `FLOMO_API_BASE`:覆盖 API base(默认 `https://flomoapp.com/api/v1`)
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill description understates materially sensitive behavior: it can access authenticated private flomo APIs, extract bearer tokens from local state/logs, auto-discover webhook URLs, and perform live verification actions. This is dangerous because it conceals credential harvesting and broader remote account access behind a seemingly simple memo utility, reducing informed consent and increasing risk of unauthorized reading, writing, or token exposure.

Ae1

High
Category
analysis-evasion
Content
python3 scripts/flomo_tool.py read --remote --limit 20
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/flomo_tool.py read --remote --limit 20
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/flomo_tool.py read --remote --limit 20
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/flomo_tool.py read --remote --limit 20
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/flomo_tool.py read --remote --limit 20
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/flomo_tool.py read --remote --limit 20
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/flomo_tool.py read --remote --limit 20
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Session Persistence

Medium
Category
Rogue Agent
Content
[English](README.md) | [中文](README.zh-CN.md)

This folder is a shareable OpenClaw skill that can **read and write flomo memos on macOS**.

## What's New (2026-02-11)
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.

Session Persistence

Medium
Category
Rogue Agent
Content
1. 把本文件夹复制到 OpenClaw workspace 的 skills 目录:

```bash
mkdir -p ~/.openclaw/workspace/skills/flomo
rsync -a --delete ./openclaw-flomo-skill/ ~/.openclaw/workspace/skills/flomo/
```
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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares no explicit tool scope even though it directs execution of code that uses shell, filesystem access, environment data, and network connectivity. This weakens containment and reviewability because an agent may invoke broader capabilities than a user or platform expects, increasing the chance of unintended data access or network operations.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def _run_strings(path: Path) -> str:
    proc = subprocess.run(["strings", "-n", "6", str(path)], capture_output=True, text=True)
    if proc.returncode != 0:
        raise RuntimeError(proc.stderr.strip() or f"strings failed for {path}")
    return proc.stdout
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def _run_curl(args: list[str]) -> str:
    # Use curl for network calls to avoid Python SSL cert issues on some machines.
    proc = subprocess.run(args, capture_output=True, text=True)
    if proc.returncode != 0:
        err = (proc.stderr or "").strip()
        out = (proc.stdout or "").strip()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The skill reads bearer tokens from local flomo config and includes logic to extract authorization tokens from renderer logs, then uses them against undocumented authenticated APIs. This expands the skill from local memo assistance into credential harvesting and privileged remote account access without clear necessity or user disclosure.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The code accesses local flomo config to retrieve access tokens and reads local application data without any user-facing disclosure or consent mechanism. In a note-taking context, that can expose sensitive personal information and account credentials, making the behavior more dangerous than generic local file access.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The stated skill purpose emphasizes local cache search and memo creation, but the implementation also performs authenticated remote reads and metadata enumeration through private APIs. That capability mismatch is dangerous because users may not expect the skill to enumerate or exfiltrate cloud-hosted note content beyond the narrow documented workflow.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The skill sends memo content to a remote webhook/API without any built-in disclosure or confirmation at the point of transmission. Because memos often contain sensitive personal notes, silent network transmission can surprise users and leak private data if the webhook or destination is not explicitly user-controlled.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def write_url_scheme(content: str):
    qs = urllib.parse.urlencode({"content": content})
    url = f"flomo://create?{qs}"
    proc = subprocess.run(["open", url], capture_output=True, text=True)
    if proc.returncode != 0:
        raise RuntimeError(proc.stderr.strip() or "open flomo:// failed")
    return {"ok": True, "mode": "url_scheme", "opened": True}
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'url' from os.getenv (line 187, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
def write_url_scheme(content: str):
    qs = urllib.parse.urlencode({"content": content})
    url = f"flomo://create?{qs}"
    proc = subprocess.run(["open", url], capture_output=True, text=True)
    if proc.returncode != 0:
        raise RuntimeError(proc.stderr.strip() or "open flomo:// failed")
    return {"ok": True, "mode": "url_scheme", "opened": True}
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
Extracting a bearer token from renderer logs is effectively credential scraping from application telemetry, and it is done silently. If abused, this grants authenticated access to the user's flomo account and bypasses normal expectations about how credentials should be handled.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
This file is a Chinese-language README and presents the Chinese version by default, with only a link to English. The policy category calls out language or locale constraints; here the skill documentation is localized to Chinese without an explicit user language choice in the content itself.

Missing User Warnings

Low
Confidence
83% confidence
Finding
Opening the flomo URL scheme causes an external app action that writes content, yet there is no confirmation step or warning. While lower impact than credential access, it still permits unintended data creation or leakage to another application context.

Static analysis

No suspicious patterns detected.