Back to skill

Security audit

flomo-sync

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real flomo backup tool, but it handles session tokens, remote attachment URLs, and generated file paths with more authority than is safely scoped.

Install only if you are comfortable giving this script access to your flomo account and writing all notes and attachments to local disk. Use a dedicated private output folder, exclude exports and .flomo.config from git/cloud sharing, prefer --no-download unless you need attachments, and treat the copied token like a password. The publisher should sanitize slugs, enforce output-directory containment, restrict attachment hosts and sizes, and provide a safer authentication story before broad 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

Warning
Location
scripts/flomo-sync.py:267
Finding
Unrestricted Attachment Retrieval Enables SSRF and Unbounded Disk Consumption<![CDATA[ ## Vulnerability Details **File Location**: `scripts/flomo-sync.py`, lines 244-280 and 339-343 **Vulnerability Type**: Server-Side Request Forgery (SSRF) and unrestricted resource download **Risk Level**: Medium ### Vulnerable Code ```python def download_attachment( url: str, name: str, slug: str, created_at: str, images_dir: Path, ) -> str | None: """ Download an attachment into images_dir/YYYY/MM/DD/{slug}_{name}. """ ext = _ext_from_url(url) or Path(name).suffix.lower() if ext not in IMAGE_EXTS and ext not in AUDIO_EXTS: return None try: dt = datetime.strptime(created_at[:10], "%Y-%m-%d") date_path = Path(f"{dt.year:04d}") / f"{dt.month:02d}" / f"{dt.day:02d}" except (ValueError, TypeError): date_path = Path("unknown") dest_dir = images_dir / date_path dest_dir.mkdir(parents=True, exist_ok=True) safe_name = name.replace("/", "_").replace("\\", "_") if not Path(safe_name).suffix and ext: safe_name = safe_name + ext filename = f"{slug}_{safe_name}" dest_path = dest_dir / filename if dest_path.exists(): return str(Path("images") / date_path / filename) try: resp = requests.get(url, timeout=30, stream=True) resp.raise_for_status() with open(dest_path, "wb") as f: for chunk in resp.iter_content(chunk_size=65536): f.write(chunk) return str(Path("images") / date_path / filename) except Exception as e: print(f" ⚠ Download failed {name}: {e}", flush=True) return None ``` The function is invoked for attachment URLs supplied by the remote API: ```python if images_dir is not None: local_path = download_attachment(url, name, slug, created_at, images_dir) ``` ### Technical Analysis Attachment URLs are taken from the flomo API response and passed directly to `requests.get()`. The implementation does not validate: - The URL scheme - The desti ...[truncated 2516 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain an explicit allowlist of HTTPS attachment hosts operated by or approved for flomo. 2. Reject non-HTTPS schemes and URLs containing embedded credentials. 3. Resolve the destination hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IP ranges for both IPv4 and IPv6. 4. Disable redirects with `allow_redirects=False`, or validate the scheme, hostname, and resolved address at every redirect. 5. Set a maximum attachment size using both `Content-Length` and a running byte counter while streaming. 6. Delete partially downloaded files when an error or size-limit violation occurs. 7. Validate the response MIME type against an explicit image/audio allowlist. 8. Download to a temporary file and atomically rename it only after validation succeeds. 9. Consider making attachment downloading opt-in rather than enabled by default. 10. Where practical, use flomo-provided stable attachment identifiers rather than arbitrary response URLs. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/flomo-sync.py:390
Finding
Unsanitized Memo Slugs Can Escape the Intended Output Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/flomo-sync.py`, lines 267-272 and 374-415 **Vulnerability Type**: Path traversal and arbitrary file write **Risk Level**: High ### Vulnerable Code The attachment filename incorporates the remote slug without sanitization: ```python safe_name = name.replace("/", "_").replace("\\", "_") if not Path(safe_name).suffix and ext: safe_name = safe_name + ext filename = f"{slug}_{safe_name}" dest_path = dest_dir / filename if dest_path.exists(): return str(Path("images") / date_path / filename) try: resp = requests.get(url, timeout=30, stream=True) resp.raise_for_status() with open(dest_path, "wb") as f: for chunk in resp.iter_content(chunk_size=65536): f.write(chunk) ``` The same untrusted slug is used to construct Markdown filenames: ```python def _memo_filename(memo: dict) -> str: slug = memo.get("slug") or f"memo_{id(memo)}" parts: list[str] = [] created_at = str(memo.get("created_at") or "") if len(created_at) >= 10: parts.append(created_at[:10]) tags = memo.get("tags") or [] if tags: leaf = tags[0].split("/")[-1] leaf = re.sub(r'[\\/:*?"<>|]', "", leaf).strip() if leaf: parts.append(leaf) content_md = html_to_md(memo.get("content") or "") content_clean = re.sub(r"#\S+", "", content_md) chars = re.findall(r"[\u4e00-\u9fff\w]", content_clean) preview = "".join(chars[:6]) if preview: parts.append(preview) parts.append(slug) return "_".join(parts) + ".md" def write_memo(memo: dict, output_dir: Path, images_dir: Path | None) -> str: new_filename = _memo_filename(memo) new_path = output_dir / new_filename slug = memo.get("slug") or "" new_content = memo_to_md_text(memo, images_dir) status = "created" if slug: for old in output_dir.glob(f"*_{slug}.md"): if old.name != new_filename: old.unlink() ...[truncated 2817 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate every slug against a conservative allowlist, for example: ```python def safe_slug(value: object) -> str: slug = re.sub(r"[^A-Za-z0-9_-]", "_", str(value or "")) slug = slug.strip("_") if not slug: raise ValueError("Invalid memo slug") return slug[:128] ``` 2. Apply the same validation before using a slug in Markdown filenames, attachment filenames, and glob patterns. 3. Resolve every destination path and verify containment before writing: ```python root = output_dir.resolve() destination = (root / filename).resolve() if destination != root and root not in destination.parents: raise ValueError("Destination escapes output directory") ``` 4. Perform an equivalent containment check against `images_dir.resolve()` for attachments. 5. Reject absolute paths and any filename containing path separators before joining paths. 6. Avoid using untrusted values directly in glob expressions. Locate existing files through validated identifiers or a local index instead. 7. Use exclusive creation or atomic temporary-file replacement where overwriting is not explicitly required. 8. Add tests for `../`, nested traversal, absolute POSIX paths, Windows drive paths, UNC paths, encoded separators, and mixed slash styles. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:20
Finding
Runtime Dependencies Are Installed Without Version or Integrity Pinning<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, line 20 **Vulnerability Type**: Unpinned third-party dependencies **Risk Level**: Low ### Vulnerable Code ```bash pip install requests markdownify ``` ### Technical Analysis The documented installation command resolves and installs the latest versions of `requests`, `markdownify`, and their transitive dependencies at installation time. It does not specify reviewed versions, cryptographic hashes, a lock file, or an isolated environment. As a result, the actual code installed by users can change after the Skill has been reviewed. A compromised package release, compromised transitive dependency, or future incompatible version could execute with the privileges of the user performing the installation. No evidence was found that either package name is intentionally malicious or typosquatted. The issue is the mutable and unverifiable dependency resolution process. ### Attack Path 1. A direct or transitive dependency publishes a compromised release, or its distribution account or package artifact is compromised. 2. A user follows the documented `pip install requests markdownify` command. 3. Package resolution selects the compromised version because no reviewed version is pinned. 4. Installation or later import executes attacker-controlled package code. 5. The package code operates with the permissions of the user running the installer or synchronization script. ### Impact Assessment A compromised dependency could access the flomo token, memo contents, local files available to the current user, and network resources reachable from the host. It could also modify files within the user's permissions. The practical likelihood is lower than the code-level vulnerabilities because exploitation requires a supply-chain compromise or unsafe future release. Nevertheless, the command does not provide reproducible or integrity-verified installation. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Provide a reviewed requirements or lock file with exact versions. 2. Include hashes for every direct and transitive artifact, and install with hash enforcement: ```bash python -m pip install --require-hashes -r requirements.txt ``` 3. Install dependencies in a dedicated virtual environment rather than the system Python environment. 4. Use an automated dependency update process that reviews release notes and reruns security tests before changing pins. 5. Periodically scan locked dependencies for published vulnerabilities. 6. Where feasible, document the trusted package index and prevent fallback to unapproved indexes. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (17)

Ae1

High
Category
analysis-evasion
Content
description: 将 flomo 所有记录 memo 同步/备份到本地 Markdown 文件的工具。使用 scripts/flomo-sync.py 脚本通过 flomo API 拉取 memo,支持增量同步、附件下载、多文件输出。当用户需要备份 flomo、同步 flomo memo 到本地、导出 flom
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
description: 将 flomo 所有记录 memo 同步/备份到本地 Markdown 文件的工具。使用 scripts/flomo-sync.py 脚本通过 flomo API 拉取 memo,支持增量同步、附件下载、多文件输出。当用户需要备份 flomo、同步 flomo memo 到本地、导出 flom
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
description: 将 flomo 所有记录 memo 同步/备份到本地 Markdown 文件的工具。使用 scripts/flomo-sync.py 脚本通过 flomo API 拉取 memo,支持增量同步、附件下载、多文件输出。当用户需要备份 flomo、同步 flomo memo 到本地、导出 flom
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
description: 将 flomo 所有记录 memo 同步/备份到本地 Markdown 文件的工具。使用 scripts/flomo-sync.py 脚本通过 flomo API 拉取 memo,支持增量同步、附件下载、多文件输出。当用户需要备份 flomo、同步 flomo memo 到本地、导出 flom
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
description: 将 flomo 所有记录 memo 同步/备份到本地 Markdown 文件的工具。使用 scripts/flomo-sync.py 脚本通过 flomo API 拉取 memo,支持增量同步、附件下载、多文件输出。当用户需要备份 flomo、同步 flomo memo 到本地、导出 flom
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
description: 将 flomo 所有记录 memo 同步/备份到本地 Markdown 文件的工具。使用 scripts/flomo-sync.py 脚本通过 flomo API 拉取 memo,支持增量同步、附件下载、多文件输出。当用户需要备份 flomo、同步 flomo memo 到本地、导出 flom
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
description: 将 flomo 所有记录 memo 同步/备份到本地 Markdown 文件的工具。使用 scripts/flomo-sync.py 脚本通过 flomo API 拉取 memo,支持增量同步、附件下载、多文件输出。当用户需要备份 flomo、同步 flomo memo 到本地、导出 flom
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
description: 将 flomo 所有记录 memo 同步/备份到本地 Markdown 文件的工具。使用 scripts/flomo-sync.py 脚本通过 flomo API 拉取 memo,支持增量同步、附件下载、多文件输出。当用户需要备份 flomo、同步 flomo memo 到本地、导出 flom
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ssd 3

High
Confidence
98% confidence
Finding
Instructing users to recover and reuse the live Authorization header from browser network traffic effectively asks them to repurpose an active session credential outside its intended context. If exposed via terminal history, screenshots, logs, shell scripts, or plaintext config files, an attacker could use that bearer token to access the user's flomo data until it expires or is revoked.

Ssd 3

High
Confidence
98% confidence
Finding
The CLI help repeats unsafe operational guidance, increasing the chance users will place a sensitive bearer token directly on the command line, where it may be captured by shell history, process listings, CI logs, or support transcripts. Repetition in built-in help makes the insecure workflow part of normal operation rather than an exceptional workaround.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill advertises behavior that reads a local config file, writes Markdown and attachments to disk, and calls the flomo API, but it declares no explicit tool scope or permission boundary. Without a declared scope, an agent may invoke file and network capabilities more broadly than users expect, increasing the chance of unintended data exposure or filesystem writes.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The description is written as a direct operational instruction in Chinese and the file provides no indication that users may choose another language or that the skill is limited to a Chinese-only compliance or regional context. Under the policy criteria, forcing a specific language without opt-in is a natural-language policy violation.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The instructions tell users to extract a live Authorization bearer token from browser developer tools and reuse it in the script, but do not treat it as a sensitive credential or warn about scope, storage, and leakage risks. This encourages insecure credential handling and normalizes copying session-equivalent secrets into local files and command histories.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The script hardcodes a reverse-engineered signing secret and reproduces a private API signature flow to impersonate the official web client. That exceeds a normal backup tool's required trust boundary and creates a durable capability to access undocumented endpoints in ways the platform did not intend, increasing legal, security, and account-risk exposure if reused or adapted.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script writes markdown files, downloads attachments, and deletes prior memo files with matching slugs when filenames change. While the tool's purpose is synchronization, the deletion behavior is not clearly disclosed to the user in nearby comments, prompts, or CLI messaging, so users may not realize existing files in the target directory can be removed or replaced.

Missing User Warnings

Low
Confidence
90% confidence
Finding
The skill describes syncing all memos and downloading attachments locally, but it does not prominently warn that this will persist potentially sensitive personal notes and media onto disk in bulk. In this context, the data being synced is likely private, so insufficient disclosure can lead to accidental local retention, backup propagation, or storage in unsafe locations.

Intent-Code Divergence

Low
Confidence
79% confidence
Finding
The file output section says the tool may generate `flomo_export.md` in a `--single` mode, but the parameter table above lists only `--token`, `--dir`, `--after`, and `--no-download`. Within this provided file, that documented mode is not otherwise defined, creating an intent/documentation mismatch that could mislead users about supported behavior.

Static analysis

No suspicious patterns detected.