other
- Location
SKILL.md:753- Finding
MCP Registration Workflow Requires Disclosure of the Wallet Private Key
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This real-money casino skill is coherent in purpose, but it needs careful review because it handles wallet private keys, API keys, and large USDC spending approvals with broad trust in remote/API-provided data.
Install only if you are comfortable trusting Clawd Casino's API and any configured CASINO_API_URL with real-money betting operations. Use a fresh wallet funded only with the amount you are willing to risk, avoid the default 1,000,000 USDC approval where possible, verify the API origin before use, do not pass a private key to MCP tooling, and treat any printed or saved private/API key as sensitive loggable material.
SKILL.md:753MCP Registration Workflow Requires Disclosure of the Wallet Private Key
script/approve.py:25Server-Controlled Spenders Receive Excessive USDC Permit Allowances
script/register.py:24Configurable API Origin Can Receive API Keys and Wallet Authorization Signatures
script/wallet_gen.py:48Wallet Private Keys and API Keys Are Stored and Printed in Plaintext
script/wallet.py:39Wallet Authentication Signatures Are Not Bound to an Action, Origin, or Server Nonce
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
def get_games() -> list:
"""Get list of all games that can be approved."""
response = requests.get(f"{API_URL}/approve/game", headers=get_api_key_header())
if response.status_code != 200:
# Fallback to known games if endpoint doesn't exist yet
This code fetches the permit nonce and, critically, the spender address from a remote API and then signs an EIP-2612 permit for that spender without any local verification. If the API endpoint is compromised or the API_URL is redirected, the wallet can be tricked into granting USDC allowance to an attacker-controlled address, which is especially dangerous given the very large default approval amount.
return False
# Get permit nonce and game address from unified API
response = requests.get(
f"{API_URL}/approve/{game_name}/permit-nonce", headers=get_api_key_header()
)
if response.status_code != 200:
The POST target is derived from an environment-configurable API_URL, and the request includes wallet-authentication material plus the signed permit payload. If API_URL is redirected to an attacker-controlled endpoint, the attacker can harvest wallet signatures and permit metadata and submit or replay them depending on the token/contract semantics and backend workflow.
"s": "0x" + s.hex(),
}
response = requests.post(
f"{API_URL}/approve/{game_name}",
headers=get_wallet_signature_header(),
json=permit_data,
The request URL is derived from the CASINO_API_URL environment variable and the code sends a bearer API key in the Authorization header to that destination. If an attacker can influence the environment or deployment configuration, they can redirect the request to an attacker-controlled server and capture the API key or manipulate returned balance/approval data.
print("Error: CASINO_WALLET_KEY not set")
sys.exit(1)
response = requests.get(f"{API_URL}/agent/me", headers=get_api_key_header())
result = response.json()
if not result.get("success"):
The API base URL is taken from the CASINO_API_URL environment variable and then used for authenticated requests carrying the Bearer API key. If an attacker can influence the environment, they can redirect traffic to an attacker-controlled endpoint and capture credentials or manipulate betting operations. In this skill, that risk is amplified because requests can trigger financial actions.
"deadline": dl.isoformat(),
}
response = requests.post(
f"{API_URL}/pvp/request", headers=get_api_key_header(), json=data
)
result = response.json()
This request uses an environment-controlled API_URL together with the API key header. A malicious or compromised environment can reroute the request to a hostile server and exfiltrate the Bearer token, enabling account access and fraudulent operations.
def list_open_bet():
"""List all bet requests waiting for quotes."""
response = requests.get(f"{API_URL}/pvp/open", headers=get_api_key_header())
result = response.json()
if result.get("success"):
Submitting quotes sends authenticated betting data to a URL derived from an untrusted environment variable. If API_URL is altered, the tool can leak the API key and allow an attacker to spoof quote handling for financial transactions.
"ttl_minutes": ttl,
}
response = requests.post(
f"{API_URL}/pvp/quote", headers=get_api_key_header(), json=data
)
result = response.json()
The code queries quote data from an environment-controlled URL while attaching the API key. Redirecting this request to an attacker-controlled host can expose credentials and let the attacker return deceptive quote data to influence user actions.
def list_quotes(bet_id: str):
"""List all quotes for a bet request."""
response = requests.get(
f"{API_URL}/pvp/quote/{bet_id}", headers=get_api_key_header()
)
result = response.json()
Accepting a quote is a state-changing financial action, yet the destination host is environment-controlled. An attacker who can set API_URL could both steal the API key and induce the client to send sensitive acceptance actions to a rogue service.
data = {"quote_id": quote_id}
response = requests.post(
f"{API_URL}/pvp/quote/accept", headers=get_api_key_header(), json=data
)
result = response.json()
This withdrawal action sends authenticated data to a URL based on an untrusted environment variable. A hostile endpoint could capture the API key and manipulate client behavior around fund-related operations.
def withdraw_quote(quote_id: str):
"""Withdraw your open quote."""
response = requests.post(
f"{API_URL}/pvp/quote/withdraw",
headers=get_api_key_header(),
json={"quote_id": quote_id},
The retrieve call discloses wallet-linked betting information to a configurable endpoint while using the Bearer token. In the context of a casino/betting skill, leaking account and activity metadata to an attacker-controlled server increases both privacy and account-takeover risk.
print("Error: CASINO_WALLET_KEY not set")
sys.exit(1)
response = requests.get(
f"{API_URL}/pvp/retrieve",
headers=get_api_key_header(),
params={
Bet history retrieval combines account data and authorization with an environment-supplied host. A malicious host can collect sensitive account history and harvest the API key for later abuse.
print("Error: CASINO_WALLET_KEY not set")
sys.exit(1)
response = requests.get(
f"{API_URL}/pvp/retrieve",
headers=get_api_key_header(),
params={
Cancelling a bet is a state-changing authenticated operation sent to an environment-controlled endpoint. That makes credential exfiltration and transaction manipulation possible if runtime configuration is compromised.
def cancel_bet(bet_id: str):
"""Cancel your bet request (only if no quotes accepted yet)."""
response = requests.post(
f"{API_URL}/pvp/cancel",
headers=get_api_key_header(),
json={"bet_id": bet_id},
The client sends its API key to a host derived from CASINO_API_URL when fetching account stats. If that URL is attacker-controlled, the token can be stolen and account metadata disclosed.
def show_stat():
"""Show your betting statistics."""
response = requests.get(f"{API_URL}/agent/me", headers=get_api_key_header())
result = response.json()
if result.get("success"):
Balance and allowance information is fetched from an environment-controlled endpoint with authentication attached. In a financial skill, exposing balance data and the API key to a rogue server is materially dangerous.
def show_balance():
"""Show USDC balance and approval status for betting."""
response = requests.get(f"{API_URL}/agent/me", headers=get_api_key_header())
result = response.json()
if result.get("success"):
The permit nonce and spender are fetched from an environment-controlled API, and those values directly influence the EIP-2612 permit that the wallet signs. A malicious endpoint could provide an attacker-controlled spender so the user signs approval for the wrong contract, enabling token theft; this is especially severe because the default amount is extremely large.
sys.exit(1)
# Get permit nonce from API
response = requests.get(f"{API_URL}/pvp/permit-nonce", headers=get_api_key_header())
if response.status_code != 200:
print("Error: Could not get permit nonce")
return
The approval submission is sent to an environment-controlled API using wallet-signature-based headers after the client signs a permit. If API_URL is malicious, the attacker can orchestrate the approval flow, harvest signature material/metadata, and abuse the signed permit—made worse by the large default approval amount and direct financial consequence.
}
# /approve requires wallet signature (not API key) since we're proving wallet ownership for the permit
response = requests.post(
f"{API_URL}/pvp/approve",
headers=get_wallet_signature_header(),
json=permit_data,
The registration endpoint is derived from the CASINO_API_URL environment variable and is used to send wallet identity and a signed authentication message. If an attacker can influence the environment, they can redirect this request to an attacker-controlled server and capture the wallet address, signature, timestamp, and any returned API key, which is especially risky because this script then trusts the response and may store or display the returned secret.
data["skill_version"] = skill_version
print(f"Registering wallet: {wallet}")
response = requests.post(
f"{API_URL}/agent/register",
headers=get_wallet_signature_header(),
json=data,
The base URL is taken from an environment variable and used directly for an authenticated POST that includes the Bearer API key. If an attacker can influence CASINO_API_URL in the runtime environment, they can redirect wagering requests and the Authorization header to an attacker-controlled endpoint, causing credential exfiltration and fraudulent bet submission.
if bet_value is not None:
data["bet_value"] = bet_value
response = requests.post(
f"{API_URL}/roulette/spin", headers=get_api_key_header(), json=data
)
result = response.json()
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
def show_rule():
"""Show roulette rules and bet types."""
response = requests.get(f"{API_URL}/roulette/rule")
result = response.json()
if result.get("success"):
The history endpoint sends an authenticated GET using a host derived from CASINO_API_URL. If that environment variable is attacker-controlled, the Bearer token and account activity queries can be sent to a malicious server, exposing credentials and user gambling history.
def show_history(limit: int = 20):
"""Show roulette spin history."""
response = requests.get(
f"{API_URL}/roulette/history",
headers=get_api_key_header(),
params={"limit": limit},
This authenticated statistics request will send the API key to whatever host is configured in CASINO_API_URL. In an adversarial or multi-tenant environment, that creates a straightforward token exfiltration path and unauthorized access to account data.
def show_stat():
"""Show roulette statistics."""
response = requests.get(f"{API_URL}/roulette/stat", headers=get_api_key_header())
result = response.json()
if result.get("success"):
The balance/account endpoint includes the Bearer token and retrieves wallet information from a host fully controlled by CASINO_API_URL. An attacker who can set that variable can capture the token and potentially trick the tool into disclosing account metadata or interacting with a rogue backend.
def show_balance():
"""Show USDC balance and approval status."""
response = requests.get(f"{API_URL}/agent/me", headers=get_api_key_header())
result = response.json()
if result.get("success"):
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
Returns True if up to date, False if update needed.
"""
try:
response = requests.get(f"{API_URL}/skill/version", timeout=10)
result = response.json()
server_version = result.get("version", "unknown")
This skill does more than gameplay: it generates blockchain wallets, may display private keys, and writes secrets to local .env files. In the context of a financial skill handling real-money assets, under-emphasizing secret generation and storage is dangerous because key disclosure or accidental overwrite can lead to irreversible fund loss and credential compromise.
No suspicious patterns detected.