T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/render_html.py:108
- Finding
- Unvalidated Social-Network URI Schemes Allow Executable Links<![CDATA[ ## Vulnerability Details **File Location**: `scripts/render_html.py:108-113` and `scripts/render_html.py:151-160` **Vulnerability Type**: Unsafe URI handling in generated HTML **Risk Level**: Medium ### Vulnerable Code ```python def social_link(network: str, username: str) -> str: if network == "GitHub": return f"https://github.com/{username}" if network == "LinkedIn": return f"https://linkedin.com/in/{username}" return username ``` The returned value is subsequently inserted into an anchor: ```python tag = "a" if item["href"] else "span" href = f' href="{esc(item["href"])}"' if item["href"] else "" rendered.append( f'<{tag} class="connection connection--{esc(item["kind"])}"{href}>{icon}<span>{esc(item["label"])}</span></{tag}>' ) ``` ### Technical Analysis For recognized GitHub and LinkedIn entries, the renderer constructs an HTTPS URL. For every other network name, however, `social_link()` returns the user-controlled `username` unchanged. The `esc()` function applies HTML entity encoding, which prevents breaking out of the `href` attribute, but it does not validate the URI scheme. Consequently, values using schemes such as `javascript:` can remain executable when the generated link is clicked. For example, the following resume data can create an executable link: ```yaml social_networks: - network: Custom username: "javascript:alert(document.domain)" ``` URI validation must be performed independently of HTML escaping. ### Attack Path 1. An attacker supplies or modifies a resume YAML file. 2. The attacker adds an unrecognized social-network name. 3. The corresponding `username` contains a `javascript:` URI or another unsafe scheme. 4. The user invokes `scripts/render_html.py` on that YAML file. 5. The renderer places the value into an anchor's `href` attribute. 6. The generated resume is opened or shared with another user. 7. If the malicious link is clicked, the browser executes the URI in the generate ...[truncated 552 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Parse every generated link using `urllib.parse.urlparse`. - Permit only an explicit scheme allowlist, such as `https` and `http` for websites and social profiles. - Handle `mailto` and `tel` only in their dedicated contact fields. - Reject `javascript`, `data`, `file`, `vbscript`, and all unknown schemes. - Maintain an explicit map of supported social networks rather than returning unknown usernames as links. - Render unsupported social-network values as plain text. - Add regression tests covering mixed-case and whitespace-obfuscated schemes, including `JaVaScRiPt:`, leading control characters, and percent-encoded variants. A safe pattern would be: ```python from urllib.parse import urlparse ALLOWED_WEB_SCHEMES = {"https", "http"} def validate_web_url(value: str) -> str: normalized = normalize_url(value) parsed = urlparse(normalized) if parsed.scheme.lower() not in ALLOWED_WEB_SCHEMES: return "" return normalized ``` ]]>
