thanos-io/thanos · warning

invalid tenant ID

Error message

invalid tenant ID

What it means

errInvalidTenantID is returned by tenant resolvers (TenantID, TestSingleResolver) when the extracted tenant ID is unsafe — it contains path separators or unsafe path segments (e.g. "..", "/", "\\"). It guards multi-tenancy against path traversal via the X-Scope-OrgID header.

Solutions

  1. Use a tenant ID restricted to safe characters (letters, digits, dash, underscore)
  2. Sanitize or reject such header values at the ingress/proxy before they reach Cortex
  3. If legitimate tenants need hierarchical names, choose a different separator convention

Example fix

// before
curl -H 'X-Scope-OrgID: ../admin' ...
// after
curl -H 'X-Scope-OrgID: tenant-admin' ...
Defensive patterns

Strategy: validation

Validate before calling

var tenantIDRe = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
func validTenantID(id string) bool { return tenantIDRe.MatchString(id) }

Type guard

func safeTenant(id string) bool {
	return !strings.ContainsAny(id, "\\/") && !strings.Contains(id, "..")
}

Try / catch

tid, err := resolver.TenantID(ctx)
if errors.Is(err, tenant.ErrInvalidTenantID) {
	http.Error(w, "invalid tenant ID", http.StatusBadRequest)
	return
}

Prevention

When it happens

Trigger: Sending X-Scope-OrgID (or the configured tenant header) with values containing "/", "\\", or path segments like ".." so containsUnsafePathSegments returns true in resolver.go:84.

Common situations: Proxies/clients injecting odd tenant headers; malicious probing for path traversal in store paths; misconfigured load balancers that mangle header values; test suites expecting IDs with slashes.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/47d44045212c80c4. Report an issue: GitHub.

Appendix: source

Thrown at internal/cortex/tenant/resolver.go:74

	return &SingleResolver{}
}

type SingleResolver struct {
}

// containsUnsafePathSegments will return true if the string is a directory
// reference like `.` and `..` or if any path separator character like `/` and
// `\` can be found.
func containsUnsafePathSegments(id string) bool {
	// handle the relative reference to current and parent path.
	if id == "." || id == ".." {
		return true
	}

	return strings.ContainsAny(id, "\\/")
}

var errInvalidTenantID = errors.New("invalid tenant ID")

func (t *SingleResolver) TenantID(ctx context.Context) (string, error) {
	//lint:ignore faillint wrapper around upstream method
	id, err := user.ExtractOrgID(ctx)
	if err != nil {
		return "", err
	}

	if containsUnsafePathSegments(id) {
		return "", errInvalidTenantID
	}

	return id, nil
}

func (t *SingleResolver) TenantIDs(ctx context.Context) ([]string, error) {
	orgID, err := t.TenantID(ctx)
	if err != nil {

View on GitHub (pinned to 35b8b99117)