Back to skill

Security audit

Agent Crypto Lens

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a normal crypto analysis skill, but it sends analysis data to external services and has some output-integrity and disclosure gaps.

Install only if you are comfortable with token queries, market/news context, and generated prompts being sent to third-party APIs. Configure restricted API keys, avoid submitting secrets or private investment information, treat the reports as informational rather than financial advice, and consider updating dependencies and adding stronger validation before production use.

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:25
Finding
Indirect Prompt Injection Through Untrusted Token and News Content<![CDATA[ ## Vulnerability Details **File Location**: `src/analyzer.ts:25-42` and `src/analyzer.ts:75` **Vulnerability Type**: Indirect prompt injection caused by treating untrusted content as LLM instructions **Risk Level**: Medium ### Vulnerable Code ```ts const newsText = news.length > 0 ? news.map((n, i) => `[${i + 1}] ${n.title}: ${n.content}`).join('\n') : '뉴스 데이터 없음'; const marketText = market.current_price_usd ? `현재가: $${market.current_price_usd}, 24h 변동: ${market.price_change_24h_pct?.toFixed(2)}%` : '시장 데이터 없음'; const systemPrompt = `크립토 센티먼트 분석 전문가입니다. 주어진 뉴스와 시장 데이터를 분석하여 반드시 아래 JSON 형식으로만 응답하십시오. 순수 JSON만 출력하고 다른 텍스트는 절대 포함하지 마십시오. { "score": 0~100 사이 정수 (0=매우 부정, 50=중립, 100=매우 긍정), "label": "Bullish 또는 Neutral 또는 Bearish", "summary": "센티먼트 판단 근거를 3문장으로 한국어로 설명" }`; const userMessage = `토큰: ${token}\n시장 데이터: ${marketText}\n\n뉴스:\n${newsText}`; ``` The generated summary is subsequently inserted into another prompt: ```ts const userMessage = `토큰: ${token}\n시장 데이터: ${marketText}\n센티먼트: ${sentiment.label} (점수: ${sentiment.score})\n센티먼트 분석: ${sentiment.summary}`; ``` ### Technical Analysis The `token`, news titles, and news contents are untrusted data. News content originates from Tavily search results, while the token value originates from the job payload. These values are concatenated directly into an LLM user message without robust data boundaries or an explicit instruction that embedded commands must be ignored. An attacker can publish search-indexed content containing instructions such as requests to disregard the system prompt, emit attacker-selected JSON, or manipulate sentiment scores. If Tavily returns that content for a token-related query, it becomes part of the model prompt. The risk is amplified because the free-form `summary` produced by the first model invocation is passed into a second invocation. Instructions retained in that summary can therefore influence both sentiment analysis and final report generation. ...[truncated 1408 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate the token before prompt construction: - Require a string. - Enforce a conservative length limit. - Permit only expected token-name or symbol characters. 2. Enclose external content in explicit structured boundaries, such as JSON fields or clearly marked data blocks. 3. Add a system-level rule stating that token values and retrieved articles are untrusted data and that instructions contained within them must never be followed. 4. Prefer provider-supported structured output or JSON-schema response enforcement instead of relying only on textual instructions. 5. Validate the complete model response: - Require integer scores in the range `0–100`. - Enforce enumerated labels. - Impose strict summary and report length limits. - Reject unexpected properties or types. 6. Do not pass unrestricted free-form model output into a subsequent prompt. Pass only validated structured values, or label the summary explicitly as untrusted model-generated data. 7. Consider preprocessing retrieved articles to remove instruction-like content and limiting input to the factual excerpts required for sentiment analysis. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
src/llm/gemini.ts:18
Finding
Gemini API Credential Included in Request URL<![CDATA[ ## Vulnerability Details **File Location**: `src/llm/gemini.ts:18-35` **Vulnerability Type**: Sensitive credential exposure through a URL query parameter **Risk Level**: Low ### Vulnerable Code ```ts // v1 REST API 엔드포인트 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 interpolated into the request URL as the `key` query parameter. HTTPS protects the complete request URL while it is in transit, and the application does not explicitly print the URL. Nevertheless, URLs are frequently captured by HTTP client instrumentation, reverse proxies, network gateways, tracing platforms, and error-monitoring systems. Query-string credentials can therefore propagate into logs or telemetry that have broader access and longer retention than dedicated secret stores. This unnecessarily increases the key's exposure surface. ### Attack Path 1. The Skill is configured to use Gemini with a valid `GOOGLE_API_KEY`. 2. The adapter constructs a request URL containing the complete key. 3. An HTTP instrumentation layer, outbound proxy, tracing system, or diagnostic collector records the full URL. 4. A user or attacker with access to those records retrieves the API key. 5. The exposed key is used to make unauthorized Gemini API requests, subject to the key's configured restrictions. This path depends on surrounding infrastructure recording full URLs; the reviewed code does not itself log the URL or key. ### Impact Assessment Exposure can permit unauthorized model requests, quota consumption, and costs associated with the compromised G ...[truncated 229 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use Google's maintained SDK or an authentication mechanism that keeps credentials out of request URLs where supported. 2. If the endpoint requires a query-string key, configure all proxies, HTTP tracing, diagnostics, and error monitoring to redact the `key` parameter. 3. Apply API restrictions to the key, including limiting it to the required Generative Language API and appropriate environments. 4. Use separate keys for development and production, with narrowly scoped quotas and alerting. 5. Rotate the key if full request URLs may previously have been retained in logs. 6. Never include complete response errors, request URLs, or client configuration objects in application logs. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
src/handler.ts:7
Finding
Missing Runtime Validation for Job Payload<![CDATA[ ## Vulnerability Details **File Location**: `src/handler.ts:7-17` **Vulnerability Type**: Improper input validation and unsafe reliance on TypeScript type assertions **Risk Level**: Low ### Vulnerable Code ```ts export async function handleJob(payload: unknown): Promise<object> { const agentId = 'crypto-lens'; const jobId = `job-${Date.now()}`; const input = payload as CryptoLensInput; // 단계 1: token 필수값 확인 if (!input.token || input.token.trim() === '') { return makeError(agentId, jobId, 'token 필드가 필요합니다.'); } const token = input.token.trim(); const analysisType = input.analysis_type ?? 'full'; ``` ### Technical Analysis The incoming payload is declared as `unknown`, but it is converted to `CryptoLensInput` using a TypeScript assertion. Type assertions are removed during compilation and do not validate runtime values. If the payload is `null` or `undefined`, accessing `input.token` throws. If `token` is a truthy non-string value, calling `.trim()` throws. Additionally, `analysis_type` is not checked against the declared `market`, `sentiment`, and `full` values. An unsupported value can skip both market and news collection while still triggering LLM analysis and report generation. There is also no maximum token length, allowing oversized values to increase search and LLM request sizes and associated processing costs. ### Attack Path 1. An untrusted caller submits a malformed payload such as `null`, `{ "token": {} }`, or a payload with an excessively large token string. 2. The TypeScript assertion accepts the value without runtime checks. 3. Property access or `.trim()` throws for malformed types, causing the job to fail outside the intended structured validation response. 4. Alternatively, an oversized string is propagated to Tavily and the LLM, increasing processing time, token use, and external API costs. 5. An invalid `analysis_type` can also create inconsistent behavior by bypassing expected data collection branches. ...[truncated 423 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate that the payload is a non-null, non-array object before accessing properties. 2. Require `token` to be a string and enforce: - A reasonable minimum and maximum length. - A conservative allowlist for token names or symbols. - Rejection of control characters and multiline instruction text. 3. Require `analysis_type` to be one of `market`, `sentiment`, or `full`. 4. Return a structured `makeError()` response for every validation failure. 5. Use a runtime schema validator such as Zod, Valibot, or JSON Schema at the job boundary. 6. Apply request-size limits, rate limits, external API timeouts, and per-job cost controls. 7. Wrap boundary validation in error handling so malformed payloads cannot terminate or destabilize a worker. ]]>
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
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (34)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
If the skill lacks actual crypto market collection, sentiment analysis, and processing logic, but only defines a generic LLM interface, then the published purpose is materially misleading. Misrepresentation is security-relevant because it can bypass scrutiny for a broad-capability tool, causing users to disclose data and operators to grant permissions under false assumptions about what the skill really does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
If the skill lacks actual crypto market collection, sentiment analysis, and processing logic, but only defines a generic LLM interface, then the published purpose is materially misleading. Misrepresentation is security-relevant because it can bypass scrutiny for a broad-capability tool, causing users to disclose data and operators to grant permissions under false assumptions about what the skill really does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
If the skill lacks actual crypto market collection, sentiment analysis, and processing logic, but only defines a generic LLM interface, then the published purpose is materially misleading. Misrepresentation is security-relevant because it can bypass scrutiny for a broad-capability tool, causing users to disclose data and operators to grant permissions under false assumptions about what the skill really does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
If the skill lacks actual crypto market collection, sentiment analysis, and processing logic, but only defines a generic LLM interface, then the published purpose is materially misleading. Misrepresentation is security-relevant because it can bypass scrutiny for a broad-capability tool, causing users to disclose data and operators to grant permissions under false assumptions about what the skill really does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
If the skill lacks actual crypto market collection, sentiment analysis, and processing logic, but only defines a generic LLM interface, then the published purpose is materially misleading. Misrepresentation is security-relevant because it can bypass scrutiny for a broad-capability tool, causing users to disclose data and operators to grant permissions under false assumptions about what the skill really does.

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
94% confidence
Finding
The lockfile pins transitive dependency form-data to 4.0.5, which is flagged for a CRLF injection issue in multipart field names. If this agent or one of its SDK dependencies constructs multipart requests using attacker-controlled field names or filenames, a crafted value could tamper with multipart headers and potentially alter downstream request parsing. In this package, the vulnerable library is transitive via @types/node-fetch rather than directly used, so exploitability depends on actual runtime use and is less certain than a direct import.

Credential Access

High
Category
Privilege Escalation
Content
import * as path from 'path';
import { MarketData } from './types';

// .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';
import { MarketData } from './types';

// .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 { MarketData } from './types';

// .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 { MarketData } from './types';

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

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The README explicitly requires Groq and Tavily API keys and references CoinGecko, but it does not warn users that prompts, token names, and other request data may be sent to third-party services. This creates a real transparency and data-handling risk because users may unknowingly transmit sensitive or proprietary inputs to external APIs.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill advertises network-backed market/news analysis but declares no explicit tool scope such as allowed network access or environment usage. That omission weakens reviewability and containment, making it harder to verify what external endpoints may be contacted and whether secrets from environment variables could be exposed through overly broad runtime capabilities.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The natural-language description and feature text are presented exclusively in Korean, with no indication that users can choose another language or that the skill is intentionally region-specific. Per the policy, forcing a specific language without opt-in is a natural-language policy violation.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
This TypeScript file makes outbound LLM provider calls via `provider.call(systemPrompt, userMessage)`, and the composed `userMessage` includes news content, market data, token identifiers, and derived sentiment text. Although there is minimal error logging, there is no confirmation prompt, user-facing notice, or comment/docstring warning that input data will be sent to an external model provider.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The system prompt explicitly requires the summary to be written '한국어로 설명', which forces a specific language for output. The file does not offer any language selection, opt-in, or documented locale justification, which matches the policy category for language or locale constraints.

Natural-Language Policy Violations

Medium
Confidence
78% confidence
Finding
The report prompt is entirely authored in Korean and instructs the model to return a report in that format, implying a fixed-language experience. There is no evidence in this file that users can choose another language or that the Korean-only constraint is documented as a justified regional requirement.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
This code contains multiple Korean-only natural-language strings in comments, warnings, errors, and status logs, such as the API-key warning and search-status messages. Because the skill does not provide any opt-in or locale selection mechanism, it effectively forces a specific language, which matches the language/locale policy violation criteria.

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.

External Transmission

Medium
Category
Data Exfiltration
Content
// 하지만 CoinGecko 무료 API는 키 없이 가능하므로 실제 호출 시도

    const searchRes = await fetch(
      `https://api.coingecko.com/api/v3/search?query=${encodeURIComponent(tokenSymbol)}`
    );

    if (!searchRes.ok) {
Confidence
50% 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
// 하지만 CoinGecko 무료 API는 키 없이 가능하므로 실제 호출 시도

    const searchRes = await fetch(
      `https://api.coingecko.com/api/v3/search?query=${encodeURIComponent(tokenSymbol)}`
    );

    if (!searchRes.ok) {
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
94% confidence
Finding
The file contains Korean-only instructional comments and a Korean-only error message ('token 필드가 필요합니다.'). This imposes a specific language in user-visible behavior without offering opt-in, fallback, or documenting a justified locale restriction.

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
88% confidence
Finding
The natural-language description forces a specific language presentation for the skill without any indication that users may choose another language or that the skill is intentionally region-specific. This matches the policy category for language or locale constraints lacking opt-in or justification.

Missing User Warnings

Low
Confidence
89% confidence
Finding
Omitting a user-facing warning that token/query data is sent to external services and that web content is fetched reduces informed consent and can surprise users about data disclosure. While not directly an exploit primitive, the lack of transparency materially increases privacy and compliance risk, especially when combined with undeclared external AI providers.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

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