Back to skill

Security audit

polymarket-predictradar-data-layer-skills

Security checks for vulnerabilities and agentic risk

Overview

This market-data skill fits its stated purpose, but it needs review because parts of the implementation can forward an API key to an untrusted stream URL, accept invalid TLS certificates, and build unsafe SQL queries.

Install only if you are comfortable with a review-needed shared market-data layer. Before production or multi-user use, fix TLS verification, validate stream URL origins before sending API keys, validate or parameterize SQL inputs, and decide whether local cache retention of wallet and trader analytics is acceptable.

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

Error
Location
scripts/mcp-client.js:440
Finding

API Credential Disclosure and Server-Side Request Forgery Through an Unvalidated Stream URL

Content
View full analysis
{ const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(streamUrl, { method: "GET", headers: { "X-API-Key": MCP_API_KEY, }, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { const errorBody = await response.text(); throw new Error( `MCP stream HTTP ${response.status}: ${errorBody.slice(0, 200)}`, ); } const contentType = response.headers.get("content-type") || ""; const text = await response.text(); if (raw) { return { contentType, raw: text }; } if (contentType.includes("application/x-ndjson")) { return { contentType, rows: parseNdjson(text), }; } return { contentType, raw: text, }; } catch (error) { clearTimeout(timeoutId); if (error.name === "AbortError") { throw new Error(`MCP stream timeout (${timeout}ms)`); } throw error; } }); } ``` The affected method is reached through this flow: ```js const opened = await openQueryStream(sql, { format, previewRows, timeoutMs, timeout, metadata, }); const consumed = await consumeQueryStream(opened.streamUrl, { timeout, raw, }); ``` ### Technical Analysis `openQueryStream()` obtains `streamUrl` from the remote MCP service. `queryStream()` passes that value directly to `consumeQueryStream()`, which performs an outbound request and unconditionally attaches `MCP_API_KEY` as the `X-API-Ke ...[truncated 2590 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/gamma-client.js:91
Finding

TLS Certificate Validation Is Disabled for Gamma API Connections

Content
View full analysis
handleSocket(tlsSock), ); tlsSock.setTimeout(timeoutMs); tlsSock.on("timeout", () => tlsSock.destroy(new Error("TLS timeout"))); tlsSock.on("error", reject); } ``` The connection established through an HTTP proxy has the same weakness: ```js const tlsSock = tls.connect( { socket: tcp, servername: GAMMA_HOST, rejectUnauthorized: false, }, () => handleSocket(tlsSock), ); tlsSock.on("error", reject); ``` ### Technical Analysis Node.js TLS clients normally authenticate the remote endpoint by validating its certificate chain and checking that the certificate is valid for the requested hostname. Setting `rejectUnauthorized: false` disables certificate-chain enforcement. Although `servername: GAMMA_HOST` is supplied for Server Name Indication, it does not compensate for disabled certificate rejection. An attacker can present a self-signed, expired, incorrectly named, or otherwise untrusted certificate and the connection will still proceed. This affects both direct and proxied requests, including: - Market lookups by condition ID. - Keyword searches. - Large market scans. - Domain-map construction used by smart-money classification and reports. The Skill only requires authenticated HTTPS access to `gamma-api.polymarket.com`. Accepting arbitrary certificates is not necessary for that function and weakens the expected network security boundary. ### Attack Path 1. An attacker obtains a network position betw ...[truncated 1276 chars]
Remediation
View remediation
handleSocket(tlsSock), ); ``` Secure proxied connection example: ```js const tlsSock = tls.connect( { socket: tcp, servername: GAMMA_HOST, rejectUnauthorized: true, }, () => handleSocket(tlsSock), ); ``` ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/queries.js:132
Finding

SQL Injection Through Direct Interpolation of Caller-Controlled Query Values

