Back to skill

Security audit

stock-portfolio-aisa-api

Security checks for vulnerabilities and agentic risk

Overview

This portfolio skill mostly matches its stated purpose, but it needs review because it can modify/delete local financial records and can send the AISA API key to an environment-configured endpoint without validation.

Review before installing. Use a low-privilege AISA_API_KEY, avoid setting AISA_BASE_URL unless you fully trust the endpoint, do not rely on returned prices as authoritative financial advice, and be careful with delete/remove commands because local portfolio data can be erased without confirmation.

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:64
Finding

API Credential Can Be Redirected to an Arbitrary 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 script reads the sensitive `AISA_API_KEY` credential and supplies it to an OpenAI-compatible client. The destination URL can be overridden through `AISA_BASE_URL` without validating its scheme, hostname, or trust relationship. When `fetch_prices()` invokes `client.chat.completions.create()`, the client sends an authenticated request to the configured base URL. Consequently, a modified process environment can redirect requests carrying the legitimate AISA credential to an arbitrary server. The code also does not require HTTPS, so a configuration using plaintext HTTP could expose the credential and request contents in transit. The default URL, `https://api.aisa.one/v1`, is consistent with the Skill's declared live-pricing functionality. The risk arises from allowing the same credential to be attached to an unrestricted alternative host, which exceeds the minimum network privileges required by the declared behavior. ### Attack Path 1. An attacker, compromised launcher, wrapper script, or unsafe runtime configuration sets `AISA_BASE_URL` to an attacker-controlled endpoint. 2. The user invokes `python3 scripts/portfolio.py show`. 3. `get_client()` reads the legitimate `AISA_API_KEY` and constructs a client using the malicious base URL. 4. `fetch_prices()` calls the chat-completions endpoint. 5. The client sends an authenticated request to the attacker-controlled server. 6. The server captures the API credent ...[truncated 722 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/portfolio.py:74
Finding

Unvalidated Ticker Input Enables Downstream LLM Prompt Injection

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 ticker originates from unrestricted command-line input and is persisted without format or length validation: ```python def cmd_add(args): data = load_portfolios() pf_name = get_active_portfolio(data, args.portfolio) ticker = args.ticker.upper() asset = { "ticker": ticker, " ...[truncated 2548 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
scripts/portfolio.py:2
Finding

Broad Unlocked Runtime Dependency Prevents Reproducible Integrity Verification

Content
View full analysis
=3.10" # dependencies = [ # "openai>=1.0.0", # ] # /// ``` ### Technical Analysis The inline dependency declaration accepts every `openai` package version at or above `1.0.0`. The project does not include a lockfile or package hash that identifies the exact dependency artifact reviewed and approved for execution. A runtime that honors this metadata may resolve a future package release that was not represented during this audit. Third-party Python packages execute code with the privileges of the Skill process, and installation can also involve package-controlled build behavior depending on the selected distribution. The package name is recognizable, and the reviewed project does not specify an unusual package index or similarly named package. Therefore, this finding is a supply-chain integrity and reproducibility weakness, not evidence that a malicious dependency is currently included. ### Attack Path 1. A user runs the script in an environment that resolves dependencies from a package index. 2. The resolver selects a newer `openai` version satisfying `>=1.0.0`. 3. That version may contain a compromised release, malicious modification, newly introduced vulnerability, or incompatible behavior not covered by this audit. 4. The dependency is installed or imported under the Skill process identity. 5. Any malicious or vulnerable dependency behavior executes with the same filesystem, environment-variable, and network access available to the script, including potential access to `AISA_API_KEY`. ### Impact Assessment The maximum theoretical impact is bounded by the privileges of the process installing or running the dependency. A compromised dependency could read accessible files and environment variables, send network requests, al ...[truncated 260 chars]
Remediation
View remediation
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 (10)

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
91% confidence
Finding

The skill declares required environment variables and implies file read/write behavior through local portfolio state, but it does not define any explicit tool scope such as permissions or allowed-tools. This creates an authorization ambiguity where an agent may grant broader access than necessary, increasing the chance of unintended file modification or environment exposure during execution.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill supports deleting and renaming portfolios and stores persistent local state, but the user-facing description does not clearly warn that actions may modify or permanently remove local data. This can lead to accidental destructive operations, especially in agent-driven contexts where users may not realize a request changes on-disk state.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The documentation and storage logic place portfolio state in ./.clawdbot/skills/stock-analysis/portfolios.json, which aligns with a different skill identity than the manifest name stock-portfolio. This means the implementation is operating on another skill's state area rather than the portfolio skill's own namespace, creating a semantic mismatch between the declared skill and the code's actual data target.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill saves and later deletes persistent local portfolio data without any upfront user-facing notice that operations modify on-disk state. In an agent setting, this can lead to surprising retention of financial holdings data or accidental destruction of records, especially when invoked indirectly by a user who expects a read-only analysis action.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

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
96% confidence
Finding

The show operation sends portfolio tickers to an external API for live pricing without a clear consent boundary at the command interface. Even though tickers are not highly sensitive by themselves, a user's holdings profile can reveal investment interests or strategies, and hidden outbound transmission is risky in an agent skill context.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

Deleting a portfolio immediately removes stored data without any confirmation, recovery step, or soft-delete mechanism. In an agent-driven workflow, a mistaken invocation, ambiguous prompt, or automation error could irreversibly erase user financial records.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest sets "language": "en", which is a natural-language locale constraint. In this file there is no indication that the user can choose another language or that the English-only behavior is explicitly justified, so it may conflict with the policy against forcing a specific language without user opt-in.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

The manifest describes creating, managing, renaming, and reviewing portfolios, but does not mention that holdings are persisted to local files. While local storage may be a reasonable implementation detail, the code's explicit read/write behavior is broader than the natural-language description and could matter for user expectations around persistence.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.