T09 · Insecure Skill Coding Practices
- Location
scripts/bot.py:80- Finding
Unescaped LinkedIn Job Data Enables Telegram HTML Injection
- Content
View full analysis
{s['score']}% - " f"{j['title']}\n" f"🏢 {j['company']}\n" f"📍 {j['location']}\n" f"⏱ {yr_text}" ) ``` Job descriptions are also inserted into HTML messages without escaping: ```python if row[3]: desc = row[3][:1500] msg += f"\nDescription:\n{desc}" self.send(chat_id, msg) ``` The notification script contains the same vulnerable rendering pattern: ```python msg = ( f"{emoji.get(j['recommendation'], '⚪')} {j['score']}% - " f"{j['title']}\n" f"🏢 {j['company']}\n📍 {j['location']}\n⏱ {yr_text}" ) ``` All such messages are sent with HTML parsing enabled: ```python payload = { "chat_id": chat_id, "text": text, "parse_mode": "HTML", "disable_web_page_preview": True, } ``` ### Technical Analysis Job titles, company names, locations, and descriptions originate from externally controlled LinkedIn job listings. The scraper calls `html.unescape`, which converts encoded markup such as `<a href=...>` back into active angle-bracket syntax. These values are persisted and later concatenated directly into Telegram messages using `parse_mode: "HTML"`. Because no `html.escape` or equivalent o ...[truncated 2012 chars]- Remediation
View remediation
{s['score']}% - " f"{safe_title}\n" f"🏢 {safe_company}\n" f"📍 {safe_location}\n" f"⏱ {yr_text}" ) ``` 3. Apply the same encoding to scoring-breakdown strings if they can contain profile or external values. 4. Alternatively, remove `parse_mode: "HTML"` and send plain-text messages where rich formatting is unnecessary. 5. Validate job URLs before adding them to inline buttons. Permit only expected HTTPS LinkedIn job URLs, for example by parsing the URL and verifying the scheme, hostname, and path rather than relying solely on the scraper regex. 6. Add tests containing encoded and literal tags, malformed markup, and crafted links to ensure external content is always rendered as text. ]]>
