T09 · Insecure Skill Coding Practices
- 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. ]]>
