Back to skill

Security audit

Geizhals.at

Security checks for vulnerabilities and agentic risk

Overview

This Geizhals price-check skill mostly matches its purpose, but its scraper can be steered by returned search data to request non-Geizhals URLs and cache the responses.

Review before installing in environments with access to private networks or metadata services. Prefer a version that only accepts relative Geizhals paths or explicitly allowlists https://geizhals.at, validates redirects, and makes cache behavior clear or optional.

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/geizhals.py:184
Finding
Unrestricted Candidate URL Fetching Enables Server-Side Request Forgery## Vulnerability Details **File Location**: `scripts/geizhals.py:184-190` and `scripts/geizhals.py:250-257` **Vulnerability Type**: Server-Side Request Forgery (SSRF) through insufficient URL validation **Risk Level**: Medium ### Vulnerable Code ```python def candidate_to_url(candidate: list[Any]) -> str | None: if not candidate: return None first = candidate[0] if len(candidate) > 0 else None if isinstance(first, str) and first: if first.startswith("http"): return first if first.startswith("/"): return BASE + first return f"{BASE}/{first}" return None ``` The accepted URL is subsequently fetched without destination validation: ```python detail_url = candidate_to_url(row) if not detail_url: continue item: dict[str, Any] = { "schema_version": SCHEMA_VERSION, "name": name, "detail_url": detail_url, "min_price_eur": None, "offer_count": None, "shop": None, "price_confidence": "unknown", "price_source": "none", "error": None, } try: page = fetch_text( detail_url, extra_headers={"Accept": "text/html,*/*"}, retries=3, backoff_base=0.6, cache_dir=cache_dir, debug=debug, ) ``` ### Technical Analysis Autocomplete candidates are obtained from a remote JSON response. `candidate_to_url()` trusts any candidate string beginning with `http`, without parsing and validating its scheme, hostname, port, resolved address, or final redirect destination. The resulting URL is passed to `urllib.request.urlopen()` through `fetch_text()`. Python's URL opener follows HTTP redirects by default, and the implementation does not validate redirect targets. Consequently, a malicious or compromised autocomplete response—or a permitted destination that redirects elsewhere—could instruct the Skill to issue HTTP requests to arbitrary destinations reachable from the Agent environment. Potential targets include: - Loopback ...[truncated 2296 chars]
Remediation
## Remediation Suggestions 1. **Reject arbitrary absolute candidate URLs.** Prefer accepting only relative Geizhals product paths returned by the autocomplete endpoint. 2. **Apply an explicit destination allowlist.** Parse URLs with `urllib.parse.urlsplit()` and require: - The `https` scheme - An exact approved hostname, such as `geizhals.at` - Only explicitly required subdomains - No embedded username or password - An approved port, normally port 443 3. **Validate resolved addresses.** Resolve the hostname and reject every address classified as loopback, private, link-local, multicast, unspecified, or reserved. Apply the validation immediately before connecting to reduce DNS rebinding risk. 4. **Control redirects.** Disable automatic redirects or implement a redirect handler that validates every redirect target using the same scheme, hostname, port, and resolved-address rules. Limit the number of redirects. 5. **Do not rely on prefix checks.** A check such as `startswith("http")` is not a security boundary and can admit unintended schemes or hosts. Use structured URL parsing and exact comparisons. 6. **Add negative security tests.** Test rejection of: - `http://127.0.0.1/` - `http://[::1]/` - Private IPv4 and IPv6 destinations - Link-local metadata addresses - URLs containing user information - Unapproved ports - Approved URLs that redirect to internal destinations - Hostnames resolving to private or mixed public/private addresses 7. **Restrict cached content.** Ensure the cache directory has restrictive permissions and consider avoiding caching responses unless the final destination has passed all validation.
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill declares networked scraping behavior, local cache usage, and test/fixture access, but does not define any explicit tool scope such as allowed tools or permissions. Without a restrictive scope, an agent platform may grant broader file and network access than the skill actually needs, increasing the blast radius if the skill code is modified, compromised, or prompted into unintended actions.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The skill hard-codes German/Austrian locale preferences via the Accept-Language header and geizhals.at referer context, and later also fixes lang=de&loc=at in requests. This is a natural-language locale policy issue because the user is not offered any language or region choice.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
The ACS search URL explicitly sets lang=de and loc=at for all users. Because no opt-in or alternative is provided, the skill enforces a locale choice in a way that can conflict with organizational language/locale policy.

Unverifiable Dependency: pytest has 2 known advisory(ies) (CVE-2025-71176 (pytest has vulnerable tmpdir handling); CVE-2025-71176 (pytest has vulnerable tmpdir handling)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Static analysis

No suspicious patterns detected.