T09 · Insecure Skill Coding Practices
Warning
- Location
- agents/classifier_agent.py:44
- Finding
- Indirect Prompt Injection Through Untrusted Paper Content<![CDATA[ ## Vulnerability Details **File Location**: `agents/classifier_agent.py:44-52`, `agents/reader_agent.py:60-96`, `agents/summary_agent.py:37-55`, `paper_reader/latex_parser.py:158-171`, `prompts/reader_system.md:1-12` **Vulnerability Type**: Indirect prompt injection caused by insufficient separation of instructions and untrusted content **Risk Level**: Medium ### Complete Vulnerable Code Snippets `agents/classifier_agent.py:44-52`: ```python def classify(self, title: str, abstract: str) -> str: """Return the category name for a single paper.""" user_input = ( f"Please classify the following paper:\n\n" f"**Title**: {title}\n\n" f"**Abstract**: {abstract}" ) try: result = self.chain.invoke( {"messages": [{"role": "user", "content": user_input}]} ) ``` The original prompt text is written in Chinese, but the data flow shown above is equivalent: attacker-controlled `title` and `abstract` values are directly interpolated into an LLM message. `agents/reader_agent.py:60-96`: ```python title = paper_info["title"] authors = ", ".join(paper_info.get("authors", [])) arxiv_id = paper_info["arxiv_id"] # Pass 1 logger.info(f" [Pass 1] {title[:60]}...") first_pass_text = truncate_text(parsed_paper.first_pass_text, 30000) user_msg_1 = FIRST_PASS_USER.format( title=title, authors=authors, arxiv_id=arxiv_id, first_pass_content=first_pass_text, ) result_1 = self.chain.invoke( {"messages": [{"role": "user", "content": user_msg_1}]} ) initial_summary = result_1["messages"][-1].content # Pass 2 logger.info(f" [Pass 2] {title[:60]}...") main_body = truncate_text(parsed_paper.main_body_text, 50000) if not main_body.strip(): return self._format_final_notes(paper_info, initial_summary) user_msg_2 = SECOND_PASS_USER.format( initial_summary=initial_summary, main_body=main_body, ) result_2 = self.chain.invoke( {"messages": [{"role": "user", "content": user_msg_2}]} ) ...[truncated 4388 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Add an explicit system-level trust-boundary rule stating that titles, abstracts, LaTeX, summaries, and appendices are untrusted reference material and that instructions contained in them must never be followed. 2. Place untrusted fields inside clearly identified structured containers, such as JSON fields or dedicated document blocks, and describe those blocks as data rather than instructions. 3. Use separate messages or typed structured inputs for task instructions and document content where supported by the LLM framework. 4. Require structured classifier output using schema validation rather than extracting arbitrary JSON from free-form output. 5. Validate generated category names, confidence values, appendix decisions, and final output against strict schemas and size limits. 6. Do not treat a previous model response as trusted. Mark `initial_summary` as untrusted model-generated context before inserting it into a later prompt. 7. Consider detecting or neutralizing common instruction-injection patterns in retrieved documents. Such filtering should supplement, not replace, system-level trust-boundary instructions. 8. Add adversarial tests containing paper text such as “ignore prior instructions,” forged system-message markers, deceptive Markdown links, and fake appendix directives. 9. If tools are added to these agents in the future, enforce tool allowlists, argument validation, least privilege, and explicit user confirmation for side effects. ]]>
