Back to skill

Security audit

Coffee Prices by City

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed China-focused coffee price estimator, with privacy and hygiene cautions around automatic IP-based city lookup, CSV output, and an unpinned dependency.

Install only if a reference-price estimator is acceptable, not a live official-price source. Provide --city explicitly to avoid the automatic ipinfo.io lookup, avoid opening CSV output from untrusted city inputs in spreadsheet software, and prefer a pinned dependency set for reproducible installs.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/coffee_prices.py:206
Finding

Spreadsheet Formula Injection in CSV Output

Content
View full analysis

Vulnerability Details

File Location: scripts/coffee_prices.py, lines 206-213
Vulnerability Type: CSV/spreadsheet formula injection
Risk Level: Medium

Vulnerable Code

python
def output_csv(rows: List[CoffeePriceRow]) -> None:
    writer = csv.writer(os.sys.stdout)
    writer.writerow(
        ["brand", "brand_en", "city", "drink", "drink_en", "price", "currency"]
    )
    for row in rows:
        writer.writerow(
            [row.brand, row.brand_en, row.city, row.drink, row.drink_en, row.price, row.currency]
        )

Technical Analysis

The city value can originate directly from the --city command-line argument or the OPENCLAW_CITY environment variable. It is retained in row.city and written to CSV without neutralizing spreadsheet formula prefixes.

The Python CSV writer correctly quotes and escapes CSV syntax, but CSV quoting does not stop spreadsheet software from treating a cell beginning with =, +, -, or @ as a formula. Consequently, an attacker-controlled city such as =HYPERLINK(...) may be evaluated when the resulting file is opened in a spreadsheet application.

Attack Path

  1. An attacker influences the city through --city or OPENCLAW_CITY.
  2. The script passes the value through resolve_city() and build_rows() without formula-prefix validation.
  3. The user selects CSV output, for example:
    bash
    python3 scripts/coffee_prices.py --city '=HYPERLINK("https://attacker.example","Click")' --output csv
    
  4. output_csv() writes the attacker-controlled value into the city column.
  5. A victim opens the generated CSV in spreadsheet software.
  6. Depending on the spreadsheet application and its security settings, the cell may be interpreted as a formula, potentially causing an external request or presenting deceptive content.

Impact Assessment

Exploitation does not grant privileges within the Python process itself. The i ...[truncated 452 chars]

Remediation
View remediation

Remediation Suggestions

Sanitize every externally controllable string before writing it to CSV. At minimum, neutralize values whose first non-whitespace character is =, +, -, or @. Prefixing such values with an apostrophe is commonly used when spreadsheet compatibility is required.

python
def sanitize_csv_cell(value: object) -> object:
    if not isinstance(value, str):
        return value

    stripped = value.lstrip()
    if stripped.startswith(("=", "+", "-", "@")):
        return "'" + value
    return value


def output_csv(rows: List[CoffeePriceRow]) -> None:
    writer = csv.writer(os.sys.stdout)
    writer.writerow(
        ["brand", "brand_en", "city", "drink", "drink_en", "price", "currency"]
    )
    for row in rows:
        writer.writerow(
            [
                sanitize_csv_cell(row.brand),
                sanitize_csv_cell(row.brand_en),
                sanitize_csv_cell(row.city),
                sanitize_csv_cell(row.drink),
                sanitize_csv_cell(row.drink_en),
                row.price,
                sanitize_csv_cell(row.currency),
            ]
        )

Apply the safeguard to all string columns rather than only city, so future changes do not reintroduce the issue. Add tests covering leading formula characters, leading whitespace, tabs, carriage returns, and ordinary city names. If strict input validation is acceptable, reject city values containing control characters or beginning with spreadsheet formula markers.

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding

Unpinned Third-Party Dependency Allows Non-Reproducible Installation

Content
View full analysis

Vulnerability Details

File Location: requirements.txt, line 1
Vulnerability Type: Unbounded dependency version and missing integrity hashes
Risk Level: Low

Vulnerable Code

text
requests>=2.31.0

The documented installation command in SKILL.md, line 25, installs this mutable dependency set:

bash
pip install -r requirements.txt

Technical Analysis

The lower-bound-only constraint permits the package resolver to install any future version of requests that satisfies the requirement, together with whatever transitive dependency versions are selected at installation time. No lock file or package hashes are provided.

