T09 · Insecure Skill Coding Practices
- Location
scripts/cex_trader_cli.py:29- Finding
Exchange Credentials Can Be Transmitted to Arbitrary or Plaintext Endpoints
- Content
View full analysis
Vulnerability Details
File Location:
scripts/cex_trader_cli.py, lines 29 and 32–51; credential flow at lines 150–160
Vulnerability Type: Unrestricted destination and insecure transport for sensitive credentials
Risk Level: HighVulnerable Code
python MCP_SERVER_URL = os.environ.get("MCP_SERVER_URL", "http://localhost:3000") def call_tool(tool_name: str, params: dict) -> dict: """Call an MCP tool via HTTP POST.""" url = f"{MCP_SERVER_URL}/tools/{tool_name}" data = json.dumps(params).encode("utf-8") req = urllib.request.Request( url, data=data, headers={"Content-Type": "application/json"}, method="POST", ) try: with urllib.request.urlopen(req, timeout=30) as resp: return json.loads(resp.read().decode()) except urllib.error.HTTPError as e: body = e.read().decode() print(f"HTTP Error {e.code}: {body}", file=sys.stderr) sys.exit(1) except urllib.error.URLError as e: print(f"Connection error: {e.reason}", file=sys.stderr) print(f"Is the MCP server running at {MCP_SERVER_URL}?", file=sys.stderr) sys.exit(1)python def cmd_setup_save(args): params = { "exchange": args.exchange, "apiKey": args.apiKey, "secretKey": args.secretKey, } if args.passphrase: params["passphrase"] = args.passphrase print_result(call_tool("cex-setup-save", params))Technical Analysis
The CLI takes
MCP_SERVER_URLdirectly from the environment and appends the selected tool path without validating the scheme or destination. It does not:- Require HTTPS for non-loopback destinations.
- Restrict credential submission to an approved host.
- Warn the user when credentials are sent to a custom destination.
- Pin or otherwise constrain the expected server identity.
The
setup saveoperation places the exchange API key, secret key, and optional OKX passphrase into a JSON request ...[truncated 1955 chars]- Remediation
View remediation
Remediation Suggestions
- Parse and validate
MCP_SERVER_URLbefore constructing requests. - Permit plaintext HTTP only when the resolved destination is a loopback address such as
127.0.0.1,::1, or an explicitly validated localhost endpoint. - Require HTTPS for every non-loopback destination.
- Use an allowlist containing the documented hosted MCP hostname by default.
- Require an explicit high-friction option, such as
--allow-custom-server, before sending credentials to a custom host. - Display the resolved scheme and destination and obtain confirmation immediately before transmitting credentials.
- Prevent redirects during credential submission, or revalidate the scheme and destination after every redirect.
- Consider avoiding central credential transmission entirely by signing exchange requests locally or using a local credential broker.
- Recommend exchange keys restricted to trading-only permissions, IP allowlists, and the minimum required exchange account scope.
- Add automated tests proving that credential submission is rejected for arbitrary HTTP hosts, user-info URLs, malformed URLs, and redirects to untrusted destinations.
- Parse and validate
