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. ]]>
