T09 · Insecure Skill Coding Practices
Warning
- Location
- src/analyzer.ts:36
- Finding
- Indirect Prompt Injection Through Untrusted Web Content<![CDATA[ ## Vulnerability Details **File Location**: `src/analyzer.ts:36-81` **Vulnerability Type**: Indirect prompt injection and insufficient LLM output validation **Risk Level**: Medium ### Vulnerable Code ```ts const articlesText = articles.map((a, i) => `[${i + 1}] ${a.title}\nURL: ${a.url}\n내용: ${a.content}` ).join('\n\n'); const systemPrompt = `트렌드 분석 전문가입니다. 주어진 뉴스 기사들을 분석하여 키워드의 트렌드 신호를 판단하십시오. 반드시 아래 JSON 형식으로만 응답하십시오. 다른 텍스트 없이 순수 JSON만 출력하십시오. 신호 정의: - Rising: 언급량/관심도가 빠르게 증가하는 중 - Peaking: 현재 최고조, 곧 하락 가능성 - Declining: 언급량/관심도가 줄어드는 중 - Insufficient_Data: 판단하기에 데이터가 부족함 응답 형식 (JSON 객체 하나): { "signal": "Rising | Peaking | Declining | Insufficient_Data", "score": 0~100 사이 정수, "reason": "판단 근거를 2문장으로 설명", "evidence": ["근거로 사용한 URL 1", "URL 2"] }`; const userMessage = `키워드: "${keyword}"\n분석 기간: ${timeframe}\n\n기사 목록:\n${articlesText}`; try { const raw = await callLLM(systemPrompt, userMessage); const cleaned = raw.replace(/```json|```/g, '').trim(); const parsed = JSON.parse(cleaned); results.push({ keyword, signal: parsed.signal ?? 'Insufficient_Data', score: typeof parsed.score === 'number' ? parsed.score : 0, reason: parsed.reason ?? '', evidence: Array.isArray(parsed.evidence) ? parsed.evidence : [], }); } ``` ### Technical Analysis Article titles, URLs, and content returned by Tavily are externally controlled data. They are concatenated directly into an LLM user message without explicit boundaries identifying them as untrusted content and without an instruction requiring the model to ignore commands embedded in the articles. An attacker can publish content containing instructions such as requests to disregard the trend-analysis task, return a fabricated classification, or insert attacker-selected evidence. If Tavily indexes and returns that content, the LLM may interpret those instructions as part of the conversation. The JSON response is parsed syntactically but not validated semantically. In ...[truncated 1841 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Clearly delimit retrieved article data as untrusted input using structured markers or provider-supported structured content. 2. Add a system-level instruction stating that article text is evidence only and that any commands or instructions inside it must never be followed. 3. Keep system and user content in separate roles for every provider. For Gemini, use the API's system-instruction facility instead of concatenating both into one text part. 4. Validate LLM output with a strict runtime schema: - Permit only `Rising`, `Peaking`, `Declining`, or `Insufficient_Data`. - Require an integer score from 0 through 100. - Require a bounded plain-text reason. - Require an array containing a limited number of valid HTTPS URLs. 5. Accept evidence URLs only if they exactly match URLs from the retrieved Tavily result set. 6. Reject unexpected fields and malformed output instead of silently accepting partially valid values. 7. Consider deterministic classification rules or a second validation pass for security-sensitive or high-impact results. ]]>
