Back to skill

Security audit

wger Fitness Manager

Security checks for vulnerabilities and agentic risk

Overview

This fitness API skill is mostly purpose-aligned, but it needs Review because it handles sensitive fitness data with under-scoped remote writes, automation, and self-hosting guidance.

Install only if you are comfortable giving the skill an API token that can read and modify your wger fitness data. Prefer a token scoped to the intended wger instance, verify the exact API base URL before using the bundled scripts, require explicit approval for POST/PATCH actions and automation, and change the self-hosted database password and image pinning before using the Docker example beyond local testing.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/create_log.py:7
Finding

Hard-Coded Hosted API Endpoint Can Disclose Tokens and Fitness Data

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:10-12, scripts/create_log.py:7-20, scripts/view_logs.py:8-20
Vulnerability Type: Hard-coded external destination inconsistent with documented self-hosting support
Risk Level: Medium

The Skill documentation states that users may use either https://wger.de/api/v2/ or a self-hosted URL:

markdown
## Setup (One-Time)
- API Base: https://wger.de/api/v2/ (or self-hosted URL).
- Auth: Token from wger dashboard (User > API). Set env WGER_TOKEN=your_key or pass in commands.

However, scripts/create_log.py unconditionally transmits the configured token and workout data to the public wger endpoint:

python
BASE_URL = 'https://wger.de/api/v2/'
TOKEN = os.getenv('WGER_TOKEN')

if not TOKEN:
    print("Error: Set WGER_TOKEN env var")
    sys.exit(1)

def create_log(date, workout_id, exercises):
    headers = {'Authorization': f'Token {TOKEN}', 'Content-Type': 'application/json'}
    data = {
        'date': date,
        'workout': workout_id,
        'exercises': exercises  # List of dicts: [{'reps': 10, 'weight': 135, 'exercise': exercise_id}]
    }
    response = requests.post(f'{BASE_URL}workoutlog/', json=data, headers=headers)

scripts/view_logs.py has the same fixed destination:

python
BASE_URL = 'https://wger.de/api/v2/'
TOKEN = os.getenv('WGER_TOKEN')  # Set env WGER_TOKEN=your_key

if not TOKEN:
    print("Error: Set WGER_TOKEN env var")
    sys.exit(1)

def view_logs(limit=7, workout_id=None):
    headers = {'Authorization': f'Token {TOKEN}', 'Content-Type': 'application/json'}
    url = f'{BASE_URL}workoutlog/?limit={limit}&format=json'
    if workout_id:
        url += f'&workout={workout_id}'
    response = requests.get(url, headers=headers)

Technical Analysis

External API access is required for the declared hosted-mode functionality, so the network behavior is not inherent ...[truncated 2123 chars]

Remediation
View remediation

Remediation Suggestions

  1. Read the API destination from an explicit environment variable, such as WGER_BASE_URL, rather than hard-coding the public service:
    python
    BASE_URL = os.environ.get("WGER_BASE_URL", "https://wger.de/api/v2/").rstrip("/") + "/"
    
  2. Clearly document whether the public endpoint is the default. For privacy-sensitive operations, consider requiring the user to set the endpoint explicitly.
  3. Parse and validate the URL before attaching credentials. Allow only https for remote hosts; permit plain HTTP only for explicitly selected loopback development endpoints.
  4. Display the destination hostname before the first authenticated request or require confirmation when transmitting fitness data to a non-loopback service.
  5. Keep credentials scoped to one endpoint and advise users not to reuse tokens between hosted and self-hosted installations.
  6. Add automated tests confirming that both scripts honor WGER_BASE_URL and never silently fall back to wger.de when a self-hosted endpoint is configured.
  7. Set explicit request timeouts and use a shared, validated API client so all workflows apply the same destination and credential controls.

T08 · Insecure Dependencies

Warning
Location
references/selfhost.md:5
Finding

Self-Hosting Configuration Uses a Weak Fixed Password and Mutable Container Image

Content
View full analysis

Vulnerability Details

File Location: references/selfhost.md:5-25
Vulnerability Type: Insecure deployment credentials and unpinned supply-chain dependency
Risk Level: Medium

The supplied Docker Compose example contains the following configuration:

yaml
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_DB: wger
      POSTGRES_USER: wger
      POSTGRES_PASSWORD: pass
    volumes:
      - db_data:/var/lib/postgresql/data
  wger:
    image: wger/server:latest
    ports:
      - "8000:80"
    environment:
      DB_HOST: db
      DB_NAME: wger
      DB_USER: wger
      DB_PASS: pass
    depends_on:
      - db
volumes:
  db_data:

Technical Analysis

The database password is the fixed, trivial value pass. Users commonly copy deployment examples directly, so this value may remain unchanged in real installations. Although PostgreSQL is not published directly to the host in this example, it is reachable from containers attached to the Compose network. Any compromised or untrusted container with access to that network would know both the database username and password.

