Back to skill

Security audit

Stock Dividend

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent for dividend analysis, but it can send the required AIsa API key to any endpoint named in an environment variable without validation.

Install only if you trust the AIsa endpoint configuration. Do not set AISA_BASE_URL unless you control and trust that server, because the skill will send your AISA_API_KEY to that destination. Treat generated dividend reports as informational and verify financial data independently before acting on it.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/dividends.py:107
Finding
Unrestricted API Endpoint Override Can Expose the AIsa API Key<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dividends.py`, lines 107–115 **Vulnerability Type**: Unrestricted transmission of credentials to a configurable network destination **Risk Level**: High ### Vulnerable Code ```python def get_client() -> 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) print(" Set it with: export AISA_API_KEY=your_key_here", 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 Skill legitimately requires `AISA_API_KEY` to authenticate with the default AIsa API at `https://api.aisa.one/v1`. However, `AISA_BASE_URL` can replace that destination with an arbitrary URL without scheme validation, hostname allowlisting, or user confirmation. The `OpenAI` client associates the API key with requests sent to the configured base URL. Consequently, a process environment controlled or influenced by another component can redirect authenticated requests to an attacker-controlled server. This behavior exceeds the minimum privileges needed for the declared functionality because dividend analysis only requires communication with the intended AIsa service, not arbitrary network destinations. The optional endpoint is documented in `SKILL.md`, but documentation does not mitigate credential disclosure when the destination is unrestricted. ### Attack Path 1. An attacker gains influence over the environment used to invoke the Skill, such as through a compromised launcher, CI configuration, wrapper script, container configuration, or unsafe environment-file handling. 2. The attacker sets `AISA_BASE_URL` to an endpoint under their control. 3. A user invokes `scripts/dividends.py` with a valid `AISA_API_KEY`. 4. `get_client()` constructs the API client using the attacker-cont ...[truncated 1058 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `AISA_BASE_URL` support if custom endpoints are not essential to the Skill. 2. If endpoint customization is required, parse the URL and enforce: - HTTPS only. - An explicit allowlist of trusted hostnames. - Expected ports only. - No embedded username or password. - No loopback, link-local, private-network, or metadata-service destinations unless explicitly required. 3. Disable or carefully validate redirects so an approved host cannot redirect an authenticated request to an untrusted destination. 4. Require explicit user confirmation before sending credentials to any non-default endpoint. 5. Use separate, narrowly scoped credentials for development or self-hosted endpoints rather than reusing production AIsa credentials. 6. Avoid logging the API key and ensure exception messages cannot reveal authentication headers. 7. Consider implementing validation similar to: ```python from urllib.parse import urlparse ALLOWED_API_HOSTS = {"api.aisa.one"} base_url = os.environ.get("AISA_BASE_URL", "https://api.aisa.one/v1") parsed = urlparse(base_url) if ( parsed.scheme != "https" or parsed.hostname not in ALLOWED_API_HOSTS or parsed.username is not None or parsed.password is not None or parsed.port not in (None, 443) ): raise ValueError("AISA_BASE_URL is not an approved AIsa API endpoint") ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/dividends.py:172
Finding
Unvalidated Ticker Arguments Enable Prompt Injection into Financial Reports<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dividends.py`, lines 172–183 and 229–235 **Vulnerability Type**: Prompt injection through unvalidated command-line input **Risk Level**: Medium ### Vulnerable Code The ticker values are inserted directly into the model prompt: ```python prompt = DIVIDEND_PROMPT.format( tickers=", ".join(tickers), ticker=tickers[0] if len(tickers) == 1 else "each ticker", compare_note=compare_note, ) if output_format == "json": prompt += ( "\n\nReturn ONLY valid JSON. Do not include markdown fences, tables, commentary, " "or prose outside the JSON object.\n" "Use this exact shape:\n" "{\"dividends\": [{\"ticker\": \"JNJ\", \"yield\": 3.1, \"annual_dividend\": 4.76, " "\"payout_ratio\": 45.2, \"payout_status\": \"safe\", \"safety_score\": 82, " "\"income_rating\": \"Excellent\", \"consecutive_years\": 62, " "\"growth_5y_cagr\": 5.8, \"ex_dividend_date\": \"2024-02-20\"}]}\n" ) ``` The command-line inputs are only converted to uppercase and are not validated as ticker symbols: ```python def main(): parser = argparse.ArgumentParser(description="Dividend analysis via AIsa API") parser.add_argument("tickers", nargs="+", help="Ticker symbols (e.g., JNJ PG KO)") parser.add_argument("--output", choices=["text", "json"], default="text") args = parser.parse_args() tickers = [t.upper() for t in args.tickers] print(f"💰 Fetching dividend data for {', '.join(tickers)} via AIsa API...\n", file=sys.stderr) result = analyze_dividends(tickers, output_format=args.output) print(result) ``` ### Technical Analysis Uppercasing input does not restrict its syntax or semantic meaning. A supplied argument can contain spaces, punctuation, line breaks, and instructions directed at the remote language model. These instructions are interpolated into the same user prompt that requests the dividend report. Because the model must dis ...[truncated 2255 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate every ticker before constructing the prompt. Reject values containing whitespace, control characters, or instruction-like punctuation. 2. Use a conservative ticker pattern and a strict maximum length, adjusted only when required by supported exchanges. For example: ```python TICKER_PATTERN = re.compile(r"^[A-Z][A-Z0-9.-]{0,14}$") def validate_tickers(values: list[str]) -> list[str]: validated = [] for value in values: ticker = value.strip().upper() if not TICKER_PATTERN.fullmatch(ticker): raise ValueError(f"Invalid ticker symbol: {value!r}") validated.append(ticker) return validated ``` 3. Replace the current conversion in `main()` with validated parsing: ```python try: tickers = validate_tickers(args.tickers) except ValueError as exc: parser.error(str(exc)) ``` 4. Represent ticker values as structured data, such as a JSON array, and explicitly instruct the model that values inside the data block are identifiers rather than instructions. 5. In JSON mode, validate the response against a strict schema: - Require only expected fields. - Require numeric ranges for yield, payout ratio, and safety score. - Verify that returned tickers exactly match the requested ticker set. - Reject additional properties when they are unnecessary. 6. Do not rely on model instructions such as “Return ONLY valid JSON” as a security boundary. 7. If reports feed automated financial workflows, independently verify material financial data against trusted market-data sources before acting on model-generated conclusions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill requires sensitive environment variables such as AISA_API_KEY but does not declare an explicit tool scope or permission boundary. That creates ambiguity about what the skill may access at runtime and can lead to over-broad environment exposure or unsafe execution assumptions by the hosting agent platform.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The manifest says to use the skill when the user asks about "dividends, income investing, or dividend safety," which is a broad natural-language trigger without clear boundaries or exclusion conditions. This could cause unintended invocation for general investing discussions that mention income or safety but do not actually require this specific skill.

External Transmission

Medium
Category
Data Exfiltration
Content
print("❌ Error: AISA_API_KEY environment variable is not set.", file=sys.stderr)
        print("   Set it with: export AISA_API_KEY=your_key_here", 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)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.