T09 · Insecure Skill Coding Practices
- Location
scripts/build_diagram.py:106- Finding
Arbitrary URL Access Enables Local File Disclosure and Server-Side Request Forgery
- Content
View full analysis
str | None: """Return raw SVG text (cached on disk).""" cache.mkdir(parents=True, exist_ok=True) fname = re.sub(r"[^A-Za-z0-9._-]", "_", url.split("/")[-1]) f = cache / fname try: if not f.exists(): safe = urllib.parse.quote(url, safe=":/?#[]@!$&'()*+,;=~") data = urllib.request.urlopen(safe, timeout=30).read() f.write_bytes(data) return f.read_text("utf-8", "replace") except Exception as e: print(f" ! could not fetch {url}: {e}", file=sys.stderr) return None ``` ```python def resolve_icon(node: dict, cache: Path, providers=None) -> tuple[str | None, str]: """Return (raw_svg_or_None, note). Honors node['icon_url'] if given. A node may set its own 'provider' to override the diagram default.""" if node.get("icon_url"): return fetch_svg(node["icon_url"], cache), node["icon_url"] ``` ### Technical Analysis The diagram specification directly controls the `icon_url` value. That value is passed to `urllib.request.urlopen()` without validating: - The URL scheme - The destination hostname or IP address - Redirect destinations - Loopback, private, or link-local address ranges - The response content type - Whether the response is actually an SVG - The maximum response size `urllib.request.urlopen()` supports schemes other than HTTPS, including `file:`. Consequently, an untrusted diagram specification can cause the process to read a local file accessible to the current user. The resulting bytes are cached, decoded as text, and subsequently embedded into generated Excalidraw or draw.io output. HTTP and HTTPS destinations are similarly unrestricted. This creates a server-side request forger ...[truncated 2394 chars]- Remediation
View remediation
