T09 · Insecure Skill Coding Practices
- Location
- src/analyzer.ts:38
- Finding
- Indirect Prompt Injection Through Untrusted News Content<![CDATA[ ## Vulnerability Details **File Location**: `src/analyzer.ts:38-62` and `src/analyzer.ts:78-87` **Vulnerability Type**: Indirect prompt injection caused by unsafe integration of untrusted web content into LLM prompts **Risk Level**: Medium ### Vulnerable Code ```ts const articlesText = rawArticles.map((a, i) => `[${i + 1}] 제목: ${a.title}\nURL: ${a.url}\n내용: ${a.content.slice(0, 300)}` ).join('\n\n'); const systemPrompt = `당신은 뉴스 요약 전문가입니다. 주어진 뉴스 기사 목록을 분석하여 반드시 아래 JSON 형식으로만 응답하십시오. 다른 텍스트, 설명, 마크다운 코드블록 없이 순수 JSON만 출력하십시오. 응답 형식: [ { "title": "기사 제목", "url": "기사 URL", "summary": "3줄 이내 한국어 요약", "published_date": "날짜 또는 알 수 없음", "importance_score": 1~5 사이 정수 (5가 가장 중요) } ]`; const userMessage = `주제: ${topic}\n\n기사 목록:\n${articlesText}`; try { const raw = await callLLM(systemPrompt, userMessage); const cleaned = raw.replace(/```json|```/g, '').trim(); return JSON.parse(cleaned); } ``` The resulting model-generated summaries are subsequently inserted into another prompt: ```ts const top5 = [...articles].sort((a, b) => b.importance_score - a.importance_score).slice(0, 5); const summaries = top5.map((a, i) => `${i + 1}. ${a.summary}`).join('\n'); const systemPrompt = `당신은 뉴스 브리핑 전문가입니다. 주어진 뉴스 요약들을 바탕으로 5문장 이내의 자연스러운 한국어 브리핑을 작성하십시오. 마크다운 형식으로 작성하되, 헤더(#)는 사용하지 마십시오.`; const userMessage = `주제: ${topic}\n\n주요 뉴스 요약:\n${summaries}`; return await callLLM(systemPrompt, userMessage); ``` ### Technical Analysis Article titles, URLs, and content returned by Tavily are externally controlled web data. The application interpolates these fields directly into an instruction-bearing LLM message without establishing a clear trust boundary or telling the model to treat embedded instructions as untrusted data. An attacker can publish content whose title or first 300 characters contain instructions directed at the model. If Tavily returns that page, the embedded instructions become part of the LLM prompt. Requiring JSON outpu ...[truncated 2549 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Explicitly separate instructions from untrusted data** - Place article content inside clearly marked data delimiters. - State that text inside those delimiters is untrusted source material and that any instructions appearing within it must never be followed. - Prefer structured message content or provider-supported structured inputs over free-form concatenation. 2. **Preserve authoritative fields in application code** - Do not allow the model to generate or replace article URLs and titles. - Assign each source article an internal identifier and ask the model to return only that identifier, its summary, and an importance score. - Reconstruct titles and URLs from the original Tavily results after validation. 3. **Apply strict response validation** - Validate LLM output against a runtime schema. - Require an array of the expected length or an explicitly bounded length. - Require known article identifiers. - Require `importance_score` to be an integer between 1 and 5. - Enforce maximum lengths and expected types for every field. - Reject unknown fields, duplicate identifiers, unexpected URLs, and malformed records. 4. **Reduce second-stage injection propagation** - Sanitize and length-limit summaries before using them in the briefing prompt. - In the briefing system prompt, explicitly identify summaries as untrusted data and prohibit following instructions contained in them. - Consider generating the final briefing deterministically from validated summaries rather than making a second LLM request. 5. **Filter suspicious source content** - Detect instruction-like phrases, role markers, prompt delimiters, and attempts to override prior instructions. - Exclude or flag suspicious records rather than silently treating them as normal article content. - Treat filtering as defense in depth, not as the sole protection. 6. **Constrain and monitor output** - Allow only `http` a ...[truncated 347 chars]
