Back to skill

Security audit

Simmer Signal Service

Security checks for vulnerabilities and agentic risk

Overview

This paid trading-signal skill uses disclosed APIs, but its billing disclosures and safeguards are inconsistent enough that users should review it carefully before installing.

Review this skill before installing. Do not enable its recurring cron examples unless the price is made consistent and you have a spend cap. Provide SkillPay and Simmer API keys only if you trust the publisher and services, and prefer a version that fails closed on billing errors, removes fabricated demo payments from production, documents the real data sources, and pins dependencies.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:19
Finding

Contradictory pricing can cause recurring charges at ten times the prominently advertised rate

Content
View full analysis
💰 **Developer revenue**: Each call automatically charges 0.001 USDT, generating passive income 24 hours a day > **Billing model**: 1 token per call = 0.001 USDT (automatically deducted through SkillPay) ``` The same document later contradicts that price: ```markdown - Each time a user obtains a signal → automatically deduct 0.01 USDT ``` The implementation charges 0.01 USDT: ```python response = requests.post( "https://api.skillpay.me/v1/billing/charge", headers={ "Authorization": f"Bearer {SKILLPAY_API_KEY}", "Content-Type": "application/json" }, json={ "skill_id": SKILLPAY_SKILL_ID, "user_id": user_id, "amount": 0.01 }, timeout=10 ) ``` It also always reports the higher amount: ```python log(f"Charged 0.01 USDT, remaining: {charge_result.get('remaining', 0)}") signal = generate_signal(args.asset.upper()) result = { "charged_usdt": 0.01, "balance_remaining": charge_result.get("remaining"), "signal": signal } ``` The documentation encourages execution every five minutes: ```bash openclaw cron add \ --name "BTC Signal Check" \ --cron "*/5 * * * *" \ --message "Run: cd ~/skills/simmer-signal-service && python signal_service.py --asset BTC --user-id your-wallet-address" \ --announce ``` ```bash crontab -e */5 * * * * cd ~/skills/simmer-signal-service && python signal_service.py --asset BTC --user-id your-wallet-address >> ~/signal.log 2>&1 ``` ### Technical Analysis The prominent price claims state that each signal costs 0.001 USDT, while the actual charge request submits 0.01 USDT. This is a tenfold discrepa ...[truncated 1606 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
signal_service.py:45
Finding

Billing failures are treated as successful authorization and payment

Content
View full analysis
= TOKENS_PER_CALL return { "can_charge": can_charge, "balance_tokens": balance_tokens, "balance_usdt": round(balance_usdt, 4), "payment_url": None if can_charge else get_payment_link(user_id) } return {"can_charge": False, "balance_tokens": 0, "balance_usdt": 0, "error": f"API error: {response.status_code}"} except Exception as e: # Fallback to demo mode on error log(f"SkillPay API error: {e}, using demo mode") return {"can_charge": True, "balance_tokens": 999000, "balance_usdt": 999.0, "payment_url": None, "demo_mode": True} ``` The charge operation likewise fabricates a successful payment following any exception: ```python try: response = requests.post( "https://api.skillpay.me/v1/billing/charge", headers={ "Authorization": f"Bearer {SKILLPAY_API_KEY}", "Content-Type": "application/json" }, json={ "skill_id": SKILLPAY_SKILL_ID, "user_id": user_id, "amount": 0.01 }, timeout=10 ) if response.status_code == 200: data = response.json() return { "success": True, ...[truncated 2282 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
signal_service.py:59
Finding

Undefined payment-link function bypasses valid insufficient-balance responses

Content
View full analysis
= TOKENS_PER_CALL return { "can_charge": can_charge, "balance_tokens": balance_tokens, "balance_usdt": round(balance_usdt, 4), "payment_url": None if can_charge else get_payment_link(user_id) } return {"can_charge": False, "balance_tokens": 0, "balance_usdt": 0, "error": f"API error: {response.status_code}"} except Exception as e: # Fallback to demo mode on error log(f"SkillPay API error: {e}, using demo mode") return {"can_charge": True, "balance_tokens": 999000, "balance_usdt": 999.0, "payment_url": None, "demo_mode": True} ``` ### Technical Analysis `get_payment_link()` is referenced but is not defined anywhere in the reviewed project. When SkillPay returns HTTP 200 with a balance lower than `TOKENS_PER_CALL`, `can_charge` is false and Python evaluates the `else` branch of the conditional expression. Calling the undefined function raises `NameError`. Because the entire operation is enclosed by a broad exception handler, that programming defect is converted into a fabricated successful balance result. Thus, the precise state that should deny service—an insufficient balance—can instead authorize service. ### Attack Path 1. A user has fewer tokens than the required billing threshold. 2. SkillPay successfully returns that low balance with HTTP 200. 3. `can_charge` becomes false. 4. Evaluation proceeds to `get_payment_link(user_id)`. 5. The undefined name raises `NameError`. 6. The broad exception handler catches the error and returns `can_charge: True` with 999,000 fabricated tokens. 7. The caller proceeds to the charge stage ...[truncated 473 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding

