Back to skill

Security audit

Supermarket Deals (DE)

Security checks for vulnerabilities and agentic risk

Overview

This deal-search skill is not destructive, but it should be reviewed because it scrapes and stores Marktguru API/client keys and sends searches plus ZIP codes to that third party.

Install only if you are comfortable with this skill using Marktguru's embedded API/client keys, storing those keys and your default ZIP/store preferences under ~/.supermarket-deals, and sending each product query plus ZIP code to Marktguru. Avoid sensitive searches, and prefer a version that uses a documented API or clearly approved authentication path and sanitizes terminal output.

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
src/formatter.ts:136
Finding
Unsanitized Remote Data Enables Terminal Escape-Sequence Injection<![CDATA[ ## Vulnerability Details **File Location**: `src/formatter.ts:136-142` and `src/formatter.ts:184-195` **Vulnerability Type**: Terminal escape-sequence injection through untrusted API fields **Risk Level**: Medium ### Vulnerable Code ```ts return { id: rawId, productName: offer.product?.name?.trim() || "Unknown product", description: offer.description?.trim() || "", store: offer.advertisers?.[0]?.name?.trim() || "Unknown store", price: typeof offer.price === "number" ? offer.price : null, pricePerLitre: computePricePerLitre(offer), validFrom: normalizeDate(validity?.from ?? ""), validTo: normalizeDate(validity?.to ?? ""), sourceQuery, size: formatSize(offer), url: (() => { const safeId = sanitizeOfferId(offer.id); return safeId ? `https://www.marktguru.de/offers/${safeId}` : null; })(), }; ``` ```ts const rows = deals.map((deal) => { const productDetails = formatProductDetails(deal); const url = deal.url ?? "-"; return [ pad(productDetails, headers.productDetails), pad(deal.store, headers.store), pad(deal.size, headers.size), pad(formatPrice(deal.price), headers.price), pad(formatPricePerLitre(deal.pricePerLitre), headers.litre), pad(`${deal.validFrom} – ${deal.validTo}`, headers.validity), url, ].join(" | "); }); return [headerLine, separator, ...rows].join("\n"); ``` ### Technical Analysis The product name, description, advertiser name, unit name, and potentially invalid date values originate from the remote Marktguru API. These strings are trimmed and truncated but are not sanitized for ANSI escape sequences, carriage returns, C0/C1 control characters, or terminal-specific operating system command sequences. The resulting values are included directly in the table returned by `formatDealsTable()` and subsequently passed to `console.log()`. Truncating a string does not make terminal control sequences safe because short escape sequences can still clear the screen, reposition the cursor, ...[truncated 1828 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat all remote textual fields as untrusted and sanitize them before terminal rendering. 2. Remove ANSI escape sequences and nonessential C0/C1 control characters. Preserve ordinary printable Unicode and, if needed, safe tab or newline behavior explicitly. 3. Replace carriage returns, line feeds, tabs, and other layout-changing characters with spaces when producing the fixed-width table. 4. Apply sanitization before truncation so an escape sequence cannot survive as a short payload. 5. Sanitize all relevant fields, including product names, descriptions, advertiser names, unit names, source queries, and invalid date values. 6. Keep structured JSON data valid, but document that API-provided values remain untrusted. Consumers that render JSON in a terminal should sanitize at the presentation boundary. 7. Add regression tests using payloads such as: - `\x1b[2J` for screen clearing. - `\rForged result` for line replacement. - ANSI color and concealment sequences. - OSC 8 hyperlink sequences. - OSC 52 clipboard sequences. 8. Consider using a maintained ANSI-stripping library from a trusted, pinned dependency source, or implement a narrowly scoped sanitizer with comprehensive tests. ]]>
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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (15)

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
Scraping HTML to extract apiKey/clientKey values is credential acquisition behavior unrelated to normal client-side input handling and materially expands the skill’s trust boundary. In the context of a simple deal-search skill, this is especially concerning because it obtains third-party access tokens from page internals rather than through a supported integration path.

Credential Access

High
Category
Privilege Escalation
Content
}
  }

  throw new Error("Could not extract API keys from marktguru homepage JSON config.");
}

async function fetchFreshKeys(): Promise<ApiKeys> {
Confidence
98% confidence
Finding
The code explicitly extracts API keys from Marktguru’s homepage configuration, which is a form of credential acquisition from a third-party service. In this skill context, that behavior is more dangerous because it is hidden behind a benign shopping use case and facilitates unauthorized or unsupported API access.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The README describes sending product queries and a ZIP code to Marktguru, but it does not clearly warn users that their location-adjacent data and search terms are transmitted to an external third-party service. This creates a privacy/transparency issue because users may unknowingly disclose shopping interests and approximate location data, especially when the skill is used in automated agent pipelines.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill performs network access to Marktguru and fetches rotating API keys, but the manifest does not declare any tool scope such as permissions or allowed-tools. This creates a capability-transparency problem: an agent or reviewer cannot easily tell from the skill metadata that external network requests will occur, which can lead to unintended data exposure, policy bypass, or execution in environments that assume no outbound access.

External Transmission

Medium
Category
Data Exfiltration
Content
import path from "node:path";

const MARKTGURU_HOME = "https://www.marktguru.de";
const SEARCH_ENDPOINT = "https://api.marktguru.de/api/v1/offers/search";
const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
const CACHE_DIR = path.join(os.homedir(), ".supermarket-deals");
const KEYS_PATH = path.join(CACHE_DIR, "keys.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.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill writes fetched third-party API keys to a local file in the user’s home directory without transparency or protection controls. Even if these are not the user’s own secrets, local persistence can expose them to other local processes or users and normalizes undisclosed credential storage.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The code programmatically harvests hidden API credentials from Marktguru’s homepage, uses them to access a backend API, and stores them locally despite the skill claiming no API key is needed. This bypasses normal credential ownership and disclosure expectations, creates legal/abuse risk, and can break if the provider rotates or restricts those keys.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The function sends the user's search term and location data to https://api.marktguru.de via fetch, which is a network operation transmitting potentially sensitive user data. This file does not include a confirmation prompt, visible log, or explanatory docstring/comment informing the user that their inputs are sent to a third-party service.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
The README states the skill is for German supermarkets and later requires a German postal code, which imposes a locale constraint. While the domain is Germany-focused, the documentation does not explicitly frame this as a region-specific limitation or user opt-in choice, so it can be read as a fixed locale requirement.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
The package description states that the skill searches "German supermarket deals," which imposes a specific locale context in the skill metadata. Because there is no accompanying indication of user choice or opt-in in this file, this can be interpreted as a locale-specific policy constraint expressed in natural language.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"node": ">=18"
  },
  "devDependencies": {
    "@types/node": "^22.15.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.3"
  }
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "devDependencies": {
    "@types/node": "^22.15.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.3"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"devDependencies": {
    "@types/node": "^22.15.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.3"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Description-Behavior Mismatch

Low
Confidence
79% confidence
Finding
The manifest frames the skill as searching supermarket flyers and ranking deals, which implies network retrieval and result processing. This file additionally creates a hidden directory under the user's home directory and reads/writes a config file there, introducing local persistent state that is not mentioned in the description.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The code persists configuration data with saveConfig(cfg), which writes user-provided ZIP/store preferences to a local config file. While this is part of the config command's purpose, this file provides no explicit user-facing notice at the point of write beyond printing the resulting config afterward, so users are not warned beforehand that data will be stored on disk.

Static analysis

No suspicious patterns detected.