Back to skill

Security audit

Agent Trend Radar

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed web-and-LLM trend analyzer, with ordinary integration risks but no evidence of hidden, destructive, or deceptive behavior.

Install only if you are comfortable providing Tavily and LLM provider API keys and sending keyword queries plus retrieved article snippets to those providers. Treat results as advisory because untrusted web content can influence the LLM output; for production use, add input size limits, strict schema validation, provider/data-transfer disclosure, and dependency updates.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

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. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/handler.ts:8
Finding
Incomplete Runtime Validation Enables Job Crashes and API Cost Amplification<![CDATA[ ## Vulnerability Details **File Location**: `src/handler.ts:8-34` **Vulnerability Type**: Improper input validation and uncontrolled input size **Risk Level**: Medium ### Vulnerable Code ```ts const input = payload as TrendRadarInput; // 단계 1: keywords 배열 유효성 검사 if (!Array.isArray(input.keywords) || input.keywords.length === 0) { return makeError(agentId, jobId, 'keywords는 1개 이상의 문자열 배열이어야 합니다.'); } // 단계 2: keywords 개수 제한 (최대 5개) if (input.keywords.length > 5) { return makeError(agentId, jobId, 'keywords는 최대 5개까지 허용됩니다.'); } // 단계 3: 빈 문자열 키워드 필터링 const keywords = input.keywords.map(k => k.trim()).filter(k => k.length > 0); if (keywords.length === 0) { return makeError(agentId, jobId, '유효한 키워드가 없습니다.'); } // 단계 4: 기본값 설정 const timeframe = input.timeframe ?? '7d'; const region = input.region ?? 'global'; ``` ### Technical Analysis The statement `payload as TrendRadarInput` is only a TypeScript compile-time assertion. It does not validate untrusted values at runtime. The handler accesses `input.keywords` before confirming that `payload` is a non-null object. A `null` payload can therefore cause a property-access exception. Although the code verifies that `keywords` is an array, it does not verify that every element is a string before invoking `trim()`. Values such as numbers, objects, or `null` can cause a runtime exception. The handler also does not impose length limits on individual keywords, `timeframe`, or `region`. It does not verify that `timeframe` is one of the declared values (`1d`, `7d`, or `30d`) or that `region` is a supported bounded string. Oversized values are incorporated into Tavily queries and later into LLM prompts. The maximum of five keyword entries limits request multiplication but does not prevent a small number of extremely large strings from consuming network bandwidth, tokens, processing time, or paid API quota. ### Attack Path Crash path: 1. An attacker submits `null` as the payload or supplies an array c ...[truncated 1128 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate that `payload` is a non-null plain object before accessing any properties. 2. Use a runtime schema validator such as Zod, JSON Schema, or an equivalent explicit validation layer. 3. Require `keywords` to contain between one and five strings. 4. Apply a conservative maximum length to each keyword and reject control characters where unnecessary. 5. Validate `timeframe` against the exact allowlist `1d`, `7d`, and `30d`. 6. Restrict `region` to a documented allowlist or enforce strict format and length constraints. 7. Set a maximum serialized request size before processing the payload. 8. Add Tavily and LLM request timeouts, concurrency limits, per-caller rate limits, and usage quotas. 9. Catch top-level validation and processing exceptions and return a structured error without exposing internal details. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
src/llm/gemini.ts:20
Finding
Gemini API Key Transmitted in a URL Query Parameter<![CDATA[ ## Vulnerability Details **File Location**: `src/llm/gemini.ts:20-36` **Vulnerability Type**: Sensitive credential exposure through URL logging **Risk Level**: Low ### Vulnerable Code ```ts const url = `https://generativelanguage.googleapis.com/v1/models/${this.modelName}:generateContent?key=${this.apiKey}`; const payload = { contents: [{ parts: [{ text: `${systemPrompt}\n\n${userMessage}` }] }], generationConfig: { maxOutputTokens: 1024, } }; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); ``` ### Technical Analysis The Google API key is embedded directly in the request URL as a query parameter. The request uses HTTPS and targets the fixed Google Generative Language endpoint, so the key is not sent in plaintext over the network. However, URLs are commonly captured by reverse proxies, outbound gateways, application performance monitoring systems, exception telemetry, and diagnostic tooling. Query parameters are more likely to appear in logs than credentials carried in dedicated authentication headers. Anyone with access to unredacted request telemetry could recover the API key and use it outside the application. No evidence was found that this project explicitly logs the constructed Gemini URL. The risk arises from deployment infrastructure or dependency-level diagnostics that may record full URLs. ### Attack Path 1. The application calls the Gemini provider. 2. The Google API key is included in the complete request URL. 3. A proxy, network-monitoring product, tracing system, or diagnostic component records the full URL without redaction. 4. A user with access to those logs extracts the query parameter. 5. The exposed key is used to make unauthorized requests within its provider-side permissions and quota. ### Impact Assessment A leaked key may allow unauthorized Gemini API consumption, quota exhaustion, o ...[truncated 363 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer a provider-supported authentication header rather than placing the key in the URL. 2. If the API requires a query parameter, configure proxies, tracing systems, HTTP clients, and error-reporting tools to redact the `key` parameter. 3. Never include the complete request URL in application error messages or logs. 4. Restrict the Google API key to the minimum required API and permitted usage context. 5. Apply provider-side quotas, usage alerts, and billing limits. 6. Rotate the key if there is any possibility that complete request URLs have already been retained. 7. Store the key only in an approved secret manager or protected environment variable. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (32)

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
If the implementation only contains generic LLM abstraction and lacks the claimed trend detection/classification features, then the skill is materially misrepresented. Misrepresentation is dangerous in this context because it can be used to obtain broader trust, permissions, or deployment approval than a generic LLM wrapper would otherwise receive.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
If the implementation only contains generic LLM abstraction and lacks the claimed trend detection/classification features, then the skill is materially misrepresented. Misrepresentation is dangerous in this context because it can be used to obtain broader trust, permissions, or deployment approval than a generic LLM wrapper would otherwise receive.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
If the implementation only contains generic LLM abstraction and lacks the claimed trend detection/classification features, then the skill is materially misrepresented. Misrepresentation is dangerous in this context because it can be used to obtain broader trust, permissions, or deployment approval than a generic LLM wrapper would otherwise receive.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
If the implementation only contains generic LLM abstraction and lacks the claimed trend detection/classification features, then the skill is materially misrepresented. Misrepresentation is dangerous in this context because it can be used to obtain broader trust, permissions, or deployment approval than a generic LLM wrapper would otherwise receive.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
If the implementation only contains generic LLM abstraction and lacks the claimed trend detection/classification features, then the skill is materially misrepresented. Misrepresentation is dangerous in this context because it can be used to obtain broader trust, permissions, or deployment approval than a generic LLM wrapper would otherwise receive.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
If the implementation only contains generic LLM abstraction and lacks the claimed trend detection/classification features, then the skill is materially misrepresented. Misrepresentation is dangerous in this context because it can be used to obtain broader trust, permissions, or deployment approval than a generic LLM wrapper would otherwise receive.

Known Vulnerable Dependency: form-data==4.0.5 — 1 advisory(ies): CVE-2026-12143 (form-data: CRLF injection in form-data via unescaped multipart field names and f)

High
Category
Supply Chain
Confidence
89% confidence
Finding
This lockfile pins transitive dependency form-data to 4.0.5, which is reported as affected by a high-severity CRLF injection issue when multipart field names are not properly escaped. Even though this file is only a dependency manifest and does not prove the vulnerable code path is exercised, a real vulnerable package version is present and could be exploited if the skill builds multipart requests from untrusted input through libraries that rely on form-data.

Credential Access

High
Category
Privilege Escalation
Content
import * as dotenv from 'dotenv';
import * as path from 'path';

// .env 직접 로드
dotenv.config({ path: path.resolve(__dirname, '../.env') });

console.log('[Fetcher] API Key Check:', process.env.TAVILY_API_KEY ? 'FOUND' : 'MISSING');
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
import * as dotenv from 'dotenv';
import * as path from 'path';

// .env 직접 로드
dotenv.config({ path: path.resolve(__dirname, '../.env') });

console.log('[Fetcher] API Key Check:', process.env.TAVILY_API_KEY ? 'FOUND' : 'MISSING');
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
import * as path from 'path';

// .env 직접 로드
dotenv.config({ path: path.resolve(__dirname, '../.env') });

console.log('[Fetcher] API Key Check:', process.env.TAVILY_API_KEY ? 'FOUND' : 'MISSING');
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
import * as path from 'path';

// .env 직접 로드
dotenv.config({ path: path.resolve(__dirname, '../.env') });

console.log('[Fetcher] API Key Check:', process.env.TAVILY_API_KEY ? 'FOUND' : 'MISSING');
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The primary skill description is written only in Korean, which imposes a language choice on users without any opt-in or alternative locale guidance. The policy allows language constraints only when users are given a choice or when the locale restriction is clearly documented and justified.

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill metadata declares no explicit tool scope or permissions, yet the associated implementation reportedly uses network access and environment variables. This is dangerous because consumers reviewing only the manifest cannot tell that the skill may exfiltrate data to external services or use sensitive credentials at runtime.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The natural-language description, feature list, and usage context are presented exclusively in Korean, and there is no indication that users may choose another language or that the skill is intentionally limited to a Korean-speaking context. Under the policy, forcing a specific language without opt-in is a reportable natural-language policy violation.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The analyzer sends raw article titles, URLs, and content to an external LLM provider for classification without any indication of consent, minimization, or provider-boundary disclosure in this component. If the article corpus includes proprietary, licensed, personal, or otherwise sensitive text, this can cause unintended third-party data exposure and retention outside the skill's trust boundary.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The prompt explicitly instructs the model to produce the final briefing 'in Korean' regardless of user preference. This is a natural-language locale constraint with no opt-in, choice, or documented region-specific justification in the file.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
This TypeScript file contains multiple natural-language strings in Korean for operational logs and warnings, such as the comments and runtime messages around search start, missing API key, and API errors. The policy explicitly disallows forcing a specific language without user opt-in, and this file does not offer any localization choice or justify a Korean-only locale.

External Transmission

Medium
Category
Data Exfiltration
Content
}

  try {
    const response = await fetch('https://api.tavily.com/search', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
}

  try {
    const response = await fetch('https://api.tavily.com/search', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file contains Korean-only user-facing error messages and comments such as 'keywords는 1개 이상의 문자열 배열이어야 합니다.' and similar strings. This enforces a specific language/locale without offering user opt-in or documenting that the skill is intentionally Korean-only, which matches the language-policy violation category.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This code constructs a remote API request to Google and includes both the system prompt and user message in the POST body. While there is a console log that a call is happening, it does not disclose that user-provided and system content are being transmitted to an external service, and the file contains no comment or docstring warning about that data transfer.

External Transmission

Medium
Category
Data Exfiltration
Content
}
    this.client = new OpenAI({
      apiKey: config.apiKey,
      baseURL: 'https://api.groq.com/openai/v1',
    });
    this.model = config.model || 'llama-3.3-70b-versatile';
  }
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Low
Confidence
87% confidence
Finding
The package description is written entirely in Korean and does not indicate that the skill is region-specific or that users may interact in another language. Under the language/locale policy, this can be a natural-language constraint without documented opt-in or justification.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"author": "KimCode",
  "license": "ISC",
  "dependencies": {
    "@anthropic-ai/sdk": "^0.33.1",
    "dotenv": "^16.4.7",
    "openai": "^6.27.0"
  },
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"license": "ISC",
  "dependencies": {
    "@anthropic-ai/sdk": "^0.33.1",
    "dotenv": "^16.4.7",
    "openai": "^6.27.0"
  },
  "devDependencies": {
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
src/fetcher.ts:8