T09 · Insecure Skill Coding Practices
Warning
- Location
- monitor.py:277
- Finding
- Unsanitized Indeed Content Persisted into the Markdown Lead Database<![CDATA[ ## Vulnerability Details **File Location**: `monitor.py:277-281` **Vulnerability Type**: Persistent Markdown content injection **Risk Level**: Medium ### Vulnerable Code ```python with open(LEADS_FILE, "a") as f: f.write(f"\n\n---\n\n## 📋 Indeed Scan — {timestamp}\n\n") f.write("| Company | Title | Location | Pain Signal |\n") f.write("|---------|-------|----------|-------------|\n") for lead in all_leads: signals = ", ".join(lead.get("pain_signals", [])) f.write(f"| {lead['company']} | {lead['title']} | {lead['location']} | {signals} |\n") ``` ### Technical Analysis The values written as `company`, `title`, and `location` are derived from public Indeed content. The application writes these untrusted values directly into `MASTER_LEAD_LIST.md` without escaping Markdown metacharacters, filtering HTML, removing control characters, or applying a restrictive character and length policy. An attacker who can publish or influence an Indeed job listing can place characters such as pipes, brackets, links, image syntax, or HTML in a parsed field. Pipe characters can create additional table cells or rows, while Markdown and HTML constructs can introduce attacker-controlled links or rendered content. If the generated lead database is subsequently included in an AI agent's context, instruction-like text from a listing could also act as persistent indirect prompt-injection content. That consequence depends on a downstream system treating the Markdown file as trusted instructions rather than untrusted data; this script itself does not execute the injected text. ### Attack Path 1. An attacker publishes or modifies an Indeed job listing containing crafted Markdown, HTML, table delimiters, or instruction-like text in a field that the parser may identify as a company name, job title, or location. 2. The user runs `monitor.py --save`. 3. The script retrieves and parses the attacker-influenced listing. 4. The crafted value reaches ...[truncated 1089 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Treat every scraped field as untrusted data. 2. Escape Markdown table delimiters and formatting characters before writing values. At minimum: - Replace `\` with `\\`. - Replace `|` with `\|`. - Remove carriage returns and replace newlines with spaces. - Encode or remove HTML delimiters such as `<` and `>`. - Neutralize Markdown link and image syntax where rendered links are unnecessary. 3. Enforce maximum lengths and an allowlist of expected characters for company names, titles, and locations. 4. Prefer a structured storage format such as JSON or properly generated CSV. If Markdown is required, generate it from validated structured data using a safe serialization function. 5. Label scraped text as untrusted external content when supplying it to an AI system. Keep data fields separate from system or task instructions and instruct the downstream agent never to follow instructions contained in lead records. 6. Consider rejecting records containing control characters, embedded URLs, multiline content, or instruction-like payloads. For example: ```python def escape_markdown_cell(value, max_length=200): value = str(value or "") value = value.replace("\r", " ").replace("\n", " ") value = value.replace("\\", "\\\\").replace("|", "\\|") value = value.replace("<", "<").replace(">", ">") return value[:max_length] for lead in all_leads: company = escape_markdown_cell(lead["company"]) title = escape_markdown_cell(lead["title"]) location = escape_markdown_cell(lead["location"]) signals = escape_markdown_cell(", ".join(lead.get("pain_signals", []))) f.write(f"| {company} | {title} | {location} | {signals} |\n") ``` ]]>
