T09 · Insecure Skill Coding Practices
- Location
tarot_skill/scripts/generate_spread_image.py:89- Finding
Unrestricted Image Source Handling Enables SSRF and Local File Disclosure
- Content
View full analysis
bytes: if "://" in url and not url.startswith("file:"): req = urllib.request.Request(url, headers=BROWSER_HEADERS) with urllib.request.urlopen(req, timeout=TIMEOUT) as r: return r.read() path = url.replace("file:", "").strip() return Path(path).read_bytes() ``` The attacker-controlled value reaches this function through the reading JSON: ```python raw = Path(args.input).read_text(encoding="utf-8") if args.input else sys.stdin.read() reading = json.loads(raw) if not reading.get("ok") or not reading.get("spreadType") or not reading.get("spread"): print("Invalid reading: need ok=true, spreadType, and spread array.", file=sys.stderr) sys.exit(1) spread_type = reading["spreadType"] layout = SPREAD_LAYOUTS.get(spread_type) if not layout or len(layout["slots"]) != len(reading["spread"]): print( f'Layout for "{spread_type}" has {len(layout["slots"]) if layout else 0} slots, ' f"but spread has {len(reading['spread'])} cards.", file=sys.stderr, ) sys.exit(1) cw = layout["canvas_width"] ch = layout["canvas_height"] bg_hex = layout["background_color"].lstrip("#") bg = tuple(int(bg_hex[i : i + 2], 16) for i in (0, 2, 4)) base = Image.new("RGB", (cw, ch), bg) images_dir = Path(args.images_dir) if args.images_dir else None if images_dir is None: default_cards = _ROOT / "cards" _ensure_card_images_once(default_cards) if default_cards.is_dir(): images_dir = default_cards for i, pos in enumerate(reading["spread"]): slot = layout["slots"][i] card_id = pos["card"]["id"] fallback_url = pos["card"].get("image") or get_card_image_url(card_id) url = get_card_url(card_i ...[truncated 3346 chars]- Remediation
View remediation