Open-ended dependency version permits unreviewed future releases

Content
View full analysis
=2.28.0 ``` ### Technical Analysis The lower-bound-only requirement allows any future version of `requests` to be installed. Installation results can therefore change over time without corresponding changes to the audited project. This weakens reproducibility and permits an unreviewed future release to enter the runtime environment. No evidence was found that the current package name is typosquatted or that a malicious release is presently selected. The risk is prospective supply-chain exposure caused by the open-ended constraint, rather than evidence of an already malicious dependency. ### Attack Path 1. A future compromised, malicious, or incompatible `requests` release satisfies `>=2.28.0`. 2. A user installs the Skill in a fresh or updated environment. 3. The package resolver selects that unreviewed release. 4. The dependency executes within the Python process when imported or used. 5. Any malicious dependency behavior would inherit the process's access to environment variables and network capabilities, including access to the API credentials loaded by the Skill. ### Impact Assessment If a selected future dependency were compromised, it would execute with the same privileges as the Skill process. This could potentially expose `SKILLPAY_API_KEY` and `SIMMER_API_KEY`, alter billing requests, manipulate signal data, or access files available to that user account. The current repository alone does not demonstrate that such compromise has occurred. ]]>
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (18)

Tainted flow: 'SKILLPAY_API_KEY' from os.getenv (line 15, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · signal_service.py (reported line 49)May include surrounding context.

python
# Real API call using official SDK format
    try:
        response = requests.get(
            f"{SKILLPAY_BASE_URL}/billing/balance",
            headers={
                "X-API-Key": SKILLPAY_API_KEY,

Tainted flow: 'SKILLPAY_SKILL_ID' from os.getenv (line 16, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · signal_service.py (reported line 90)May include surrounding context.

python
# Real API call
    try:
        response = requests.post(
            "https://api.skillpay.me/v1/billing/charge",
            headers={
                "Authorization": f"Bearer {SKILLPAY_API_KEY}",

Tainted flow: 'SIMMER_API_KEY' from os.getenv (line 17, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · signal_service.py (reported line 161)May include surrounding context.

python
return {"available": False, "reason": "No API key"}
    
    try:
        response = requests.get(
            "https://api.simmer.markets/api/sdk/briefing",
            headers={"Authorization": f"Bearer {SIMMER_API_KEY}"},
            timeout=10

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The documented behavior materially conflicts with the described billing and service semantics: the file advertises 0.001 USDT per signal but multiple sections and sample output indicate 0.01 USDT charges, and it claims Polymarket signals without actual Polymarket integration. In a paid, trading-related skill, these discrepancies can mislead users into incurring unexpected costs or relying on inaccurately represented financial functionality.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill declares network and environment-variable requirements in metadata but does not declare an explicit tool scope such as permissions or allowed-tools. That weakens user visibility and platform enforcement around what the skill can access, which is especially relevant because it handles API keys and performs paid network actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill explicitly promotes scheduled execution every 5 minutes and frames it as passive income for the developer, but it does not present a prominent user-facing warning that each automated invocation triggers another paid charge. In a micropayment service, this omission can lead to unbounded repeated billing and user harm through accidental balance depletion.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
85% confidence
Finding

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.

Content

Scanner excerpt · SKILL.md (reported line 152)May include surrounding context.

bash
# 编辑 crontab
crontab -e

# 添加每5分钟运行
*/5 * * * * cd ~/skills/simmer-signal-service && python signal_service.py --asset BTC --user-id 你的钱包地址 >> ~/signal.log 2>&1

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The pricing comments state 1 token per call equals 0.001 USDT, but the charging code later bills 0.01 USDT. This mismatch can mislead users and integrators about the real price and creates a billing-integrity issue, especially dangerous in a paid trading-signal skill where users may rely on advertised micro-pricing.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The docstring says the function is 'DEMO MODE for testing', but for non-demo users it performs a real external charge. Mislabeling live-billing code as demo/test code can cause unsafe deployment assumptions, accidental production charges, and reduced operator scrutiny over financially sensitive behavior.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
92% confidence
Finding

This code performs an irreversible external billing action based on a supplied user ID, and on network/API failure it later falls back to reporting success rather than failing closed. In a paid service context, external financial operations require strict integrity guarantees; otherwise users can receive service without valid billing or operators can lose auditability over whether charges actually occurred.

Content

Scanner excerpt · signal_service.py (reported line 90)May include surrounding context.

python
# Real API call
    try:
        response = requests.post(
            "https://api.skillpay.me/v1/billing/charge",
            headers={
                "Authorization": f"Bearer {SKILLPAY_API_KEY}",

External Transmission

Medium
Category
Data Exfiltration
Confidence
92% confidence
Finding

This code performs an irreversible external billing action based on a supplied user ID, and on network/API failure it later falls back to reporting success rather than failing closed. In a paid service context, external financial operations require strict integrity guarantees; otherwise users can receive service without valid billing or operators can lose auditability over whether charges actually occurred.

Content

Scanner excerpt · signal_service.py (reported line 90)May include surrounding context.

python
# Real API call
    try:
        response = requests.post(
            "https://api.skillpay.me/v1/billing/charge",
            headers={
                "Authorization": f"Bearer {SKILLPAY_API_KEY}",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · signal_service.py (reported line 91)May include surrounding context.

python
# Real API call
    try:
        response = requests.post(
            "https://api.skillpay.me/v1/billing/charge",
            headers={
                "Authorization": f"Bearer {SKILLPAY_API_KEY}",
                "Content-Type": "application/json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · signal_service.py (reported line 136)May include surrounding context.

python
def fetch_binance_price(symbol: str) -> dict:
    """Fetch price from Binance"""
    try:
        url = f"https://api.binance.com/api/v3/klines?symbol={symbol}USDT&interval=1m&limit=10"
        response = requests.get(url, timeout=5)
        data = response.json()

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · signal_service.py (reported line 162)May include surrounding context.

python
try:
        response = requests.get(
            "https://api.simmer.markets/api/sdk/briefing",
            headers={"Authorization": f"Bearer {SIMMER_API_KEY}"},
            timeout=10
        )

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill constructs user-facing reasoning strings in Chinese, including messages like '10分钟变动', '波动较小', and '建议观望'. This imposes a specific language on all users, and the file does not offer a language choice or explain a justified region-specific requirement.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The user-facing instructions and operational guidance are predominantly presented in Chinese, but the skill does not state that it is Chinese-only or provide an opt-in language choice. This can violate language/locale policy expectations when a specific language is effectively forced on users without notice.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
98% confidence
Finding

The dependency is specified as requests>=2.28.0, which allows any future major or minor version and does not guarantee reproducible installs. This increases supply-chain risk and makes it harder to verify whether deployed environments include a vulnerable or incompatible release.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
requests>=2.28.0

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
95% confidence
Finding

Because the manifest does not pin requests to a specific version, it is not possible to determine whether installations will resolve to a release affected by known advisories. In a network-facing trading-signal skill that likely performs outbound HTTP calls, uncertainty around the exact requests version increases the chance of inheriting known client-side flaws such as credential leakage or TLS/request-handling issues.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.