Back to skill

Security audit

fleece

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but it handles sensitive spending and wallet data and can send saved profile details to external services without a clear per-field confirmation step.

Install only if you are comfortable storing a local financial profile and having saved profile details used in live research. Prefer an isolated virtual environment, provide `BRAVE_API_KEY` through an environment variable or protected `.env` file rather than `--api-key`, avoid putting highly personal text in `goal` or `preferences`, and avoid exposing the Streamlit interface to untrusted users unless custom image URL fetching is hardened.

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

T09 · Insecure Skill Coding Practices

Error
Location
cli.py:406
Finding
Stored Financial Profile Transmitted in External Search Queries<![CDATA[ ## Vulnerability Details **File Location**: `db.py:174-205`, `cli.py:406-410` **Vulnerability Type**: Sensitive financial information exposure to a third-party search provider **Risk Level**: High ### Vulnerable Code ```python def profile_as_context(cards: list[str] | None = None) -> str: """ Return a compact natural-language summary of the user profile suitable for injecting into a Brave Search query or LLM prompt. """ p = get_profile() parts = [] spend_fields = [ ("dining_monthly", "dining"), ("groceries_monthly", "groceries"), ("travel_monthly", "travel"), ("gas_monthly", "gas"), ("other_monthly", "other"), ] spend_parts = [f"${p[k]}/mo {label}" for k, label in spend_fields if p.get(k)] if spend_parts: parts.append("Spending: " + ", ".join(spend_parts)) if p.get("annual_fee_tolerance"): parts.append(f"Max annual fee: ${p['annual_fee_tolerance']}") if p.get("points_programs"): parts.append(f"Points programs: {p['points_programs']}") if p.get("home_airport"): parts.append(f"Home airport: {p['home_airport']}") if p.get("goal"): parts.append(f"Goal: {p['goal']}") if p.get("preferences"): parts.append(f"Preferences: {p['preferences']}") if cards: parts.append(f"Current cards: {', '.join(cards)}") return " | ".join(parts) if parts else "" ``` ```python # Enrich with saved profile context if available saved_ctx = _db.profile_as_context(cards=_db.get_card_names()) ctx = f"{saved_ctx} | " if saved_ctx else "" query = f"best credit cards {ctx}{profile} {pref}US 2025 site:nerdwallet.com OR site:thepointsguy.com OR site:doctorofcredit.com OR site:uscreditcardguide.com" _run_search(_get_wrapper(key), query, "recommend", as_json) ``` ### Technical Analysis The `recommend` command automatically reads the complete saved profile and wallet composition, serializes them into natural-language cont ...[truncated 1851 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not automatically include the complete saved profile in external search queries. 2. Default to a minimized query containing only the spending categories necessary for the requested recommendation. 3. Exclude the home airport, goals, preferences, and wallet names unless the user explicitly selects those fields. 4. Display the exact outbound query or a field-level summary and require confirmation before sending sensitive data. 5. Add a local-only mode that generates recommendations from bundled information without network transmission. 6. Separate search retrieval from personalization: search for generic card information first, then apply the private profile locally. 7. Clearly document which fields leave the device, the destination service, and the applicable retention policy. 8. Add tests asserting that sensitive profile fields are absent from network queries unless explicit consent is recorded. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
image_service.py:73
Finding
User-Controlled Card Image URL Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `pages/my_credit_cards.py:277-294`, `image_service.py:73-85` **Vulnerability Type**: Server-Side Request Forgery through unrestricted remote image retrieval **Risk Level**: High ### Vulnerable Code ```python # Only show image URL field for custom card image_url = template["image_url"] if selected_template == "Custom Card": image_url = st.text_input("Card Image URL (optional)") submitted = st.form_submit_button("Add Card") if submitted: new_card = { "name": name, "last_four": last_four, "annual_fee": annual_fee, "credit_limit": credit_limit, "rewards": rewards, "expiration": expiration, "image_url": image_url, "date_added": datetime.datetime.now().strftime("%Y-%m-%d"), } try: db.add_card(new_card) load_user_cards.clear() st.success(f"Added {name} to your cards!") st.rerun() except ValueError: st.error(f'"{name}" is already in your cards.') ``` ```python # Check if we have a valid cached response in the dictionary cache if image_url in IMAGE_CACHE: timestamp, content = IMAGE_CACHE[image_url] if time.time() - timestamp < CACHE_EXPIRY: logging.info(f"Cache hit for {image_url}") return content # Fetch the image if not in cache or expired try: logging.info(f"Fetching image from {image_url}") response = requests.get(image_url, timeout=5) if response.status_code == 200: # Store in cache IMAGE_CACHE[image_url] = (time.time(), response.content) return response.content except Exception as e: logging.warning(f"Error fetching image from {image_url}: {e}") ``` ### Technical Analysis The custom-card form accepts an arbitrary image URL and stores it without validation. When cards are rendered, the application passes the stored value to `requests.get()`, causing the Streamlit server—not the user's browser—to ...[truncated 1967 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Accept only `https` URLs. 2. Prefer a strict allowlist of known card-issuer image domains. 3. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IP ranges for both IPv4 and IPv6. 4. Disable redirects or validate the scheme, hostname, and resolved address after every redirect. 5. Apply connection and read timeouts separately. 6. Stream responses and enforce a small maximum byte size before loading content into memory. 7. Require an expected image MIME type and validate the file signature before passing data to Pillow. 8. Configure outbound firewall rules to block metadata services and private networks. 9. Consider eliminating server-side image fetching and using vetted local assets or a hardened image proxy. 10. Add tests for localhost, RFC1918 addresses, IPv6 loopback, DNS rebinding, redirects to private hosts, and oversized responses. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
cli.py:102
Finding
Brave API Key Accepted Through Process Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `cli.py:102-104` **Vulnerability Type**: Secret exposure through command-line arguments **Risk Level**: Medium ### Vulnerable Code ```python ApiKeyOpt = Annotated[Optional[str], typer.Option("--api-key", help="Override BRAVE_API_KEY env var.")] JsonOpt = Annotated[bool, typer.Option("--json", "-j", help="Emit JSON output (agent-friendly).")] NoDotenv = Annotated[bool, typer.Option("--no-dotenv", help="Skip loading .env file.")] ``` The value is subsequently resolved as follows: ```python def _resolve_key(api_key: Optional[str], no_dotenv: bool) -> str: """Load env and return the Brave API key (may be empty — checked at use time).""" import os if not no_dotenv: load_dotenv() return api_key or os.getenv("BRAVE_API_KEY", "") ``` ### Technical Analysis The CLI permits users and agents to provide the Brave API key through `--api-key`. Command-line arguments are not a safe secret-transport mechanism on many systems because they can be exposed through: - Shell history - Process inspection tools - Process accounting - CI/CD command logs - Terminal session recording - Agent tool-call transcripts - Error reports that capture command invocations Using a Typer option does not mask the value because it is already present in the process argument vector before the application starts. ### Attack Path 1. A user or agent invokes a command such as `fleece card "Card Name" --api-key SECRET`. 2. The shell records the command in history or an automation platform records the invocation. 3. While the command is running, another local user or monitoring process reads the process arguments where platform permissions permit. 4. A party with access to the history or logs retrieves the key. 5. The exposed key is used to consume Brave API quota or issue requests under the victim's account. ### Impact Assessment The exposed privilege is limited to whatever access and quota the Brave API ...[truncated 318 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove or deprecate the `--api-key` option. 2. Prefer a protected environment variable, an operating-system keyring, or a configuration file with owner-only permissions. 3. If interactive entry is needed, use a hidden prompt such as `getpass` rather than command-line input. 4. Warn users when the deprecated command-line option is used. 5. Ensure API keys are redacted from logs, exceptions, telemetry, and agent transcripts. 6. Document secure key rotation and recommend immediate rotation after suspected exposure. 7. Add automated tests ensuring secret values are never emitted in normal or error output. ]]>

T08 · Insecure Dependencies

Warning
Location
pyproject.toml:22
Finding
Open-Ended Dependency Constraints Undermine Reproducible Installation<![CDATA[ ## Vulnerability Details **File Location**: `pyproject.toml:22-28`, `requirements.txt:1-11`, `SKILL.md:84-90` **Vulnerability Type**: Unbounded third-party dependency resolution and supply-chain exposure **Risk Level**: Medium ### Vulnerable Code ```toml dependencies = [ "typer[all]>=0.12.0", "python-dotenv>=1.0.0", "pydantic>=2.0", "langchain-core>=0.3.0", "requests>=2.31.0", ] ``` ```text langchain>=0.3.0 langchain-core>=0.3.0 langchain-community>=0.3.24 langchain_openai>=0.3.0 streamlit>=1.22.0 openai>=1.0.0 pydantic>=2.0 python-dotenv requests watchdog typer[all]>=0.12.0 ``` The Skill directs users to install the package directly from the public package index: ```bash # Install once pip install fleece-cli # Set in environment or .env file export BRAVE_API_KEY=<your_key> ``` ### Technical Analysis The installation configuration uses lower bounds without upper bounds, and multiple requirements have no version constraints. It also supplies no lock file or package hashes for the documented installation path. This means two installations performed at different times can resolve materially different dependency graphs. A future incompatible, vulnerable, or compromised dependency release can enter the environment without a corresponding change to the reviewed repository. This finding does not establish that any currently named dependency is malicious. The vulnerability is the lack of reproducible, integrity-constrained dependency resolution in a Skill explicitly instructing users and agents to install executable packages. ### Attack Path 1. A user or agent follows the Skill instructions and runs `pip install fleece-cli`. 2. Pip resolves the latest releases satisfying the open-ended constraints. 3. A dependency account compromise, malicious future release, or newly introduced vulnerable version becomes eligible for resolution. 4. Pip downloads and installs that release. 5. Package build hooks, installation behavior, or impo ...[truncated 609 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define tested upper bounds for runtime dependencies. 2. Publish a lock file for application deployments. 3. Generate a hash-pinned requirements file and install with `pip --require-hashes` where practical. 4. Build and publish artifacts through a controlled, reproducible release pipeline. 5. Run dependency vulnerability and provenance scanning during continuous integration. 6. Review automated dependency updates before release rather than accepting unrestricted future versions. 7. Recommend installation in a dedicated virtual environment with minimum filesystem and network privileges. 8. Record and publish the dependency versions used to build each released package. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (169)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The skill explicitly stores user card portfolio and spending-profile information locally, and if it also collects sensitive card metadata like last four digits, expiration, limits, or rewards without clearly disclosing privacy implications, that broadens the sensitivity of local storage. In a finance-related context, undeclared retention and portfolio management increase the chance of exposing personal financial patterns through local compromise or overbroad agent access.

Hidden Instructions

High
Category
Prompt Injection
Content
</head>
<body>

<!-- NAV -->
<nav>
  <a class="nav-logo" href="/">Fleece</a>
  <button class="nav-toggle" aria-label="Toggle navigation" aria-expanded="false">
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
<meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <!-- Primary SEO -->
  <title>Fleece — Credit Card Research CLI & Rewards Optimizer</title>
  <meta name="description" content="Fleece is a free, open-source CLI that researches credit cards with live data. Compare Chase, Amex, Citi, and Capital One cards — earning rates, transfer partners, statement credits, ROI, and MCC-level merchant lookup. pip install fleece-cli." />
  <meta name="robots" content="index, follow" />
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
<meta property="og:description" content="Research credit cards with live data. Compare Chase, Amex, Citi, Capital One — earning rates, transfer partners, ROI, MCC lookup, and award flight search. Free and open source." />
  <meta property="og:site_name" content="Fleece" />

  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary" />
  <meta name="twitter:title" content="Fleece — Credit Card Research CLI & Rewards Optimizer" />
  <meta name="twitter:description" content="Research credit cards with live data. Compare Chase, Amex, Citi, Capital One — earning rates, transfer partners, ROI, MCC lookup, and award flight search. Free and open source." />
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</ul>
</nav>

<!-- HERO -->
<section class="hero">
  <div class="hero-inner">
    <div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</div>
</section>

<!-- STATS STRIP -->
<div style="background:var(--black);padding:1.5rem 2.5rem;display:flex;align-items:center;justify-content:center;gap:3rem;flex-wrap:wrap;border-top:1px solid #222;border-bottom:1px solid #222;">
  <div style="text-align:center;">
    <div style="font-family:var(--font-head);font-size:1.6rem;font-weight:700;color:var(--yellow);letter-spacing:-0.5px;">13</div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</div>
</section>

<!-- COMMANDS + LINKS -->
<section class="contact-section" id="commands">
  <div class="contact-inner">
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</head>
<body>

<!-- NAV -->
<nav>
  <a class="nav-logo" href="/" style="display:flex;align-items:center;gap:10px;">
    <img src="assets/ios/icon.png" alt="Fleece"
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</div>
    </div>

    <!-- Phone mockups -->
    <div class="hero-phone">
      <div class="phone-frame back">
        <img src="assets/ios/screen-wallet.png" alt="Wallet tab" />
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</div>
</section>

<!-- HOW IT WORKS -->
<section class="how-section" id="how">
  <div class="how-inner">
    <p class="section-label">How it works</p>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</head>
<body>

<!-- NAV -->
<nav>
  <a class="nav-logo" href="/">Fleece</a>
  <ul class="nav-links">
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</div>
</section>

<!-- CONTENT -->
<div class="content">

  <div class="section">
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The replacement table systematically converts phrases like 'can I get' and 'welcome bonus' into alternate wording specifically to avoid triggering Apple safety mechanisms while preserving the user’s underlying request. In a credit-card research app, this is not necessary for core functionality and materially increases risk by normalizing circumvention of safety enforcement.

Static analysis

Detected: suspicious.prompt_injection_instructions

Prompt-injection style instruction pattern detected.

Warn
Code
suspicious.prompt_injection_instructions
Location
ios/ios-known-issues.md:104