Content
View full analysis
= now() - INTERVAL ${days} DAY AND traded_at IS NOT NULL GROUP BY wallet_address `, { queryName: "baseMetrics", timeout: 180_000, useStream: true, }, ), ); } ``` `minBought` is also interpolated without numeric validation: ```js async function roiMetrics({ minBought = 1000 } = {}) { return toMap( await runRows( ` SELECT address, (sum_pnl + sum_upnl) / (sum_bought + 1e-9) AS avg_roi, sum_wins / (sum_closed + 1e-9) AS win_rate, sum_pnl AS realized_pnl FROM ( SELECT wallet_address AS address, sum(toFloat64(realized_pnl)) AS sum_pnl, sum(toFloat64(unrealized_pnl)) AS sum_upnl, sum(toFloat64(total_bought)) AS sum_bought, countIf(is_closed = 1 AND toFloat64(realized_pnl) > 0) AS sum_wins, countIf(is_closed = 1) AS sum_closed FROM default.positions GROUP BY wallet_address HAVING sum(toFloat64(total_bought)) >= ${minBought} ) `, ...[truncated 4497 chars]
Remediation
View remediation
max) { throw new TypeError(`${name} must be an integer from ${min} to ${max}`); } return value; } async function baseMetrics({ days = 30 } = {}) { days = requireIntegerInRange(days, "days", 1, 365); // Construct the query only after validation. } ``` Example wallet validation: ```js function normalizeWalletAddress(value) { if (typeof value !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(value)) { throw new TypeError("Invalid wallet address"); } return value.toLowerCase(); } const normalizedAddresses = addresses.map(normalizeWalletAddress); ``` Where supported, parameter binding is preferred: ```js await mcp.query( `SELECT * FROM positions WHERE lower(wallet_address) = lower({address:String}) LIMIT 5`, { param ...[truncated 65 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (21)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The documented purpose is narrow and read-only, but the detected behavior expands into proxy/environment inspection and local system command execution unrelated to market-data retrieval. That mismatch is dangerous because users and orchestrators may grant or auto-select the skill under a low-risk assumption while it can access sensitive local configuration and execute host-level commands.

Content

No source excerpt is available for this finding.

MCP Config Access

High
Category
Agent Snooping
Confidence
80% confidence
Finding

Skill accesses MCP server configuration files (mcp.json). MCP configs contain server URLs, authentication tokens, and tool definitions — reading them allows the skill to discover and potentially abuse other tool integrations.

Content

Scanner excerpt · scripts/mcp-client.js (reported line 317)May include surrounding context.

js
}

/**
 * List available MCP tools.
 * @returns {Promise<object[]>} Tool definitions
 */
async function listTools() {

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding

The skill declares itself as a shared read-only data layer but does not constrain tool scope with an explicit allowlist or permissions block, despite documented network access and references to local helper scripts. In an agent environment, missing scope boundaries can let downstream workflows invoke broader capabilities than users expect, increasing the risk of unintended network, local command, or environment access.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The description says to use this skill whenever another skill or task needs 'any other read-only market intelligence,' which is open-ended and lacks clear boundaries. In a markdown skill file, this can create ambiguous invocation conditions and increase the chance the skill is selected for loosely related requests.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 15)May include surrounding context.

Data Source Priority

text
1. PredicTradar MCP Server  https://api.predictradar.ai/api/mcp/v2  (primary)
2. polymarket-cli           polymarket <command>                    (service degradation fallback)
3. Polymarket Data API      https://data-api.polymarket.com         (final fallback)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

The guidance encourages streamed exports and local cache writes without warning about privacy, retention, or handling of potentially sensitive trader-linked data such as wallet activity and performance metrics. In shared-agent contexts, silent export and local persistence can create unintended data exposure or retention beyond the user's expectations.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The manifest describes a shared read-only Polymarket data access layer for market intelligence. This file instructs consumers to invoke a locally installed CLI via child_process.execFileSync, which adds host-level subprocess execution capability beyond ordinary data retrieval and is not an obvious requirement of the stated purpose.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The REST fallback sends the user's wallet address to an external Polymarket endpoint without any disclosure, consent, or indication that data is leaving the primary MCP boundary. In a read-only market-intelligence skill, wallet addresses can still be sensitive because they enable activity correlation, profiling, and unintended third-party data sharing.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The complete fallback chain broadens the skill from a read-only data layer into one that can query a database, spawn a local executable, and make direct outbound network requests. Even though the demonstrated operations are read-only, this materially increases the attack surface and bypasses the skill's narrowly described interface and expected trust boundaries.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill manifest frames this skill as providing read-only market intelligence, yet this module persists cached data to disk, invalidates entries, clears cache files, and deletes old/corrupt cache files. Local file mutation is a materially broader behavior than purely read-only data access, even if used as an implementation convenience.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The direct TLS connection explicitly sets rejectUnauthorized: false, which disables certificate validation and allows a man-in-the-middle attacker to impersonate gamma-api.polymarket.com. Even though this client is described as read-only, it supplies market intelligence that other skills may trust for trading, analytics, or decision-making, so tampered responses can poison downstream actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This finding is substantively valid because the code makes an insecure TLS connection without any user-visible warning, increasing the chance that operators believe the transport is authenticated when it is not. The absence of disclosure does not create the core flaw by itself, but it worsens operational risk by concealing an insecure default in a supposedly read-only data client.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The proxy-tunneled TLS path also disables certificate verification, so anyone controlling the proxy path or network can terminate TLS and feed forged API data to the skill. The skill context makes this more dangerous because it is a shared data layer: compromised data may be reused broadly by multiple dependent components, amplifying the effect of a single interception point.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The proxied connection similarly suppresses certificate verification and provides no warning, so users may incorrectly assume the proxy tunnel preserves authenticated TLS to the upstream API. In a shared market-data layer, hidden insecurity can contaminate analytics and trading decisions across consumers that rely on this module's outputs.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

consumeQueryStream() performs fetch(streamUrl) on an unvalidated URL and sends the MCP API key in the request headers. If an attacker can influence streamUrl, they can trigger arbitrary outbound GET requests and potentially exfiltrate the API key to an attacker-controlled host, creating an SSRF-plus-credential-leak primitive.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The manifest describes this skill as a shared read-only market intelligence layer. While querying and reading streamed results fit that purpose, cancelQueryStream issues a DELETE request to /api/query/streams/{token}, which mutates server-side stream state rather than merely reading data.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The script takes a raw CLI-supplied address and inserts it directly into a SQL string inside the WHERE clause. Even though this is presented as an example script and the skill is described as read-only, direct interpolation allows an attacker to alter query logic, expand data access beyond the intended trader lookup, and potentially exploit any backend that permits stacked queries or permissive SQL parsing.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
99% confidence
Finding

The example query embeds unvalidated user input directly into SQL without sanitization, escaping, or parameter binding. Because this skill is a shared data-access layer for market and trader intelligence, the flaw is more dangerous than a standalone local demo: downstream users may copy this pattern into production workflows, enabling SQL injection and unauthorized read access across the dataset.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest frames the skill as providing read-only market intelligence, which implies non-mutating data access. However, the documentation explicitly says cache.js is used for local file-backed cache reads and writes, introducing write behavior beyond purely read-only access even if limited to local caching.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

A shared read-only data layer obviously justifies network-based data retrieval, but falling back to an external CLI is a distinct operational mode not reflected in the manifest description. This is a mild description/behavior mismatch because local CLI execution is broader than the stated high-level role of a shared data access layer.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

The header comment states that filenames are derived from hashed keys, which implies a one-way hashed mapping. In reality, keyToFilename replaces disallowed characters and truncates the original key, so the documentation contradicts the implemented behavior.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access, suspicious.exposed_secret_literal, suspicious.insecure_tls_verification

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/mcp-client.js:29

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
SKILL.md:205

HTTPS certificate verification is disabled.

Warn
Code
suspicious.insecure_tls_verification
Location
scripts/gamma-client.js:90