{"record":{"id":"46df79044516f558","repo":"thanos-io/thanos","slug":"tenant-name-not-valid","errorCode":null,"errorMessage":"tenant name not valid","messagePattern":"tenant name not valid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/tenancy/tenancy.go","lineNumber":43,"sourceCode":"\tDefaultTenant = \"default-tenant\"\n\t// DefaultTenantLabel is the default label-name with which the tenant is announced in stored metrics.\n\tDefaultTenantLabel = \"tenant_id\"\n\t// This key is used to pass tenant information using Context.\n\tTenantKey contextKey = 0\n\t// MetricLabel is the label name used for adding tenant information to exported metrics.\n\tMetricLabel = \"tenant\"\n)\n\n// Allowed fields in client certificates.\nconst (\n\tCertificateFieldOrganization       = \"organization\"\n\tCertificateFieldOrganizationalUnit = \"organizationalUnit\"\n\tCertificateFieldCommonName         = \"commonName\"\n)\n\nfunc IsTenantValid(tenant string) error {\n\tif tenant != path.Base(tenant) {\n\t\treturn errors.New(\"tenant name not valid\")\n\t}\n\treturn nil\n}\n\n// GetTenantFromHTTP extracts the tenant from a http.Request object.\nfunc GetTenantFromHTTP(r *http.Request, tenantHeader string, defaultTenantID string, certTenantField string) (string, error) {\n\tvar err error\n\ttenant := r.Header.Get(tenantHeader)\n\tif tenant == \"\" {\n\t\ttenant = r.Header.Get(DefaultTenantHeader)\n\t\tif tenant == \"\" {\n\t\t\ttenant = defaultTenantID\n\t\t}\n\t}\n\n\tif certTenantField != \"\" {\n\t\ttenant, err = getTenantFromCertificate(r, certTenantField)\n\t\tif err != nil {","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/tenancy/tenancy.go#L25-L61","documentation":"IsTenantValid validates a multi-tenancy tenant ID by ensuring it is a single path element (tenant == path.Base(tenant)). Tenant IDs are used in object storage paths, so values containing '/' or being '.' would break storage layout or escape directories.","triggerScenarios":"IsTenantValid(tenant) returns this error when tenant != path.Base(tenant) — e.g. tenant contains '/', is empty (path.Base(\"\") == \".\"), or equals \".\".","commonSituations":"Missing tenant header causing empty string; a downstream proxy appending path-like IDs (\"org/team\") as tenants; URL parsing pulling tenant from a path segment including slashes; clients configured with wrong tenant header name.","solutions":["Send the correct tenant header (e.g. THanos-Scope: <id>) with a simple identifier containing no '/' and not empty.","Validate the tenant at configuration/startup: call tenancy.IsTenantValid(tenant) and fail fast with a clear message.","If you need hierarchical tenants, encode the hierarchy without '/' (e.g. use '-' separator).","Check reverse proxy/gateway config so it forwards the tenant header verbatim and doesn't inject request paths."],"exampleFix":"// before\ntenant := r.URL.Path // \"org/team\"\n// after\ntenant := r.Header.Get(\"THanos-Scope\") // \"org-team\"\nif err := tenancy.IsTenantValid(tenant); err != nil { http.Error(w, err.Error(), http.StatusBadRequest); return }","handlingStrategy":"validation","validationCode":"func tenantOK(t string) bool { return t != \"\" && t == path.Base(t) }","typeGuard":null,"tryCatchPattern":"if err := tenancy.IsTenantValid(tenant); err != nil {\n    http.Error(w, \"invalid tenant\", http.StatusBadRequest)\n    return\n}","preventionTips":["Always require a tenant header with a simple identifier","Fail fast at startup for configured tenants","Encode hierarchy with '-' instead of '/'","Test gateways forward the tenant header unchanged"],"tags":["tenancy","validation","multi-tenancy","path"],"backgroundTag":"invalid-identifier-format","analyzedSha":"35b8b991177def87ed52dcf10f9b6d87f07282c8","analyzedAt":"2026-09-07T01:49:59.689Z","contentChangedAt":"2026-09-07T01:49:59.689Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}