This configuration does not establish that the current requests package is malicious. However, it makes installations non-reproducible and prevents verification that users receive the exact dependency versions reviewed and tested by the project. A compromised package-index account, compromised configured index, or malicious future dependency release could therefore affect subsequent installations without any change to this repository.

Attack Path

  1. A user follows the documented command pip install -r requirements.txt.
  2. pip resolves the latest available versions satisfying requests>=2.31.0, using the user's configured package indexes.
  3. If a selected package release or package index has been compromised, attacker-controlled package content is downloaded.
  4. The content is installed into the user's Python environment and may subsequently execute when imported or invoked.

This path depends on an external supply-chain compromise or untrusted package-index configuration; the audited repository itself does not contain evidence of a malicious dependency.

Impact Assessment

If the dependency supply chain were compromised, malicious package code could execute with the privileges of the user performing installation or running the skill. This could ...[truncated 346 chars]

Remediation
View remediation

Remediation Suggestions

Pin reviewed direct and transitive dependency versions and verify downloaded artifacts with cryptographic hashes. For example, generate a locked requirements file with pip-tools or an equivalent dependency-management tool:

bash
pip-compile --generate-hashes requirements.in
pip install --require-hashes -r requirements.txt

The generated file should contain exact versions and SHA-256 hashes for requests and all transitive dependencies. Update dependencies through a controlled review process, run automated tests and vulnerability scanning before accepting updates, and avoid installing from untrusted or unexpected package indexes. Document the supported Python version so dependency resolution remains consistent across environments.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill claims to fetch current coffee prices, but the document itself reveals it actually uses hardcoded reference prices, reads OPENCLAW_CITY, and may call ipinfo.io for IP-based city inference. This mismatch is security-relevant because it obscures real data flows and capabilities, preventing informed consent and making it easier to hide unexpected external access or environment-derived behavior behind a harmless-seeming description.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The skill metadata promises to fetch current coffee prices from major chains, but the implementation only derives synthetic estimates from hard-coded base prices and city-tier multipliers. This creates a deceptive capability mismatch that can mislead downstream users or agents into treating fabricated data as live market information, which is especially risky for benchmarking or decision-making workflows.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill documentation describes behavior that uses environment data and external network access, but it does not declare any corresponding tool scope or permissions. This is dangerous because an agent or platform may execute the skill with broader capabilities than the user expects, enabling silent access to local environment state and outbound requests for IP-based geolocation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The natural-language instructions, examples, and operational guidance in the skill are predominantly Chinese, which effectively forces a specific language for users. The file does not indicate that Chinese is optional, configurable, or limited to a justified region-specific deployment.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The code performs IP-based geolocation via a third-party service to infer the runtime city, which exceeds the minimum functionality needed for a coffee-price comparison tool. This leaks network/contextual metadata to an external service without necessity and may reveal deployment location or user environment information.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The CLI/help text frames the tool as operating on a user-specified city, yet the implementation silently falls back to environment-derived or IP-derived location. This mismatch undermines informed consent and can cause the tool to process or disclose location context that the user did not intentionally provide.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
79% confidence
Finding

The CLI description, help text, supported city examples, and output labels are all in Chinese, and the city handling logic is tailored to Chinese city names. There is no indication that users can opt into another language or locale, which can violate language/locale policy when a skill forces a specific language by default.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

When no city is supplied, the skill automatically makes a network request to ipinfo.io to infer location, without an execution-time warning or explicit consent. Even if limited, this transmits runtime metadata to a third party and creates an unnecessary privacy and supply-chain exposure for a nonessential feature.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
97% confidence
Finding

The dependency is specified as requests>=2.31.0, which allows any newer release to be installed without review. This is a real supply-chain hygiene issue because builds may become non-reproducible and could unexpectedly pull in a vulnerable or breaking version later, even though it is not an immediately exploitable flaw by itself.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
requests>=2.31.0

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
93% confidence
Finding

Because requests is not pinned, it is impossible to verify from this manifest whether deployment will use a version affected by known advisories. In a skill that likely performs external HTTP fetching for coffee-price data, that uncertainty matters more because network-facing libraries are directly exercised against untrusted remote endpoints.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The manifest frames the skill as taking a user-specified city and outputting brand comparisons. Reading OPENCLAW_CITY introduces ambient environment-data access that is not necessary to the core pricing task and is not mentioned in the skill description.

Content

No source excerpt is available for this finding.