Back to skill

Security audit

stock-portfolio-aisa

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but it forwards an API key to a configurable external endpoint and has weak controls around portfolio data and pricing integrity.

Review before installing. Use only in an environment where AISA_BASE_URL is not attacker-controlled, prefer a narrowly scoped and rotatable AISA_API_KEY, and do not rely on the displayed live prices for financial decisions without independent verification. Be aware that portfolio removals/deletions immediately change the local portfolio file.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/portfolio.py:70
Finding

API Credential Exposure Through a Configurable Untrusted Endpoint

Content
View full analysis
OpenAI: api_key = os.environ.get("AISA_API_KEY") if not api_key: print("❌ Error: AISA_API_KEY environment variable is not set.", file=sys.stderr) sys.exit(1) base_url = os.environ.get("AISA_BASE_URL", "https://api.aisa.one/v1") return OpenAI(api_key=api_key, base_url=base_url) ``` ### Technical Analysis The client obtains the sensitive `AISA_API_KEY` credential from the environment but permits the API base URL to be replaced through `AISA_BASE_URL`. The replacement value is not validated against an approved hostname, URL scheme, or origin. The OpenAI client uses the supplied API key to authenticate requests sent through the configured base URL. Consequently, a party capable of controlling the Skill's process environment can redirect authenticated requests to an attacker-operated server. External networking is necessary for the declared live-pricing functionality. However, allowing credentials to be attached to requests for an arbitrary origin is broader than the minimum privilege required. The endpoint should be fixed to the declared AISA service or restricted to a narrowly defined allowlist. ### Attack Path 1. An attacker gains the ability to influence the environment used to launch the Skill, such as through a wrapper, deployment configuration, or inherited environment variable. 2. The attacker sets `AISA_BASE_URL` to an HTTPS endpoint under their control. 3. A user invokes the `show` command for a non-empty portfolio. 4. `fetch_prices()` calls `get_client()` and constructs an OpenAI client using the legitimate `AISA_API_KEY` and the attacker-controlled endpoint. 5. The client sends an authenticated pricing request to the attacker-operated server. 6. The attacker captures authentication ...[truncated 751 chars]
Remediation
View remediation
OpenAI: api_key = os.environ.get("AISA_API_KEY") if not api_key: raise RuntimeError("AISA_API_KEY is not configured") return OpenAI(api_key=api_key, base_url=AISA_BASE_URL) ``` 2. If custom endpoints are operationally required, parse the URL and enforce: - HTTPS only. - An exact allowlist of approved hostnames. - An expected port and path prefix. - Rejection of embedded credentials, fragments, unexpected redirects, and local or private-network destinations. 3. Configure the HTTP client not to forward authorization headers across cross-origin redirects. 4. Use a narrowly scoped API key with quota limits and rotation support. 5. Document every supported network destination in `SKILL.md` so users can make an informed disclosure decision. 6. Add tests confirming that malformed URLs, HTTP URLs, unapproved domains, loopback addresses, and private-network addresses are rejected before any authenticated request occurs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/portfolio.py:78
Finding

LLM Prompt Injection Through Unvalidated Portfolio Tickers

