Back to skill

Security audit

Horizon SDK

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed prediction-market trading tool, but it needs Review because it can submit or cancel real financial orders and start arbitrary HTTPS data feeds with limited containment.

Install only if you intend to give an agent trading authority. Use paper trading by default, restrict HORIZON_API_KEY permissions, confirm every order/cancel/arbitrage/risk-control action yourself, avoid untrusted feed URLs, and prefer a pinned reviewed SDK version before live trading.

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

Error
Location
scripts/horizon.py:56
Finding
Configurable Feed URLs Permit SSRF Through DNS and IPv6 Validation Bypasses<![CDATA[ ## Vulnerability Details **File Location**: `scripts/horizon.py`, lines 56-90 and 386-410 **Vulnerability Type**: Server-Side Request Forgery caused by incomplete destination validation **Risk Level**: High ### Vulnerable Code ```python # Blocked hostnames / IP patterns for SSRF prevention. _BLOCKED_HOSTS = {"localhost", "127.0.0.1", "0.0.0.0", "[::1]", "metadata.google.internal"} def _is_private_ip(hostname: str) -> bool: """Check if hostname looks like a private/internal IP address.""" parts = hostname.split(".") if len(parts) != 4: return False try: octets = [int(p) for p in parts] except ValueError: return False if octets[0] == 10: return True if octets[0] == 172 and 16 <= octets[1] <= 31: return True if octets[0] == 192 and octets[1] == 168: return True if octets[0] == 169 and octets[1] == 254: return True return False def _validate_public_url(url_str: str, label: str) -> str: """Validate that a URL is HTTPS and targets a public host (not internal/private).""" from urllib.parse import urlparse parsed = urlparse(url_str) if parsed.scheme not in ("https",): _print({"error": f"{label} must use HTTPS"}) sys.exit(1) hostname = (parsed.hostname or "").lower() if not hostname: _print({"error": f"{label} has no hostname"}) sys.exit(1) if hostname in _BLOCKED_HOSTS or _is_private_ip(hostname): _print({"error": f"{label} cannot target private/internal addresses"}) sys.exit(1) return url_str ``` The validation is applied to user-configurable feeds as follows: ```python elif cmd == "start-feed": if len(args) < 3: _print({"error": "usage: start-feed <name> <feed_type> [config_json]"}) sys.exit(1) name = _validate_id(args[1], "feed_name") feed_type = _validate_id(args[2], "feed_type") if feed_type not in _VALID_FEED_TYPES: _print({"error": f ...[truncated 3820 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the URL and reject embedded credentials, malformed ports, fragments, and ambiguous host syntax. 2. Resolve the hostname with `socket.getaddrinfo()` before making a connection. 3. Convert every resolved address through Python's `ipaddress.ip_address()` and require `is_global` to be true. 4. Reject loopback, private, link-local, multicast, reserved, unspecified, and IPv4-mapped non-global addresses for both IPv4 and IPv6. 5. Validate every redirect destination with the same policy, or disable redirects. 6. Prevent DNS rebinding by connecting to a validated resolved address while preserving the intended TLS hostname for certificate and SNI verification. 7. Prefer an allowlist of approved RPC and REST API domains where operationally possible. 8. Apply outbound firewall or proxy controls so the process cannot reach instance metadata, private networks, or loopback services regardless of application validation. 9. Add tests covering DNS-to-private resolution, IPv6 loopback, unique-local and link-local IPv6, IPv4-mapped IPv6, redirects, and rebinding scenarios. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:10
Finding
Unpinned Horizon SDK Dependency Creates Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 10-13 **Vulnerability Type**: Unpinned third-party package installation **Risk Level**: Medium ### Vulnerable Code ```yaml install: - id: pip kind: uv formula: horizon-sdk ``` ### Technical Analysis The Skill installs `horizon-sdk` without an exact version, lockfile, integrity hash, or signed-artifact requirement. The wrapper then imports and delegates its substantive networking, account, and trading operations to that package: ```python from horizon import tools ``` As a result, the effective behavior of the Skill can change after this source package has been reviewed. A future compromised, malicious, or otherwise incompatible release may execute with the environment and privileges available to the Skill. The dependency is directly relevant to the declared functionality, so using an SDK is not itself excessive. The security issue is that the release is mutable and the audited project does not establish which exact implementation will be installed. ### Attack Path 1. A malicious or compromised version of `horizon-sdk` becomes available from the configured package source, or dependency resolution otherwise selects an unintended release. 2. A new Skill installation resolves the unpinned package name to that release. 3. `scripts/horizon.py` imports `horizon.tools`. 4. Package initialization or invoked tool functions execute with the Skill process's privileges. 5. The compromised dependency may access `HORIZON_API_KEY`, alter trading requests, falsify returned results, or make unauthorized network requests. This finding does not establish that the current `horizon-sdk` package is malicious. It identifies the absence of controls that would keep installations tied to an audited artifact. ### Impact Assessment A compromised dependency could potentially obtain all privileges available to the wrapper process, including: - Reading environment variables available to the process, ...[truncated 508 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `horizon-sdk` to a reviewed exact version rather than using an unconstrained package name. 2. Maintain a lockfile covering all transitive dependencies. 3. Require package hashes, signed artifacts, or another integrity-verification mechanism. 4. Verify the package source and restrict installation to a trusted repository. 5. Review dependency release changes before updating the pin. 6. Run the Skill with a minimally scoped API key and separate paper and live-trading credentials. 7. Restrict filesystem and outbound-network access through runtime sandboxing. 8. Revoke and rotate the API key promptly if package integrity is ever in doubt. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill exposes clear network-capable functionality, including live feeds, external HTTPS endpoints, exchange connectivity, and wallet/market lookups, but does not declare any explicit tool scope such as permissions or allowed-tools. This creates a policy and containment gap: an agent may invoke networked operations more broadly than reviewers or platform controls expect, increasing the risk of unintended external access or data transmission.

External Transmission

Medium
Category
Data Exfiltration
Content
"nba": hz.ESPNFeed("basketball", "nba"),
        "weather": hz.NWSFeed(state="FL", mode="alerts"),
        "custom": hz.RESTJsonPathFeed(
            url="https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
            price_path="bitcoin.usd",
        ),
    },
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This CLI exposes commands that submit orders, cancel orders, toggle a kill switch, start feeds, and execute arbitrage, but the file provides no confirmation prompt or explicit warning before invoking these actions. Because these operations can materially affect live trading state or automation behavior, users are not clearly warned at the point of execution.

Static analysis

No suspicious patterns detected.