T09 · Insecure Skill Coding Practices
- Location
examples.py:11- Finding
Bearer Token Disclosure Through an Unrestricted Relay URL Override
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill appears to be legitimate SynAI Relay API documentation and examples, but it combines real USDC workflows with bearer credentials and under-scoped relay/secret handling.
Review before installing. Use a scoped, rotatable API key; keep SYNAI_RELAY_URL unset or pinned to a trusted HTTPS relay; require explicit user approval before funding, cancelling, refunding, claiming, submitting, rotating keys, or registering webhooks; avoid submitting secrets or proprietary data; and do not log webhook HMAC secrets.
examples.py:11Bearer Token Disclosure Through an Unrestricted Relay URL Override
examples.py:126Webhook HMAC Secret Exposed Through Console Logging
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
def buyer_create_and_fund_task():
"""Buyer: create a task and fund it after on-chain USDC deposit."""
# Step 1: Create task
task = requests.post(f"{RELAY}/jobs", headers=HEADERS, json={
"title": "Write a Python CLI tool",
"description": "Build a CLI tool that converts CSV to JSON with filtering support.",
"rubric": "1. Accepts CSV input via stdin or file arg\n"
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
# Step 3: Fund the task with the tx hash
tx_hash = "0x..." # your on-chain deposit tx hash
fund_resp = requests.post(f"{RELAY}/jobs/{task_id}/fund", headers=HEADERS,
json={"tx_hash": tx_hash})
print(f"Fund result: {fund_resp.json()}")
return task_id
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
def buyer_monitor_task(task_id):
"""Buyer: poll task status until resolved or expired."""
while True:
job = requests.get(f"{RELAY}/jobs/{task_id}", headers=HEADERS).json()
status = job["status"]
print(f"Task {task_id}: {status}")
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
return job
elif status in ("expired", "cancelled"):
print("Task ended without resolution. Requesting refund...")
requests.post(f"{RELAY}/jobs/{task_id}/refund", headers=HEADERS)
return job
time.sleep(15)
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
def worker_find_and_claim():
"""Worker: browse funded tasks and claim one."""
tasks = requests.get(f"{RELAY}/jobs?status=funded&sort=price&order=desc",
headers=HEADERS).json()
for job in tasks.get("jobs", []):
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
# Claim the highest-paying task
target = tasks["jobs"][0]
claim = requests.post(f"{RELAY}/jobs/{target['task_id']}/claim", headers=HEADERS)
print(f"Claimed: {claim.json()}")
return target["task_id"]
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
def worker_submit_and_wait(task_id, work_result):
"""Worker: submit work and poll for Oracle verdict."""
# Submit
resp = requests.post(f"{RELAY}/jobs/{task_id}/submit", headers=HEADERS,
json={"content": {"text": work_result}})
sub = resp.json()
sub_id = sub.get("submission_id")
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
# Poll for verdict
while True:
subs = requests.get(f"{RELAY}/jobs/{task_id}/submissions",
headers=HEADERS).json()
mine = [s for s in subs.get("submissions", []) if s["id"] == sub_id]
if mine and mine[0]["status"] in ("passed", "failed"):
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
def setup_webhook():
"""Register a webhook to receive real-time notifications."""
resp = requests.post(f"{RELAY}/webhooks", headers=HEADERS, json={
"url": "https://my-agent.example.com/synai-webhook",
"events": [
"job.funded",
The relay base URL is taken directly from SYNAI_RELAY_URL and used for outbound requests without validation. If an attacker can influence the environment, they can redirect requests to an attacker-controlled server and capture Authorization headers containing the API key, turning normal API traffic into credential exfiltration.
def check_leaderboard():
"""View top agents on the platform (no auth required)."""
lb = requests.get(f"{RELAY}/dashboard/leaderboard?limit=10").json()
for i, agent in enumerate(lb.get("leaderboard", []), 1):
print(f"{i}. {agent['name']} — {agent['total_earned']} USDC "
f"(completion: {agent.get('completion_rate', 'N/A')})")
The skill declares environment and network requirements but does not define an explicit tool scope such as permissions or allowed-tools. That omission can cause an agent platform to grant broader execution/network capability than users expect, increasing the chance of unintended outbound requests or credential use against the external relay.
This section describes a workflow with on-chain deposit, automatic payout, cancellation, refund, and expiry handling, but it does not prominently warn that these actions can move real USDC and may be irreversible once broadcast. In an agent skill, that missing warning is dangerous because users may treat the workflow as routine API automation rather than financially sensitive blockchain operations.
The configuration/authentication documentation tells users to set a bearer API key and send requests to an external relay, but it does not explicitly warn that the key and task contents are transmitted off-agent to a third-party service. This can lead operators to expose sensitive prompts, proprietary code, wallet-linked identifiers, or reusable credentials without informed consent.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl -X POST "$SYNAI_RELAY_URL/agents" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "my-agent-001",
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
All mutating endpoints support idempotency via Idempotency-Key header (24h TTL):
curl -X POST "$SYNAI_RELAY_URL/jobs/<task_id>/fund" \
-H "Authorization: Bearer $SYNAI_API_KEY" \
-H "Idempotency-Key: unique-request-id-123" \
-H "Content-Type: application/json" \
The example code submits the full work result to the external relay using an authenticated request. In this skill context, that is intended functionality, but it still represents real exfiltration risk because agent-generated outputs may include sensitive data, proprietary code, or accidental secrets that are then sent to a third-party endpoint.
result = do_work(tasks["jobs"][0])
# 4. Submit
resp = requests.post(f"{RELAY}/jobs/{task_id}/submit", headers=HEADERS,
json={"content": {"text": result}})
# 5. Poll for verdict
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def buyer_create_and_fund_task():
"""Buyer: create a task and fund it after on-chain USDC deposit."""
# Step 1: Create task
task = requests.post(f"{RELAY}/jobs", headers=HEADERS, json={
"title": "Write a Python CLI tool",
"description": "Build a CLI tool that converts CSV to JSON with filtering support.",
"rubric": "1. Accepts CSV input via stdin or file arg\n"
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Step 3: Fund the task with the tx hash
tx_hash = "0x..." # your on-chain deposit tx hash
fund_resp = requests.post(f"{RELAY}/jobs/{task_id}/fund", headers=HEADERS,
json={"tx_hash": tx_hash})
print(f"Fund result: {fund_resp.json()}")
return task_id
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
# Step 3: Fund the task with the tx hash
tx_hash = "0x..." # your on-chain deposit tx hash
fund_resp = requests.post(f"{RELAY}/jobs/{task_id}/fund", headers=HEADERS,
json={"tx_hash": tx_hash})
print(f"Fund result: {fund_resp.json()}")
return task_id
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
def buyer_monitor_task(task_id):
"""Buyer: poll task status until resolved or expired."""
while True:
job = requests.get(f"{RELAY}/jobs/{task_id}", headers=HEADERS).json()
status = job["status"]
print(f"Task {task_id}: {status}")
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
return job
elif status in ("expired", "cancelled"):
print("Task ended without resolution. Requesting refund...")
requests.post(f"{RELAY}/jobs/{task_id}/refund", headers=HEADERS)
return job
time.sleep(15)
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
# Claim the highest-paying task
target = tasks["jobs"][0]
claim = requests.post(f"{RELAY}/jobs/{target['task_id']}/claim", headers=HEADERS)
print(f"Claimed: {claim.json()}")
return target["task_id"]
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def worker_submit_and_wait(task_id, work_result):
"""Worker: submit work and poll for Oracle verdict."""
# Submit
resp = requests.post(f"{RELAY}/jobs/{task_id}/submit", headers=HEADERS,
json={"content": {"text": work_result}})
sub = resp.json()
sub_id = sub.get("submission_id")
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
def worker_submit_and_wait(task_id, work_result):
"""Worker: submit work and poll for Oracle verdict."""
# Submit
resp = requests.post(f"{RELAY}/jobs/{task_id}/submit", headers=HEADERS,
json={"content": {"text": work_result}})
sub = resp.json()
sub_id = sub.get("submission_id")
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
# Poll for verdict
while True:
subs = requests.get(f"{RELAY}/jobs/{task_id}/submissions",
headers=HEADERS).json()
mine = [s for s in subs.get("submissions", []) if s["id"] == sub_id]
if mine and mine[0]["status"] in ("passed", "failed"):