Back to skill

Security audit

Transition MCP

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent Transition coaching API wrapper, but it has under-disclosed credential and data-routing risks that users should review before installing.

Install only if you trust Transition with your workout history, profile, performance metrics, coach-chat messages, and any health-related details you provide. Keep TRANSITION_API_KEY private, avoid setting TRANSITION_API_URL unless you are intentionally using a trusted endpoint, and explicitly approve plan changes or third-party pushes such as Garmin before running them.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
mcp/main.go:12
Finding
API Key Can Be Transmitted to an Arbitrary Configured Server<![CDATA[ ## Vulnerability Details **File Location**: `mcp/main.go:12-17` and `mcp/client.go:30-45` **Vulnerability Type**: Unrestricted destination configuration and credential disclosure **Risk Level**: High ### Vulnerable Code ```go // mcp/main.go:12-17 apiKey := os.Getenv("TRANSITION_API_KEY") baseURL := os.Getenv("TRANSITION_API_URL") if baseURL == "" { baseURL = "https://api.transition.fun" } client := NewTransitionClient(baseURL, apiKey) ``` ```go // mcp/client.go:30-45 func (tc *TransitionClient) doRequest(method, path string, body io.Reader) (*http.Response, error) { url := tc.baseURL + path req, err := http.NewRequest(method, url, body) if err != nil { return nil, err } if tc.apiKey != "" { req.Header.Set("X-API-Key", tc.apiKey) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") return tc.httpClient.Do(req) } ``` ### Technical Analysis The server obtains its destination from `TRANSITION_API_URL` without validating the URL scheme or hostname. The HTTP client then attaches `TRANSITION_API_KEY` as an `X-API-Key` header to every request, regardless of the configured destination. Consequently, setting `TRANSITION_API_URL` to an untrusted origin causes the application to disclose the Transition API key to that origin. A plain HTTP destination would additionally expose the credential and request contents to network interception. This destination override is not described in the reviewed README or skill documentation. The same requests may contain sensitive athlete information, including coaching questions, injury descriptions, training schedules, adaptation reasons, profile information, and performance metrics. ### Attack Path 1. An attacker, malicious launcher, or unsafe MCP configuration sets `TRANSITION_API_URL` to an attacker-controlled HTTP or HTTPS server. 2. The user starts the MCP server with a valid `TRANSITION_API_KEY`. 3. The user or ...[truncated 1070 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `TRANSITION_API_URL` if custom API origins are not an explicit requirement. 2. If custom endpoints are required, parse the value with `net/url` and require: - An `https` scheme. - No embedded user information. - No unexpected port. - A hostname from an explicit allowlist. 3. Attach `X-API-Key` only after verifying that the final request destination exactly matches the trusted Transition API origin. 4. Use separate constructors for production and development clients so development endpoints cannot accidentally receive production credentials. 5. Reject plain HTTP destinations whenever an API key is present. 6. Document all supported configuration variables and their security implications. 7. Consider scoping and rotating API keys, and advise users to revoke keys that may have been exposed. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
mcp/client.go:21
Finding
Custom Authentication Header May Be Forwarded Across Redirects<![CDATA[ ## Vulnerability Details **File Location**: `mcp/client.go:21-45` **Vulnerability Type**: Cross-origin credential disclosure through unsafe redirect handling **Risk Level**: High ### Vulnerable Code ```go // mcp/client.go:21-26 return &TransitionClient{ baseURL: strings.TrimRight(baseURL, "/"), apiKey: apiKey, httpClient: &http.Client{ Timeout: 60 * time.Second, }, } ``` ```go // mcp/client.go:30-45 func (tc *TransitionClient) doRequest(method, path string, body io.Reader) (*http.Response, error) { url := tc.baseURL + path req, err := http.NewRequest(method, url, body) if err != nil { return nil, err } if tc.apiKey != "" { req.Header.Set("X-API-Key", tc.apiKey) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") return tc.httpClient.Do(req) } ``` ### Technical Analysis The HTTP client uses Go's default redirect behavior because no `CheckRedirect` function is configured. Authentication is carried in a custom `X-API-Key` header. Applications should not rely on default redirect behavior to protect custom authentication headers. A redirect to another origin may cause request metadata, including custom headers, to be replayed to a destination outside the original trust boundary. The code also performs no final scheme or host verification before following redirects. Even when the initial base URL is trusted, a compromised or misconfigured endpoint could return a redirect to an attacker-controlled host. ### Attack Path 1. The MCP server sends an authenticated request to the configured API endpoint. 2. The endpoint responds with an HTTP redirect to another origin. 3. The default `http.Client` follows the redirect because no restrictive `CheckRedirect` policy is installed. 4. The redirected request may retain the custom `X-API-Key` header. 5. The redirected destination records and reuses the credential. This path requires cont ...[truncated 546 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Configure an explicit redirect policy: ```go httpClient := &http.Client{ Timeout: 60 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { if len(via) == 0 { return nil } original := via[0].URL if req.URL.Scheme != "https" || req.URL.Scheme != original.Scheme || !strings.EqualFold(req.URL.Host, original.Host) { return http.ErrUseLastResponse } return nil }, } ``` Additional hardening should include: 1. Disable redirects entirely if the API does not require them. 2. Compare normalized scheme, hostname, and effective port before permitting a redirect. 3. Explicitly remove `X-API-Key` before any permitted redirect unless the destination is the exact trusted origin. 4. Limit the number of same-origin redirects. 5. Add tests covering same-origin, cross-origin, HTTPS-to-HTTP, and subdomain redirects. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
mcp/client.go:47
Finding
Unbounded API and SSE Response Buffering Enables Memory Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `mcp/client.go:47-137` **Vulnerability Type**: Uncontrolled memory consumption from remote responses **Risk Level**: Medium ### Vulnerable Code ```go // mcp/client.go:47-64 func (tc *TransitionClient) Get(path string) (string, error) { resp, err := tc.doRequest("GET", path, nil) if err != nil { return "", fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode >= 400 { return "", fmt.Errorf("API error %d: %s", resp.StatusCode, string(data)) } return string(data), nil } ``` ```go // mcp/client.go:68-93 func (tc *TransitionClient) Post(path string, body interface{}) (string, error) { var reader io.Reader if body != nil { jsonData, err := json.Marshal(body) if err != nil { return "", fmt.Errorf("failed to marshal body: %w", err) } reader = strings.NewReader(string(jsonData)) } resp, err := tc.doRequest("POST", path, reader) if err != nil { return "", fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode >= 400 { return "", fmt.Errorf("API error %d: %s", resp.StatusCode, string(data)) } return string(data), nil } ``` ```go // mcp/client.go:109-137 if resp.StatusCode >= 400 { data, _ := io.ReadAll(resp.Body) return "", fmt.Errorf("API error %d: %s", resp.StatusCode, string(data)) } // Check if this is actually an SSE response contentType := resp.Header.Get("Content-Type") if !strings.Contains(contentType, "text/event-stream") { // Not SSE, just read the whole body data, err := io.ReadAll(resp.Body) if err != nil { retur ...[truncated 1950 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define separate maximum response sizes for normal JSON, API errors, and SSE output. 2. Wrap response bodies with `io.LimitReader` or a custom reader that reports an explicit size-limit error. 3. Read no more than the configured limit plus one byte so oversized responses can be reliably detected. 4. Track cumulative SSE output and stop processing once the maximum is reached. 5. Configure an explicit scanner buffer size appropriate for expected SSE events. 6. Avoid embedding an entire remote error response in returned errors; truncate and sanitize it. 7. Add transport-level controls such as `ResponseHeaderTimeout`, `TLSHandshakeTimeout`, and idle connection timeouts. 8. Add tests using oversized fixed-length, chunked, error, non-SSE, and SSE responses. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
mcp/tools.go:117
Finding
Workout Generator Constructs Query Strings Without Input Validation or Encoding<![CDATA[ ## Vulnerability Details **File Location**: `mcp/tools.go:117-132` **Vulnerability Type**: Improper input validation and unsafe query-string construction **Risk Level**: Low ### Vulnerable Code ```go // mcp/tools.go:117-132 args := parseArgs(req.Params.Arguments) sport := "run" duration := 45 if s, ok := args["sport"].(string); ok { sport = s } if d, ok := args["duration"].(float64); ok { duration = int(d) } data, err := client.Get(fmt.Sprintf("/api/v1/wod?sport=%s&duration=%d", sport, duration)) if err != nil { return errorResult(fmt.Sprintf("Failed to generate workout: %v", err)), nil } return textResult(data), nil ``` ### Technical Analysis The `sport` argument is inserted directly into a URL query string without URL encoding or validation against the documented values of `run`, `bike`, `swim`, and `strength`. The `duration` argument is converted to an integer but is not constrained to the documented range of 10–300 minutes. A crafted `sport` value containing query delimiters such as `&`, `=`, or `#` can alter how the server interprets the query. Although this construction does not allow direct control of the request hostname because the value is appended after a fixed path and query prefix, it permits parameter manipulation and creates ambiguous or malformed requests. ### Attack Path 1. A caller invokes `generate_workout` with a crafted `sport` argument containing query-control characters. 2. The server concatenates the value directly into the request path. 3. The HTTP parser interprets the constructed text as query syntax rather than as one encoded parameter value. 4. The remote API receives modified, additional, truncated, or malformed query parameters. 5. The caller may bypass intended client-side parameter restrictions or cause unexpected API behavior. ### Impact Assessment The demonstrated scope is limited to manipulation of Workout of the Day query semantics and malformed requests. The issue does not by itself change ...[truncated 215 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `sport` against an explicit allowlist: ```go allowedSports := map[string]bool{ "run": true, "bike": true, "swim": true, "strength": true, } if !allowedSports[sport] { return errorResult("sport must be run, bike, swim, or strength"), nil } ``` 2. Reject durations outside the documented 10–300 minute range. 3. Reject non-integral, non-finite, or excessively large numeric values before converting them to `int`. 4. Construct the query with `net/url` rather than string formatting: ```go values := url.Values{} values.Set("sport", sport) values.Set("duration", strconv.Itoa(duration)) path := "/api/v1/wod?" + values.Encode() ``` 5. Define an MCP input schema so invalid arguments can be rejected before the handler runs. 6. Return explicit validation errors instead of relying on the remote API to enforce client-side constraints. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (28)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description promises an AI coaching capability focused on personalized workouts, training plans, and performance analytics for endurance sports. The supplied code does not implement any of that domain logic. Instead, it is a reusable network client that sends HTTP requests to a configurable base URL, optionally authenticates with an X-API-Key header, and reads standard JSON or SSE responses. This is a materially different primary purpose from multisport coaching. While API access could be a supporting detail if the surrounding code clearly implemented coaching features, this chunk itself only exposes generic transport functionality and accesses an external service not described in the skill purpose. Therefore, the description does not accurately represent the actual behavior shown.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared purpose is a multisport coaching assistant focused on workouts, training plans, and performance analytics. The supplied code instead sets up an MCP server connected to a 'Transition' API using environment-provided credentials and URL. There is no visible logic related to running, cycling, swimming, triathlon, coaching, analytics, or athlete personalization. While this is only a bootstrap file, the observable primary purpose is infrastructure for a Transition service, which does not accurately represent the declared coaching functionality.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
Try it right now — no API key needed:

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

For personalized features (your training plan, performance data, AI coach), you need a Transition account + API key. See [Getting an API Key](#getting-an-api-key) below.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The README encourages users to access personalized workouts, performance data, and AI coaching through a remote service but does not clearly warn that sensitive health, training, and performance information will be transmitted to a third-party API. In a coaching skill context, this omission matters because users may share injury details, schedules, and athlete metrics without understanding the privacy implications.

Missing User Warnings

Medium
Confidence
81% confidence
Finding
The README advertises plan adaptation and AI coaching features without warning that these actions may modify recommendations derived from user data or trigger remote processing of athlete history and health-related context. This can mislead users into invoking impactful features without informed consent about data use or the consequences of automated coaching outputs.

External Transmission

Medium
Category
Data Exfiltration
Content
### 3. Direct API

Use the API from scripts, automations, or any HTTP client. See [SKILL.md](SKILL.md) for complete endpoint documentation with curl examples.

```bash
# Get this week's workouts
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
curl -X POST -H "X-API-Key: $TRANSITION_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "My left knee hurts after long runs. Should I adjust my plan?"}' \
  "https://api.transition.fun/api/v1/coach/chat"
```

---
Confidence
74% confidence
Finding
The coach chat example encourages sending a health-related prompt ('My left knee hurts...') to a remote AI endpoint without warning that medical-adjacent or injury information will be transmitted off-device. In this skill context, that increases privacy and safety risk because users may disclose sensitive health information and over-rely on non-medical AI advice.

Lp3

Medium
Category
MCP Least Privilege
Confidence
74% confidence
Finding
The skill documents use of environment-based secrets and shell/curl interactions with an external API but do not declare any explicit tool scope or permissions boundary. This increases the chance that an agent runtime grants broader capabilities than users expect, especially where API keys and outbound requests are involved.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill transmits sensitive training history, athlete profile information, and coach-chat content to an external service, but it does not prominently warn users about data sharing, retention, or privacy implications. In a coaching context, this data can reveal health, schedule, and behavioral information that users may not expect to leave the agent environment.

External Transmission

Medium
Category
Data Exfiltration
Content
Generate a random structured workout. Each request returns a different workout.

```bash
curl "https://api.transition.fun/api/v1/wod?sport=run&duration=45"
```

**Parameters:**
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The Garmin push operation can modify or synchronize data with a third-party account, but the skill does not warn that this is an external side-effecting action. Users may unintentionally trigger changes in connected services if the agent performs this action without explicit confirmation.

External Transmission

Medium
Category
Data Exfiltration
Content
echo ""
echo "=== AI Coach Chat ==="
curl -s -X POST -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "What should I focus on this week?"}' \
  "$BASE/api/v1/coach/chat"
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.