T09 · Insecure Skill Coding Practices
- Location
axiom_markdown_link_auditor.py:83- Finding
Server-Side Request Forgery Through Unrestricted Markdown URLs
- Content
View full analysis
Vulnerability Details
File Location:
axiom_markdown_link_auditor.py:83-95, 131-137
Vulnerability Type: Server-Side Request Forgery (SSRF)
Risk Level: MediumVulnerable Code
python def check_url(url: str, timeout: int = 5) -> dict: """ Check a single URL via HEAD request. Returns dict with: url, status, ok, error """ # Skip non-HTTP schemes if not url.startswith(("http://", "https://")): return { "url": url, "status": None, "ok": None, "skipped": True, "reason": f"Non-HTTP scheme: {url.split(':')[0]}", } try: req = urllib.request.Request(url, method="HEAD") req.add_header("User-Agent", "axiom-markdown-link-auditor/0.1") with urllib.request.urlopen(req, timeout=timeout) as resp: return { "url": url, "status": resp.status, "ok": 200 <= resp.status < 400, "skipped": False, }python if check_remote: broken = [] for link in links: url = link["url"] if not url.startswith(("http://", "https://")): continue check = check_url(url, timeout=timeout) if not check.get("ok") and not check.get("skipped"): broken.append({**link, **check})Technical Analysis
The application extracts URLs from potentially untrusted Markdown and sends
HEADrequests to them when remote checking is enabled. Validation only checks whether the string starts withhttp://orhttps://.It does not resolve and validate the destination address, reject loopback or private networks, block link-local and reserved addresses, enforce a destination allowlist, or revalidate redirect targets. Consequently, an attacker who controls audited Markdown can cause the host running the auditor to initiate requ ...[truncated 1975 chars]
- Remediation
View remediation
Remediation Suggestions
- Parse URLs with
urllib.parse.urlsplit()and require the normalized scheme to be exactlyhttporhttps. - Reject URLs containing credentials, malformed hostnames, ambiguous numeric IP representations, or unsupported ports.
- Resolve the hostname before connecting and validate every resolved address with Python's
ipaddressmodule. - Reject loopback, private, link-local, multicast, reserved, and unspecified IPv4 and IPv6 addresses.
- Prevent DNS rebinding by ensuring the connection uses an address that was validated, rather than resolving the hostname independently after validation.
- Disable redirects or explicitly validate the scheme, hostname, and resolved addresses of every redirect target.
- Prefer an explicit domain allowlist in CI or other sensitive environments.
- Apply network-level egress controls so the process cannot reach metadata services or internal management networks.
- Preserve strict connection and overall request timeouts, and add limits for the number of checked links.
- Add tests covering loopback, RFC1918 private ranges, IPv6 local addresses, link-local metadata addresses, redirects to internal hosts, and DNS rebinding scenarios.
- Clearly document that
--check-remoteperforms network requests based on URLs contained in the audited document.
- Parse URLs with
