{"record":{"id":"d53546259467459c","repo":"cloudflare/cloudflared","slug":"auth-domain-q-does-not-end-with-q","errorCode":null,"errorMessage":"auth_domain %q does not end with %q","messagePattern":"auth_domain %q does not end with %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"token/jwks.go","lineNumber":88,"sourceCode":"\t}\n\n\tvar claims metadataClaims\n\tif err := json.Unmarshal(payload, &claims); err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to decode verified metadata JWT claims\")\n\t}\n\treturn &claims, nil\n}\n\n// parseAuthDomain extracts the canonical hostname used for JWKS requests and\n// cache paths from the auth_domain claim.\nfunc parseAuthDomain(authDomain string) (url.URL, error) {\n\tparsed, err := url.Parse(httpsScheme + \"://\" + authDomain)\n\tif err != nil {\n\t\treturn url.URL{}, fmt.Errorf(\"failed to parse auth_domain %q: %w\", authDomain, err)\n\t}\n\thostname := strings.ToLower(parsed.Hostname())\n\tif !strings.HasSuffix(hostname, accessDomainSuffix) {\n\t\treturn url.URL{}, fmt.Errorf(\"auth_domain %q does not end with %q\", authDomain, accessDomainSuffix)\n\t}\n\treturn url.URL{Scheme: httpsScheme, Host: hostname}, nil\n}\n\n// fetchJWKS fetches the JWKS from the auth domain's certs endpoint over HTTPS.\nfunc fetchJWKS(authDomain url.URL) (*jose.JSONWebKeySet, error) {\n\tjwksURL := authDomain\n\tjwksURL.Path = accessCertPath\n\n\tclient := &http.Client{\n\t\tCheckRedirect: func(_ *http.Request, _ []*http.Request) error {\n\t\t\treturn http.ErrUseLastResponse\n\t\t},\n\t\tTimeout: time.Second * 10,\n\t}\n\tresp, err := client.Get(jwksURL.String()) // nolint: gosec\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to fetch JWKS from %s\", jwksURL.String())","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/token/jwks.go#L70-L106","documentation":"After parsing, parseAuthDomain lowercases the hostname and enforces that the auth_domain ends with the Cloudflare Access suffix \".cloudflareaccess.com\". This is a security check: JWKS requests and cache paths must only ever target Cloudflare Access domains, preventing a tampered metadata JWT from redirecting token verification to an attacker-controlled host. A non-matching auth_domain is rejected with \"auth_domain %q does not end with %q\".","triggerScenarios":"GetAppInfo / testAuthDomain / metadata processing receive an auth_domain claim whose hostname does not end in \".cloudflareaccess.com\" — e.g. \"myteam.example.com\", \"myteam.cloudflareaccess.com.evil.io\", an empty string, or a claim missing entirely so the value is blank.","commonSituations":"A self-hosted or third-party Access-like setup whose domain is not on cloudflareaccess.com is plugged into cloudflared; the metadata JWT was tampered with or generated by a mock/stub in a custom environment; the team domain in the dashboard was customized and doesn't use the standard Access suffix; a typo in a hand-edited tunnel token.","solutions":["Use the standard Cloudflare Access team domain ending in .cloudflareaccess.com for auth_domain (find it in the Zero Trust dashboard under Access).","Re-issue/re-download the tunnel token from the Cloudflare dashboard — a stale or hand-edited token may carry a wrong auth_domain.","If the JWT came from an untrusted source, treat this as a possible tampering attempt and fetch metadata only from Cloudflare edge.","If you legitimately need a custom auth domain, this library does not support it; do not bypass the suffix check — keep the standard domain for JWKS verification."],"exampleFix":"// before\n\"auth_domain\": \"myteam.example.com\"\n// after\n\"auth_domain\": \"myteam.cloudflareaccess.com\"","handlingStrategy":"validation","validationCode":"func isCloudflareAccessDomain(authDomain string) bool {\n\tu, err := url.Parse(\"https://\" + authDomain)\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn strings.HasSuffix(strings.ToLower(u.Hostname()), \".cloudflareaccess.com\")\n}","typeGuard":"func isAccessAuthDomain(u url.URL) bool {\n\treturn u.Scheme == \"https\" && strings.HasSuffix(u.Host, \".cloudflareaccess.com\")\n}","tryCatchPattern":"ad, err := parseAuthDomain(claims.AuthDomain)\nif err != nil {\n\tif strings.Contains(err.Error(), \"does not end with\") {\n\t\tlog.Error().Str(\"authDomain\", claims.AuthDomain).\n\t\t\tMsg(\"auth_domain is not a Cloudflare Access domain; possible tampering or misconfiguration — refusing JWKS fetch\")\n\t}\n\treturn err\n}","preventionTips":["Only trust metadata JWTs obtained from Cloudflare edge with signature verification.","Configure Access applications with the standard .cloudflareaccess.com team domain.","Treat a suffix-check failure on signed metadata as a security incident, not just a config bug.","Never bypass or weaken the domain suffix allowlist."],"tags":["cloudflare-access","security","domain-validation","jwt"],"backgroundTag":"invalid-argument-value","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}