T09 · Insecure Skill Coding Practices
- Location
skills/seesaw/scripts/seesaw.py:13- Finding
SeeSaw credentials can be transmitted to an untrusted or plaintext endpoint
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This is a real SeeSaw prediction-market client, but it needs Review because it can run live financial and account-changing automation with weak safeguards.
Install only if you understand that this skill can affect real SeeSaw account state and funds. Before using automation, set dry_run to true, avoid --yes for live workflows, verify SEESAW_BASE_URL is the intended HTTPS SeeSaw endpoint, protect and rotate API keys, and avoid running LLM/search-driven trading or oracle scripts until they require explicit per-transaction review and safer token storage.
skills/seesaw/scripts/seesaw.py:13SeeSaw credentials can be transmitted to an untrusted or plaintext endpoint
skills/seesaw/scripts/seesaw.py:9Bearer token is stored in a predictable shared temporary file
skills/seesaw/scripts/open_position.py:113Untrusted search content can influence automated financial transactions through the LLM
skills/seesaw/scripts/auto_assert.py:20Hard-coded creator identity redirects “my markets” automation to a fixed third-party account
skills/seesaw/scripts/utils.py:129Gemini API key is embedded in the request URL
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
url = "https://api.search.brave.com/res/v1/images/search"
params = {"q": query, "count": max_results}
try:
resp = requests.get(url, headers=headers, params=params, timeout=10)
data = resp.json()
images = [item.get("properties", {}).get("url") for item in data.get("results", [])]
return {"text": "", "images": [img for img in images if img]}
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
url = "https://api.search.brave.com/res/v1/images/search"
params = {"q": query, "count": max_results}
try:
resp = requests.get(url, headers=headers, params=params, timeout=10)
data = resp.json()
images = [item.get("properties", {}).get("url") for item in data.get("results", [])]
return {"text": "", "images": [img for img in images if img]}
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
}
try:
resp = requests.post(url, json=payload, headers=headers, timeout=15)
resp.raise_for_status()
data = resp.json()
if not data.get("ok"):
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
}
try:
resp = requests.post(url, json=payload, headers=headers, timeout=60)
resp.raise_for_status()
data = resp.json()
return data["candidates"][0]["content"]["parts"][0]["text"]
The code can settle completed markets and execute live trades through the Seesaw client even though no manifest scope or permission boundary is shown. In an agent skill context, undisclosed live financial side effects are dangerous because the skill can modify real positions and move funds if invoked with valid credentials.
Settlement and trading operations are performed automatically once conditions are met, with no user confirmation step before modifying positions. For a financial automation skill, this lack of approval significantly increases the chance of unintended loss from bad data, model errors, misconfiguration, or malicious prompting through external inputs.
The client defaults to http://localhost:3000/v1 and transmits api_key and api_secret during login, which means credentials may be sent in cleartext if the service is not strictly local or if traffic is intercepted/redirected. Even on local systems, insecure defaults normalize unsafe deployment and can expose secrets through proxies, port-forwarding, or compromised hosts.
The skill documentation instructs users to store API credentials in a local config file and also shows passing secrets directly on the command line. Command-line arguments can be exposed via shell history, process listings, logs, or terminal recording, and the markdown provides no warning or safer alternative handling guidance.
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.
```bash
mkdir -p ~/.openclaw/workspace/skills
cp -R skills/seesaw ~/.openclaw/workspace/skills/
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.
git clone https://github.com/SeesawTech/SeeSaw-Claw.git
mkdir -p ~/.openclaw/workspace/skills
cp -R SeeSaw-Claw/skills/seesaw ~/.openclaw/workspace/skills/
pip install -r ~/.openclaw/workspace/skills/seesaw/requirements.txt
openclaw skills check
The documentation instructs users to configure API credentials and perform live financial trading actions, but it does not prominently warn about secret handling, real-money exposure, or the risk of unintended trades. In an agent skill context, this increases the chance that users paste sensitive keys into unsafe places or run commands assuming they are harmless, leading to account compromise or monetary loss.
The skill documents account-affecting and potentially irreversible operations such as buy, sell, claim, follow/block, comment deletion, oracle actions, and market creation without clear warnings about permanence or user confirmation requirements. In a prediction-market environment, misuse of these commands can cause financial loss, reputation impact, or irreversible market/account state changes.
The script pulls untrusted external news content and feeds it to an LLM, then directly uses the model's JSON output to decide whether to buy, sell, or hold a live prediction-market position. This creates an unsafe autonomous decision loop where prompt-influenced or low-quality inputs can trigger financial actions without deterministic controls, making the trading behavior easy to manipulate or degrade.
The prompt explicitly instructs the model to 'Write a short update (in Chinese...)', which imposes a fixed language policy on output. There is no visible opt-in, configuration, or documentation here indicating that Chinese-only output is required or user-selected.
The script sends derived market subjects to a web search service and then forwards up to 1500–2000 characters of retrieved news text into LLM prompts. While the script logs high-level progress, it does not disclose in comments, docstrings, or user-facing warnings that market-related content will be sent to external services.
The script performs a state-changing reward claim by calling client.claim_challenge(c_id) as part of normal execution, with only console logging and an optional dry-run as safeguards. This can cause unintended claims if the script is run in the wrong environment, by automation, or by a user who does not realize it will irreversibly act on available rewards.
The script transmits the proposed title, description, probabilities, and image URL to Slack before the interactive confirmation step. Although the action is logged by surrounding print statements, the file does not disclose to the user that generated content will be sent to an external messaging service prior to approval, which is a network/data-sharing operation.
In dry-run mode, the script automatically sets confirmation to yes, bypassing the human approval gate for the decision path. Although dry-run does not create the market in this file, it still simulates approval and can normalize unattended operation of an LLM-driven workflow that selects and advances external content without meaningful review, increasing the chance of unsafe or misleading outputs being treated as approved.
confirm = "y"
elif dry_run:
confirm = "y"
print("[DRY-RUN] Auto-confirming.")
else:
confirm = input("Create this market? (y/n): ")
The script places real buy orders directly from LLM-generated recommendations once a confidence threshold is met, with no human approval, secondary validation, or hard safety guard. Because the decision input includes untrusted external news/search content and a non-deterministic model response, prompt manipulation, bad model judgment, or malformed output could trigger unintended live trades and financial loss.
The authentication token is cached in /tmp/seesaw_token.json, a shared world-accessible temporary location on many systems, with no file permission hardening or expiration handling. Other local users or processes could read or replace the token, leading to account/session hijacking or unauthorized API actions.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
url = f"{self.base_url}/auth/agent-login"
payload = {"api_key": self.api_key, "api_secret": self.api_secret}
try:
resp = requests.post(url, json=payload, timeout=10)
if not resp.ok: print(f"Error Body: {resp.text}")
resp.raise_for_status()
except requests.exceptions.RequestException as e:
Data is uploaded to cloud storage (S3 / GCS / Azure Blob). This may be a legitimate backup or exfiltration to an external bucket. Manual review is recommended.
print(json.dumps(client.create_market(args.title, args.options, args.probs, args.end_time, args.description, args.images), indent=2))
elif args.command == "upload":
presigned = client.get_presigned_url(args.type, args.ext)
client.upload_file(presigned["upload_url"], args.file, args.type)
print(json.dumps({"file_url": presigned["file_url"]}, indent=2))
elif args.command == "create-share-link":
print(json.dumps(client.create_share_link(args.market_id, args.image_url, args.share_source, args.share_target), indent=2))
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
"""Parse command line arguments including standard ones like --dry-run."""
parser = argparse.ArgumentParser(description=description)
parser.add_argument("--dry-run", action="store_true", help="Simulate actions without executing")
parser.add_argument("--yes", action="store_true", help="Skip confirmation prompts")
return parser.parse_args()
def search_web(query, topic=None, days=3, max_results=5, include_images=False):
The code issues outbound requests to Brave search endpoints for web and image retrieval. Because no manifest or declared purpose is provided, this network-search capability is unjustified by the available intent context and represents a broader capability than a neutral local utility helper would imply.
Image search sends the query to Brave Search, an external provider. If the query contains sensitive user or workspace data, that information leaves the environment without any visible consent mechanism in this helper.
headers = {"Accept": "application/json", "X-Subscription-Token": brave_key}
if include_images:
url = "https://api.search.brave.com/res/v1/images/search"
params = {"q": query, "count": max_results}
try:
resp = requests.get(url, headers=headers, params=params, timeout=10)
No suspicious patterns detected.