Content
View full analysis
dict[str, float]: """Fetch current prices for a list of tickers via AIsa API.""" if not tickers: return {} client = get_client() model = os.environ.get("AISA_MODEL", "gpt-4o") ticker_str = ", ".join(tickers) prompt = ( f"Fetch the current live price for each of these tickers: {ticker_str}\n\n" "Return ONLY a JSON object with ticker symbols as keys and current prices (numbers) as values.\n" "Example: {\"AAPL\": 195.50, \"BTC-USD\": 67234.10}\n" "No explanation. JSON only." ) try: response = client.chat.completions.create( model=model, messages=[ {"role": "system", "content": "You are a financial data fetcher. Use your tools to get current prices and return only JSON."}, {"role": "user", "content": prompt}, ], temperature=0, ) content = response.choices[0].message.content.strip() # Extract JSON from possible markdown fences if "```" in content: content = content.split("```")[1] if content.startswith("json"): content = content[4:] return json.loads(content.strip()) except Exception as e: print(f"⚠️ Could not fetch live prices: {e}", file=sys.stderr) return {} ``` The untrusted value originates when an asset is added: ```python def cmd_add(args): data = load_portfolios() pf_name = get_active_portfolio(data, args.portfolio) ticker = args.ticker.upper() asset = { "ticker": ticker, "quantity": args.quantity, "cost_basis": args.cost, "added": datetime.now().isoformat(), } data["portfolios"][pf_n ...[truncated 2331 chars]
Remediation
View remediation
str: ticker = value.strip().upper() if not TICKER_PATTERN.fullmatch(ticker): raise ValueError("Invalid ticker symbol") return ticker ``` 2. Apply validation when assets are added or updated and revalidate existing persisted data before including it in a request. 3. Prefer a dedicated market-data API with a structured ticker parameter instead of using a general-purpose language model for authoritative financial pricing. 4. If a model must be used, serialize symbols as a JSON array and clearly delimit them as data. This reduces, but does not eliminate, prompt-injection risk. 5. Validate the response before valuation: - Require a JSON object. - Reject unexpected keys. - Require all requested keys to be present. - Reject booleans, strings, `NaN`, and infinite values. - Require finite, non-negative numbers. - Apply reasonable provider-specific bounds where feasible. 6. Mark unavailable or invalid prices as missing rather than incorporating them into totals. 7. Add regression tests using ticker values containing spaces, newlines, quotes, Markdown fences, instruction text, and oversized input. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/portfolio.py:2
Finding

Unpinned Third-Party Dependency Allows Non-Reproducible Package Resolution

Content
View full analysis
=3.10" # dependencies = [ # "openai>=1.0.0", # ] # /// ``` ### Technical Analysis The inline dependency metadata permits any available `openai` package version greater than or equal to `1.0.0`. No upper bound, exact version, lockfile, or package hash is provided. When the script is run through a dependency-resolving tool such as the documented `uv run` workflow, the selected package can vary over time. A future compromised, malicious, or behaviorally incompatible release could consequently be downloaded, installed, imported, and executed without any change to the reviewed Skill source. This does not establish that the current `openai` package is malicious. The security issue is that the audited artifact does not identify the exact third-party code that will execute, weakening reproducibility and supply-chain controls. ### Attack Path 1. A user invokes the script through a runtime that resolves the inline dependency. 2. The resolver queries the configured Python package index and selects a version satisfying `openai>=1.0.0`. 3. If a newly published satisfying release is compromised, or if the package index or dependency-resolution configuration is subverted, that release is installed. 4. Package installation or import causes third-party code to execute in the user's process. 5. That code runs with the same operating-system permissions and environment access as the portfolio script, potentially including access to `AISA_API_KEY` and local portfolio data. ### Impact Assessment A compromised resolved package would execute with the privileges of the user running the Skill. It could potentially read environment variables, including `AISA_API_KEY`; access portfolio state readable by that user; make network requests; or modify use ...[truncated 218 chars]
Remediation
View remediation
", # ] ``` 2. Generate and commit a lockfile that records exact transitive dependency versions. 3. Require cryptographic hashes for downloaded distributions where the installation workflow supports them. 4. Use a trusted package index configured explicitly, and prevent fallback to untrusted or user-controlled indexes in production execution. 5. Add an automated dependency-review process for version upgrades, including vulnerability scanning and changelog review. 6. Rebuild and retest the Skill whenever the pinned dependency is updated so the executed dependency graph matches the audited artifact. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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 (7)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 31)May include surrounding context.

md
## When NOT to Use

- Do not use this skill for browser-cookie extraction, passwords, Keychain access, or other local sensitive credential access.
- Prefer a different skill when the user request is outside this skill's domain.

## Capabilities

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill declares required environment access and uses repo-local state files, but it does not explicitly define a tool scope such as permissions or allowed-tools. That omission weakens least-privilege controls and can let the runtime grant broader file and environment access than is necessary for portfolio operations.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
81% confidence
Finding

This code performs outbound transmission to a third-party service and allows the destination to be overridden by an environment variable. In the context of a portfolio skill, that means portfolio-derived data and API credentials may be sent to an attacker-controlled endpoint if the runtime environment is manipulated, making this more dangerous than a fixed, documented vendor endpoint.

Content

Scanner excerpt · scripts/portfolio.py (reported line 77)May include surrounding context.

python
if not api_key:
        print("❌ Error: AISA_API_KEY environment variable is not set.", file=sys.stderr)
        sys.exit(1)
    base_url = os.environ.get("AISA_BASE_URL", "https://api.aisa.one/v1")
    return OpenAI(api_key=api_key, base_url=base_url)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill transmits user portfolio tickers to an external API for live pricing without making that data-sharing behavior explicit in the skill description or obtaining user awareness at runtime. Even though only tickers are sent, holdings can reveal sensitive investment interests or strategies, and the danger is increased because the external endpoint is configurable via AISA_BASE_URL.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code performs a destructive local-data operation by deleting an asset from the portfolio and immediately writing the change, but it does not ask for user confirmation or provide any explicit warning that the action is irreversible. The same pattern also appears for full portfolio deletion, increasing the risk of accidental data loss from mistyped commands.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The module docstring claims portfolio data is stored in './.clawdbot/skills/stock-analysis/portfolios.json'. The code in get_storage_path also uses the 'stock-analysis' directory, which contradicts the stated skill identity of 'stock-portfolio' and indicates copied or stale documentation about where this skill's data belongs.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The manifest frames this skill as creating and managing portfolios with live pricing, but the implementation also reaches into process environment variables for AISA_API_KEY, AISA_BASE_URL, and AISA_MODEL. While live pricing justifies network access, directly reading runtime configuration and credentials is an additional capability not described in the skill purpose text.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.