Back to skill

Security audit

polymarket-predictradar-daily-anomalies-skills

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed Polymarket anomaly-reporting workflow, though users should be comfortable with full wallet-level reporting and query-code hardening needs.

Install only if wallet-level Polymarket reporting is acceptable for your use case. Treat 'Insider Watch' labels as leads, not proof of wrongdoing, avoid broadly sharing full-address reports unless needed, and harden the SQL list construction before relying on this in a production or adversarial data environment.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/content-analysis.js:147
Finding

SQL Injection Through Unescaped Upstream Identifiers

Content
View full analysis

Vulnerability Details

File Location: scripts/content-analysis.js, lines 147–184, 294–308, and 443–449
Vulnerability Type: SQL injection caused by dynamic construction of IN clauses
Risk Level: Medium

Vulnerable Code

js
// Lines 147–166
const activeIds = activeMarkets.map(r => `'${r.market_id}'`).join(',');

const buys = await mcp.queryWithRetry(`
  SELECT
    market_id,
    wallet_address,
    price        AS buy_price,
    usd_amount   AS trade_size,
    traded_at
  FROM trades
  WHERE traded_at >= now() - INTERVAL 24 HOUR
    AND type = 'trade'
    AND side = 'buy'
    AND outcome_index = 0
    AND price <= ${C.max_buy_price}
    AND price > 0
    AND usd_amount >= ${C.min_trade_usd}
    AND market_id IN (${activeIds})
    AND wallet_address IS NOT NULL
    AND wallet_address != ''
  ORDER BY usd_amount DESC
  LIMIT 3000
`, { maxRows: 3000, retries: 3 });
js
// Lines 178–186
const walletIn = walletSet.map(w => `'${w}'`).join(',');

const walletCounts = await mcp.queryWithRetry(`
  SELECT wallet_address, count() AS total_trades
  FROM trades
  WHERE type = 'trade'
    AND wallet_address IN (${walletIn})
  GROUP BY wallet_address
`, { maxRows: 5000, retries: 3 });
js
// Lines 294–308
const bigIds = bigMarkets.map(r => `'${r.market_id}'`).join(',');

const walletSides = await mcp.queryWithRetry(`
  SELECT
    market_id,
    wallet_address,
    sumIf(usd_amount, side = 'buy')  AS buy_total,
    sumIf(usd_amount, side = 'sell') AS sell_total,
    count()                          AS trade_count
  FROM trades
  WHERE traded_at >= now() - INTERVAL 24 HOUR
    AND type = 'trade'
    AND market_id IN (${bigIds})
    AND wallet_address IS NOT NULL
    AND usd_amount > 0
  GROUP BY market_id, wallet_address
  HAVING buy_total >= ${C.min_wallet_usd} OR sell_total >= ${C.min_wallet_usd}
  ORDER BY market_id, greatest(buy_total, sell_total) DESC
  LIMIT 3000
`, { maxRows: 3000, retries: 3 });
js
// Lines 443–451
const inLi
...[truncated 2679 chars]
Remediation
View remediation

Remediation Suggestions

  1. Use parameterized array bindings. Pass identifier arrays through placeholders or typed query parameters supported by the MCP client instead of interpolating SQL fragments.
js
const marketIds = activeMarkets.map(row => row.market_id);

const buys = await mcp.queryWithRetry(
  `
    SELECT market_id, wallet_address, price AS buy_price,
           usd_amount AS trade_size, traded_at
    FROM trades
    WHERE traded_at >= now() - INTERVAL 24 HOUR
      AND type = 'trade'
      AND side = 'buy'
      AND outcome_index = 0
      AND market_id IN ({market_ids:Array(String)})
  `,
  {
    params: { market_ids: marketIds },
    maxRows: 3000,
    retries: 3,
  }
);

Adapt the parameter syntax to the actual interface exposed by mcp-client.

  1. Strictly validate external identifiers before reuse. Wallet addresses should match the exact expected format:
js
const WALLET_RE = /^0x[0-9a-fA-F]{40}$/;

function validateWallet(value) {
  if (typeof value !== 'string' || !WALLET_RE.test(value)) {
    throw new Error('Invalid wallet address returned by the data service');
  }
  return value;
}

Define similarly restrictive patterns and length limits for market_id and condition_id based on their documented canonical formats. Reject unexpected values rather than attempting to repair them.

  1. Centralize safe list-query construction. Create one helper that accepts validated values and uses parameter binding. Replace all four interpolation sites so future queries do not reintroduce the issue.

  2. Fail closed and record safe diagnostics. If an upstream identifier is malformed, skip or reject the affected record and log only a sanitized diagnostic. Do not include the raw hostile value in SQL errors or user-facing reports.

  3. Retain server-side restrictions. Continue enforcing SELECT-only queries, query timeouts, row limits, and least-privilege access on the MCP service. These controls reduce impact but should not repla ...[truncated 239 chars]

Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (7)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The core purpose mostly aligns: the code does scan for the three declared anomaly types using Polymarket trade data. However, the description claims it 'generates a narrative daily report with real news context,' while the supplied code only produces console/table summaries and a JSON file export; there is no narrative synthesis or news retrieval/context integration. Additionally, the code enriches market metadata through the Gamma API, classifies markets into domains, filters some Black Swan sports events, and writes results to disk—capabilities not reflected in the declared description or permissions. These are material enough to count as a description-behavior mismatch, primarily because the advertised reporting/news-context output is absent from the implementation.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The auto-trigger conditions are underspecified and do not clearly constrain when the skill should run versus when a user is asking a general market question. In a system with multiple skills, this ambiguity can lead to privilege/behavior confusion, where this skill executes external queries or analysis outside the user's actual intent.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The auto-trigger conditions are underspecified and do not clearly constrain when the skill should run versus when a user is asking a general market question. In a system with multiple skills, this ambiguity can lead to privilege/behavior confusion, where this skill executes external queries or analysis outside the user's actual intent.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill explicitly requires disclosure of full wallet addresses and frames that disclosure as useful for user verification and copy-trading. Even if the addresses are on-chain and public, aggregating and spotlighting them in a report lowers the effort needed to track individuals, profile behavior, and facilitate targeted harassment, deanonymization, or imitation of trading activity.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The Insider Watch workflow requires listing all full addresses for 'suspicious' new-wallet activity, effectively producing a curated watchlist of potentially sensitive behavioral profiles. In context, this is more dangerous than generic blockchain reporting because it combines accusation-like labeling ('insider', 'suspicious') with complete identifiers, which can amplify misidentification harms and enable surveillance or targeted abuse.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill exports raw wallet addresses, grouped wallet lists, transaction sizes, and timing into a JSON report, which exceeds the stated purpose of producing a narrative anomaly summary. Even though the data may originate from public blockchain activity, packaging and persisting detailed identifiers materially increases traceability, profiling, and downstream misuse risk.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The stated purpose is to scan Polymarket markets and generate a daily anomaly report. Persisting a JSON artifact to disk is an additional operational capability that is not mentioned in the manifest and is not obviously required if the skill's role is simply to generate the report content for the caller.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.