The application image uses wger/server:latest. The latest tag is mutable and does not identify a specific reviewed artifact. A later deployment or image pull can therefore retrieve different code from the code that existed when this Skill was audited. postgres:15 is also a moving major-version tag, though it is narrower than latest.

This is a deployment hardening and supply-chain reproducibility issue. The audit found no evidence that the referenced images are currently malicious.

Attack Path

A credential-based exploitation path is:

  1. A user copies the documented Compose configuration without changing POSTGRES_PASSWORD.
  2. An attacker compromises another container, introduces an untrusted container, or otherwise obtains access to the Compose network.
  3. The attacke ...[truncated 1395 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove the fixed password from the example and require a cryptographically random secret:
    yaml
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/wger_db_password
    secrets:
      - wger_db_password
    
  2. Store the password through Docker secrets or another dedicated secret manager. If an .env file is used, ensure it is excluded from version control and protected with restrictive filesystem permissions.
  3. Generate a unique password for each deployment and document a secure generation command.
  4. Pin container images to reviewed immutable digests, for example:
    yaml
    image: wger/server@sha256:<reviewed-digest>
    
  5. Pin PostgreSQL to a reviewed patch version and preferably an immutable digest rather than a moving major-version tag.
  6. Use automated dependency monitoring to identify security updates, then review and deliberately update pinned digests.
  7. Isolate the database on an internal network accessible only to the wger application container.
  8. Apply least-privilege database permissions to the application account and avoid using a database superuser for routine application access.
  9. Add container hardening where compatible, including a non-root user, dropped Linux capabilities, a read-only root filesystem, and resource limits.
  10. Replace the plain HTTP exposure guidance with instructions for a trusted TLS reverse proxy or a private VPN-only deployment.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (8)

Tainted flow: 'headers' from os.getenv (line 15, 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 · scripts/create_log.py (reported line 21)May include surrounding context.

python
'workout': workout_id,
        'exercises': exercises  # List of dicts: [{'reps': 10, 'weight': 135, 'exercise': exercise_id}]
    }
    response = requests.post(f'{BASE_URL}workoutlog/', json=data, headers=headers)
    if response.status_code in [200, 201]:
        print(f"Log created: {response.json()}")
    else:

Tainted flow: 'headers' from os.getenv (line 16, 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 · scripts/view_logs.py (reported line 20)May include surrounding context.

python
url = f'{BASE_URL}workoutlog/?limit={limit}&format=json'
    if workout_id:
        url += f'&workout={workout_id}'
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        data = response.json()
        print(json.dumps(data, indent=2))

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill explicitly advertises API-based pulls and pushes, including editing and creating workout data, but it does not clearly warn the user that invoking the skill may modify remote fitness records. In an agent context, this can lead to unintended writes to a third-party service and unexpected disclosure or alteration of personal health-related data.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

The skill is designed to send data to an external service via curl and to involve subagents for analysis, which creates a real external transmission path for potentially sensitive workout, nutrition, and progress data. While this is core functionality rather than obviously malicious behavior, the lack of strong disclosure and guardrails makes the transmission security-relevant in an agent environment.

Content

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

md
# wger Fitness Manager

wger is an open-source fitness tracker with REST API for routines, logs, nutrition, and progress. This skill handles API interactions for seamless integration with OpenClaw (exec/curl for calls, subagents for analysis).

## Setup (One-Time)
- API Base: https://wger.de/api/v2/ (or self-hosted URL).

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The examples include authenticated POST and PATCH requests to the remote wger service, but there is no clear notice that workout and nutrition data will be transmitted off-box to an external endpoint. Because these examples may be followed by an agent or user with minimal scrutiny, the omission increases the risk of accidental disclosure or unauthorized modification of sensitive fitness data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The example exposes the service on a host port and uses trivial default database credentials ('pass') without a prominent warning to change them before real use. While localhost access is somewhat limited, Docker port publishing can still expand exposure depending on host/network configuration, and reused example credentials materially increase risk if the setup is copied into a non-local or shared environment.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% 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 · scripts/create_log.py (reported line 21)May include surrounding context.

python
'workout': workout_id,
        'exercises': exercises  # List of dicts: [{'reps': 10, 'weight': 135, 'exercise': exercise_id}]
    }
    response = requests.post(f'{BASE_URL}workoutlog/', json=data, headers=headers)
    if response.status_code in [200, 201]:
        print(f"Log created: {response.json()}")
    else:

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This code constructs an Authorization header from the WGER_TOKEN environment variable and sends it in an HTTP request, but the script provides no confirmation, warning, or explanatory comment about transmitting credentials to a remote service. For code files, outbound network calls carrying user or system data should have some visible disclosure unless clearly covered by the skill description, which is not present here.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.