fatedier/frp · error

missing %s

Error message

missing %s

What it means

Produced by decodeV2PathParam when the requested path parameter (e.g. "key" for /api/v2/clients/{key}) is an empty string. The route matched but the variable segment is missing, so no URL-decoding is attempted. Because it is created with fmt.Errorf rather than httppkg.NewError, it surfaces as an internal-style error instead of a clean 400.

Source

Thrown at server/http/controller_v2.go:300

// /api/v2/proxies/{name}/traffic
func (c *Controller) APIV2ProxyTraffic(ctx *httppkg.Context) (any, error) {
	name, err := decodeV2PathParam(ctx, "name", "proxy name")
	if err != nil {
		return nil, err
	}

	proxyTrafficInfo := mem.StatsCollector.GetProxyTraffic(name)
	if proxyTrafficInfo == nil {
		return nil, httppkg.NewError(http.StatusNotFound, "no proxy info found")
	}

	return buildV2ProxyTrafficResp(name, proxyTrafficInfo, time.Now()), nil
}

func decodeV2PathParam(ctx *httppkg.Context, key string, label string) (string, error) {
	raw := ctx.Param(key)
	if raw == "" {
		return "", fmt.Errorf("missing %s", label)
	}
	decoded, err := url.PathUnescape(raw)
	if err != nil {
		return "", httppkg.NewError(http.StatusBadRequest, fmt.Sprintf("invalid %s", label))
	}
	return decoded, nil
}

func getOrCreateV2User(items map[string]*model.V2UserResp, user string) *model.V2UserResp {
	item, ok := items[user]
	if !ok {
		item = &model.V2UserResp{User: user}
		items[user] = item
	}
	return item
}

func parseV2PageParams(ctx *httppkg.Context) (int, int, error) {

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Include the client key in the path: GET /api/v2/clients/{actual-key}
  2. Check reverse-proxy rewrite rules if the caller sends the key but the server sees it empty
  3. In scripts, assert the key variable is non-empty before issuing the request

Example fix

# before
curl http://frps:7500/api/v2/clients/

# after
curl http://frps:7500/api/v2/clients/abc123
Defensive patterns

Strategy: validation

Validate before calling

key := os.Getenv("CLIENT_KEY")
if strings.TrimSpace(key) == "" {
    log.Fatal("CLIENT_KEY must not be empty")
}
url := fmt.Sprintf("/api/v2/clients/%s", url.PathEscape(key))

Prevention

When it happens

Trigger: Requesting /api/v2/clients/ (trailing slash, empty key) or /api/v2/clients//status; a route rewriter or reverse proxy stripping the path segment; calling the router with a mismatched route pattern that leaves the param unset.

Common situations: Manually building dashboard/API URLs and forgetting the key; gateway rewrite rules (nginx traefik) that trim the last path segment; scripts concatenating an empty variable into the URL.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/ea230da3418d2575. Report an issue: GitHub.