Back to skill

Security audit

OKX Trading with Permission Gate

Security checks for vulnerabilities and agentic risk

Overview

This OKX trading skill is purpose-built for crypto trading, but it needs Review because its approval and guardrail design may not reliably prevent unintended live orders.

Install only if you are comfortable reviewing and operating a high-impact trading automation skill. Use demo mode first, prefer tightly scoped OKX keys with no withdrawal permission and IP restrictions, set small per-trade and daily caps, avoid live grid autonomy until the confirmation-token, guardrail recheck, and concurrency issues are fixed, and pin reviewed dependency versions before use.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (6)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/_pending.py:64
Finding
Local Confirmation Token Does Not Prove Human Approval<![CDATA[ ## Vulnerability Details **File Location**: `scripts/_pending.py:64-82`; execution occurs in `scripts/okx_execute_trade.py:34-57` and `scripts/okx_grid_apply.py:28-60` **Vulnerability Type**: Human-approval authorization bypass **Risk Level**: High ### Vulnerable Code ```python def save_pending(kind: str, payload: dict, ttl_seconds: int | None = None) -> tuple[str, str]: """Create a new pending record. Returns (id, confirmation_token).""" _ensure_dirs() if ttl_seconds is None: ttl_seconds = TRADE_TTL_SECONDS if kind == "trade" else GRID_TTL_SECONDS pid = new_id() token = _new_token() record = { "id": pid, "confirmation_token": token, "kind": kind, "payload": payload, "created_at": now_iso(), "expires_at_epoch": int(time.time()) + ttl_seconds, } path = _path(pid) path.write_text(json.dumps(record, indent=2)) os.chmod(path, 0o600) return pid, token ``` The trade execution path then accepts possession of that token as authorization: ```python try: record = load_pending(args.id) validate_token(record, args.confirmation_token) except PendingError as e: print(f"REFUSED: {e}", file=sys.stderr) return 3 if record.get("kind") != "trade": print(f"REFUSED: proposal {args.id} is kind={record.get('kind')!r}, not 'trade'", file=sys.stderr) return 3 payload = record["payload"] api_params = payload["api_params"] try: check_all(payload["instId"], float(payload["notional_usdt"])) except GuardrailError as e: print(f"REFUSED at execute time: {e}", file=sys.stderr) return 3 resp = trade_api().place_order(**api_params) ``` ### Technical Analysis The confirmation token proves only that the caller can read the local pending file. It does not prove that the user sent the documented `YES <id>` response. The Skill instructions explicitly direct the Agent to read that same pending file to obtain the token. Consequently, the proposing ...[truncated 1465 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Move approval into a trusted component separate from the proposing Agent. 2. Record an authenticated approval event only after independently receiving and validating `YES <id>` from the correct user and conversation. 3. Bind approval to: - Proposal ID - Authenticated user and account - Conversation or session ID - Immutable hash of the complete order payload - Creation and expiration timestamps 4. Have execution atomically consume server-side approval state rather than accepting a bearer token readable by the Agent. 5. Prevent the proposal process from writing or reading approval credentials. 6. Preserve single-use and expiry protections, but treat them as replay defenses rather than proof of human consent. 7. Log the authenticated approval event and consumed payload hash for later audit. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/okx_grid_apply.py:38
Finding
Grid Application Does Not Revalidate Financial Guardrails<![CDATA[ ## Vulnerability Details **File Location**: `scripts/okx_grid_apply.py:38-70`; proposal-time checks appear in `scripts/okx_grid_setup.py:116-123` **Vulnerability Type**: Time-of-check/time-of-use guardrail bypass **Risk Level**: High ### Vulnerable Code Grid setup checks the symbol and daily capacity only when creating the proposal: ```python try: check_symbol(args.instId) check_daily_room(total_initial_deployment) except GuardrailError as e: print(f"REFUSED: {e}", file=sys.stderr) return 3 ``` The application path later places all initial orders without repeating either check: ```python payload = record["payload"] inst_id = payload["instId"] quote_sz = float(payload["quote_sz_per_level"]) api = trade_api() strategy_id = "grid-" + new_id() active_orders: list[dict] = [] failures: list[dict] = [] for level_idx, level_px in enumerate(payload["level_prices"]): if level_px >= payload["ref_price_at_propose"]: # Sells deferred until matching buys fill — recorded as pending levels. continue # Spot limit buy: sz is base currency. base_sz = quote_sz / level_px sz_str = f"{base_sz:.8f}".rstrip("0").rstrip(".") px_str = f"{level_px:.8f}".rstrip("0").rstrip(".") resp = api.place_order( instId=inst_id, tdMode="cash", side="buy", ordType="limit", sz=sz_str, px=px_str, ) ``` ### Technical Analysis Grid proposals remain valid for up to 30 minutes. During that period, other trades or grid fills may consume daily capacity, and the symbol allowlist or configured limits may change. The earlier check therefore cannot safely authorize later execution. Unlike `okx_execute_trade.py`, `okx_grid_apply.py` does not perform execute-time validation. This creates a time-of-check/time-of-use gap and contradicts the stated policy that guardrails are rechecked before irreversible actions. The application also places multiple orders sequentially without reserving aggr ...[truncated 850 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Immediately before placing any grid order, call `check_symbol(inst_id)` and `check_daily_room(total_initial_deployment)`. 2. Recompute the deployment from the exact orders that will be submitted rather than trusting a stored rounded value. 3. Atomically reserve the complete deployment amount before submitting orders. 4. Decrement or release the reservation for rejected or canceled orders. 5. Fail closed before any placement if the full grid cannot fit within current policy. 6. Revalidate all immutable proposal fields against an approved payload hash. 7. Add tests covering capacity consumption and allowlist changes between proposal and application. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/okx_grid_step.py:274
Finding
Autonomous Grid Rescaling Bypasses the Daily Notional Limit<![CDATA[ ## Vulnerability Details **File Location**: `scripts/okx_grid_step.py:274-342` **Vulnerability Type**: Missing financial guardrail on autonomous order placement **Risk Level**: High ### Vulnerable Code During automatic rescaling, the strategy cancels existing orders and submits multiple replacement buys without calling `check_daily_room`: ```python # Cancel everything still on the book. to_cancel = still_active + new_orders cancel_failures = 0 for o in to_cancel: if not o.get("ordId"): continue cresp = api.cancel_order(instId=inst_id, ordId=o["ordId"]) cdata = (cresp.get("data") or [{}])[0] if cresp.get("code") != "0" or cdata.get("sCode") != "0": cancel_failures += 1 # New band centered on current price, preserving span. new_low = round(cur_px - band / 2, 8) new_high = round(cur_px + band / 2, 8) lvls = int(strategy["levels"]) step = (new_high - new_low) / (lvls - 1) new_level_prices = [round(new_low + i * step, 8) for i in range(lvls)] # Re-seed initial buys below cur_px (subject to position cap). replacements: list[dict] = [] cur_pos = _position_base(history) for idx, lvl_px in enumerate(new_level_prices): if lvl_px >= cur_px: continue if max_position_base > 0 and cur_pos >= max_position_base: audit_append( "position_capped", strategy_id=strategy["id"], level_idx=idx, position=round(cur_pos, 8), cap=max_position_base, context="rescale", ) continue placed = _place_buy(api, inst_id, idx, lvl_px, quote_sz) if placed: replacements.append(placed) ``` `_place_buy` directly submits the replacement order: ```python def _place_buy(api, inst_id: str, level_idx: int, level_px: float, quote_sz: float) -> dict | None: base_sz = quote_sz / level_px resp = api.place_order( instId=inst_id, tdMode="cash", side="buy", ordType="limit", sz=_format_sz(base_sz ...[truncated 1487 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Calculate the total quote exposure of all proposed replacement buys before rescaling. 2. Check and atomically reserve daily capacity for that aggregate amount. 3. If full reseeding would exceed the limit, either halt the strategy or submit only a clearly documented, deterministic subset within remaining capacity. 4. Recheck capacity before each submission to handle external account activity. 5. Update projected position after each accepted replacement order when enforcing `max_position_base`. 6. Apply the symbol allowlist and per-order cap to every autonomous placement path. 7. Add tests proving that normal restocks, rescaling, and concurrent grids cannot collectively exceed configured limits. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/okx_grid_step.py:144
Finding
Grid Maintenance Is Not Safe Under Concurrent Scheduler Execution<![CDATA[ ## Vulnerability Details **File Location**: `scripts/okx_grid_step.py:144-246`; non-atomic persistence occurs in `scripts/_pending.py:136-138` **Vulnerability Type**: Race condition causing duplicate financial operations **Risk Level**: High ### Vulnerable Code Each process independently observes a filled order and submits a replacement: ```python for order in active_orders: ord_id = order.get("ordId") if not ord_id: continue resp = api.get_order(instId=inst_id, ordId=ord_id) if resp.get("code") != "0" or not resp.get("data"): still_active.append(order) continue info = resp["data"][0] state = info.get("state", "") if state == "filled": fill_px = float(info.get("avgPx") or info.get("fillPx") or order["px"]) fill_sz = float(info.get("accFillSz") or order["sz_base"]) notional = fill_px * fill_sz history.append({ "ts_epoch": int(time.time()), "ordId": ord_id, "side": order["side"], "level_idx": order["level_idx"], "fillPx": fill_px, "fillSz": fill_sz, "notional_usdt": notional, }) try: check_daily_room(quote_sz) except GuardrailError as e: strategy["halted"] = True strategy["halt_reason"] = f"daily-cap breach after fill: {e}" audit_append("halted", strategy_id=strategy["id"], reason=str(e)) continue direction = "buy_filled" if order["side"] == "buy" else "sell_filled" target_idx = _next_level_idx(len(level_prices), order["level_idx"], direction) if target_idx is None: continue target_px = level_prices[target_idx] target_side = "sell" if order["side"] == "buy" else "buy" base_sz = quote_sz / target_px place = api.place_order( instId=inst_id, tdMode="cash", side=target_side, ordType="limit" ...[truncated 2073 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Acquire an interprocess lock for each strategy before reading or processing its state. 2. Hold the lock through reconciliation, order submission, and atomic state persistence. 3. Write updated JSON to a mode-`0600` temporary file in the same directory, `fsync` it, and atomically replace the original. 4. Store a state generation number and reject stale writes. 5. Supply deterministic OKX client-order IDs derived from strategy ID, source order ID, target level, and cycle so duplicate submissions are rejected by the exchange. 6. Make daily-cap reservation and notional updates transactional under a shared lock. 7. Reconcile exchange orders by client-order ID after uncertain network failures. 8. Add parallel-execution tests that prove a fill creates at most one replacement. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/_okx_client.py:97
Finding
Trading Credentials Are Unnecessarily Supplied to Public API Clients<![CDATA[ ## Vulnerability Details **File Location**: `scripts/_okx_client.py:97-109`; invoked by `scripts/okx_propose_trade.py:26-36` and `scripts/okx_grid_setup.py:93` **Vulnerability Type**: Excessive credential exposure and failure of least privilege **Risk Level**: Medium ### Vulnerable Code ```python def market_api(): """Public market data — auth not strictly required, but keys are accepted.""" from okx.MarketData import MarketAPI api_key, secret_key, passphrase, flag = _creds() return MarketAPI(api_key, secret_key, passphrase, False, flag) def public_api(): from okx.PublicData import PublicAPI api_key, secret_key, passphrase, flag = _creds() return PublicAPI(api_key, secret_key, passphrase, False, flag) ``` The flagged scripts use these clients for public lookups: ```python resp = market_api().get_ticker(instId=inst_id) ``` ```python resp = public_api().get_instruments(instType=inst_type, instId=inst_id) ``` ```python tick = market_api().get_ticker(instId=args.instId) ``` ### Technical Analysis The ticker and instrument-metadata network requests are necessary for the declared sizing and grid-proposal functions. No evidence shows transmission to an unrelated destination or deliberate credential exfiltration. However, the implementation explicitly acknowledges that market data is public while still loading the API key, secret, and passphrase and passing them into the SDK clients. This unnecessarily expands the number of code paths and third-party objects that handle trading credentials. Any SDK logging defect, compromised dependency, debugging instrumentation, or future client behavior in these public paths would gain access to secrets it does not need. It also prevents public proposal lookups from operating without privileged credentials. ### Attack Path 1. A user invokes a ticker, proposal, candle, or grid-setup operation that only requires public market data. 2. `_creds()` reads all OKX secrets from the process ...[truncated 574 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Construct `MarketAPI` and `PublicAPI` clients without API credentials using the SDK's supported unauthenticated mode. 2. Load `OKX_API_SECRET` and `OKX_API_PASSPHRASE` only inside authenticated account and trade client factories. 3. Permit public lookup scripts to run without privileged environment variables. 4. Use separate read-only and trade keys where authenticated reads are genuinely necessary. 5. Configure OKX keys with the narrowest available permissions, IP restrictions, and no withdrawal authority. 6. Add tests ensuring public client objects are never initialized with secret material. ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Security-Sensitive Dependencies Are Not Reproducibly Pinned<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-2` **Vulnerability Type**: Unbounded dependency resolution for trading software **Risk Level**: Medium ### Vulnerable Code ```text python-okx>=0.4.0 numpy>=1.24 ``` ### Technical Analysis Both requirements use lower-bound-only constraints. A future installation may therefore resolve to any later package version, including a release that has not been reviewed with this Skill. This is particularly sensitive for `python-okx`: the package receives the user's API key, secret, and passphrase and can submit financial orders. A compromised upstream release or an incompatible behavioral change would execute with those privileges when the Skill imports the package. The audit did not identify typosquatting or an explicitly malicious package in the current requirements. The issue is the lack of deterministic, reviewed dependency resolution. ### Attack Path 1. A new, compromised, or incompatible dependency release is published after this Skill version was audited. 2. A user runs `pip install -r requirements.txt`. 3. The resolver selects the newer release because it satisfies the `>=` constraint. 4. The Skill imports that release. 5. For `python-okx`, the dependency receives live account credentials and is invoked to query the account or place orders. ### Impact Assessment A supply-chain compromise could expose OKX credentials, manipulate market responses, alter order parameters, or submit unauthorized trades with the permissions granted to the API key. An incompatible update could also disable timeouts or break safety assumptions. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin exact reviewed versions of every direct and transitive dependency. 2. Generate and commit a lockfile suitable for the deployment environment. 3. Require package hashes during installation, such as with `pip --require-hashes`. 4. Install exclusively from an approved package index over TLS. 5. Review release notes and source changes before updating `python-okx`. 6. Run dependency vulnerability and provenance checks in CI. 7. Re-run order-gate, timeout, credential-handling, and API-response tests for every dependency update. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code's actual behavior is limited to retrieving and displaying open orders from OKX. The declared description presents a broader trading skill focused on balances, prices, proposing/executing trades, DCA, grid strategies, and confirmations before execution. While open orders may be adjacent to trading workflows, this specific capability is not explicitly included in the description, and the code does not implement the advertised confirmation gate or strategy functionality. Therefore the supplied code chunk does not accurately represent the declared purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The code is a narrow read-only utility for listing open OKX positions, especially for non-spot products such as margin, swaps, futures, and options. The declared description presents a much broader trading skill centered on balances, prices, trade execution with explicit confirmation, DCA/grid strategies, and daily snapshots, with defaults to demo trading and spot instruments. While position inspection could loosely relate to account monitoring, open positions are not explicitly described, and the script’s focus on leveraged/non-spot instrument types materially differs from the stated default spot orientation. There is no trading or confirmation logic in this chunk, so the actual behavior is narrower and somewhat inconsistent with the declared purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The description presents a comprehensive OKX trading skill whose key safety property is that trades are never executed without explicit human confirmation. The actual code chunk is only a daily snapshot/reporting utility. It accesses OKX account, market, and trade-history/order-list endpoints to assemble a portfolio/activity digest, writes it to disk, and prints a summary. That does align with the narrow sub-capability of 'get a daily snapshot/PnL digest,' but it does not represent the broader declared purpose of proposing/executing trades, running DCA or grid strategies, or enforcing a confirmation gate. Because the code’s primary purpose is materially narrower and different from the declared full trading assistant behavior, and it also performs undeclared local persistence, this should be flagged as a mismatch.

Session Persistence

Medium
Category
Rogue Agent
Content
pip install -r skills/aeon/okx-trading/requirements.txt
   ```

2. **Get OKX demo API credentials.** Log in at <https://www.okx.com>, switch to "Demo trading" in the top-right account menu, then go to Account → API. Create a key with **Trade** permission, save the API key, secret, and passphrase. (Live keys go in the same place but on the live account; do NOT mix them.)

3. **Set environment variables** in `.env` (and `source` it before running aeon):
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
dca_dip_state.json        # drawdown-trigger state (v0.3.0+)
```

Files are 0600 so the confirmation token is not world-readable. Pending proposals expire automatically (10 min for trades, 30 min for grids).

## What the user types in chat
Confidence
70% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill invokes shell commands, accesses environment variables containing exchange credentials, reads and writes local files, and relies on networked trading scripts, but it declares no explicit tool scope. In a trading skill, that omission is dangerous because it gives the agent broad implicit authority over high-risk capabilities without a machine-readable restriction boundary, increasing the chance of misuse, prompt-injection pivoting, or accidental execution with live credentials.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The skill first insists that execution must use the absolute pending-file path printed by the propose step, then later instructs the agent to read a guessed path under ~/.aeon/okx/pending/<id>.json. That contradiction weakens the confirmation control: an agent could read the wrong file, fail open in edge cases, or be steered toward attacker-controlled/incorrect state, undermining the integrity of the trade-approval gate.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script presents RSI-based outputs as actionable signals using phrases like 'consider buy' and 'consider sell' without any caution that these are simplistic heuristic indicators, not robust investment advice. In a trading skill, such phrasing can unduly influence users or downstream agent reasoning into executing risky trades based on oversimplified logic.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The file explicitly states that after the initial confirmed setup, subsequent fills are handled by another script without further confirmation. That behavior conflicts with the skill description’s blanket promise that it never executes a trade without explicit YES confirmation in chat, creating a trust-boundary violation where users may authorize one setup action but unintentionally grant ongoing autonomous trading.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This code autonomously cancels existing orders and places replacement buy orders during grid auto-rescale without any per-action user confirmation or user-visible warning at execution time. Although the broader skill description mentions a human-in-the-loop gate for initial setup, this file implements bounded post-setup trading autonomy, so a misconfigured strategy, market move, or compromised scheduler could trigger real account actions repeatedly without fresh consent.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script deletes the persisted grid strategy and pending record even if one or more exchange order cancellations fail, creating a dangerous mismatch between local state and real exchange state. In a trading skill, this can leave live orders active while the system believes the grid is stopped, leading to unintended fills, unmanaged exposure, and loss of the ability to safely track or unwind the remaining orders.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
This is a real vulnerability because the security model explicitly relies on the confirmation token being inaccessible to the LLM, yet the script prints the exact pending file path containing that token. In a tool-enabled agent environment, the LLM can typically use a file-read capability on that path, recover the token, and bypass the intended human-in-the-loop confirmation barrier. In a crypto-trading skill, that weakens the core control protecting against unauthorized trade execution, making the issue more dangerous than it would be in a non-financial context.

Session Persistence

Medium
Category
Rogue Agent
Content
Designed as the body of a scheduled morning Telegram digest:

  schedule_create  schedule="0 9 * * *"  task="Run okx_snapshot.py"  notify=true

Defaults the watched instrument list from `OKX_ALLOWED_SYMBOLS` plus active
grid strategies. Override with one or more `--instId` flags. Pass
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Unpinned Dependencies

Low
Category
Supply Chain
Content
python-okx>=0.4.0
numpy>=1.24
Confidence
94% confidence
Finding
The dependency python-okx is specified with a lower bound only, which allows future installs to resolve to different versions over time. This creates a supply-chain risk because a later release could introduce breaking changes or a vulnerable version, and in a trading skill that can access exchange accounts, dependency compromise could affect account data or trade execution logic.

Unpinned Dependencies

Low
Category
Supply Chain
Content
python-okx>=0.4.0
numpy>=1.24
Confidence
97% confidence
Finding
The numpy dependency is also unpinned, so builds are not reproducible and may silently pull different versions in different environments. In a financial/trading context, unexpected dependency changes can affect numerical behavior, stability, or expose the environment to known package-level vulnerabilities if an unsafe release is selected.

Unverifiable Dependency: numpy has 16 known advisory(ies) (CVE-2014-1859 (Numpy arbitrary file write via symlink attack); CVE-2021-41495 (NumPy NULL Pointer Dereference); CVE-2021-33430 (NumPy Buffer Overflow (Disputed)) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
89% confidence
Finding
The manifest references numpy without pinning to a specific version, while multiple advisories exist across historical numpy releases. Because the actual installed version is unverifiable from this file, you cannot determine whether deployment will avoid affected versions, which is a real supply-chain hygiene weakness even though it does not prove an exploitable vulnerable version is currently in use.

Static analysis

No suspicious patterns detected.