{"record":{"id":"f4e5ce3ed5cbb414","repo":"siyuan-note/siyuan","slug":"oidc-issuer-url-must-use-https-unless-it-is-a-loop","errorCode":null,"errorMessage":"OIDC issuer URL must use HTTPS unless it is a loopback address","messagePattern":"OIDC issuer URL must use HTTPS unless it is a loopback address","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/oidc.go","lineNumber":472,"sourceCode":"\nfunc ValidateOIDCConfiguration(config *conf.OIDC) error {\n\tif config == nil || !config.Enabled {\n\t\treturn errors.New(\"OIDC login is not enabled\")\n\t}\n\tif config.ClientID == \"\" {\n\t\treturn errors.New(\"OIDC client ID is required\")\n\t}\n\tif config.Provider == conf.OIDCProviderGitHub && config.ClientSecret == \"\" {\n\t\treturn errors.New(\"GitHub OAuth client secret is required\")\n\t}\n\tif (config.Provider == conf.OIDCProviderCustom || config.Provider == conf.OIDCProviderMicrosoft) && config.IssuerURL == \"\" {\n\t\treturn errors.New(\"OIDC issuer URL is required\")\n\t}\n\tif (config.Provider == conf.OIDCProviderCustom || config.Provider == conf.OIDCProviderMicrosoft) && config.IssuerURL != \"\" {\n\t\tissuer, err := url.Parse(config.IssuerURL)\n\t\tif err != nil || issuer.Host == \"\" || issuer.User != nil || issuer.RawQuery != \"\" || issuer.Fragment != \"\" ||\n\t\t\t(issuer.Scheme != \"https\" && !util.IsLocalHostname(issuer.Hostname())) {\n\t\t\treturn errors.New(\"OIDC issuer URL must use HTTPS unless it is a loopback address\")\n\t\t}\n\t}\n\tif config.Provider != conf.OIDCProviderCustom && config.Provider != conf.OIDCProviderGoogle &&\n\t\tconfig.Provider != conf.OIDCProviderMicrosoft && config.Provider != conf.OIDCProviderGitHub {\n\t\treturn errors.New(\"Unsupported OIDC provider\")\n\t}\n\tif !config.AllowAll && len(config.ClaimRules) == 0 {\n\t\treturn errors.New(\"OIDC login requires at least one claim rule when Allow all users is disabled\")\n\t}\n\tfor _, rule := range config.ClaimRules {\n\t\tif rule == nil || rule.Claim == \"\" || len(rule.Values) == 0 {\n\t\t\treturn errors.New(\"OIDC claim rules must include a claim and at least one value\")\n\t\t}\n\t\tif rule.Operator != conf.OIDCClaimOperatorEquals && rule.Operator != conf.OIDCClaimOperatorContains {\n\t\t\treturn errors.New(\"Unsupported OIDC claim rule operator\")\n\t\t}\n\t\tfor _, value := range rule.Values {\n\t\t\tif value == \"\" {","sourceCodeStart":454,"sourceCodeEnd":490,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/oidc.go#L454-L490","documentation":"When a Custom or Microsoft provider has an IssuerURL, it is strictly parsed and checked: it must parse, have a host, no userinfo, no query string, no fragment, and use https — unless the host is a local/loopback address allowed by util.IsLocalHostname. Any violation returns \"OIDC issuer URL must use HTTPS unless it is a loopback address\" to prevent leaking tokens over insecure or ambiguous URLs.","triggerScenarios":"Calling ValidateOIDCConfiguration with an IssuerURL that uses plain http against a public host, contains a query (?tenant=...), a fragment (#...), userinfo (user@host), is missing a host, or is not parseable by url.Parse.","commonSituations":"Testing against a self-hosted IdP over http:// on a LAN address that is not in the loopback allowlist; appending query parameters to the issuer; pasting the full discovery URL (including /.well-known/openid-configuration?x=1) as the issuer; typos leaving the scheme off so parsing produces no host.","solutions":["Serve the identity provider over HTTPS and set IssuerURL with the https scheme.","For local development, run the IdP on a loopback host (localhost/127.0.0.1) so the IsLocalHostname exemption applies.","Strip any query strings, fragments, and userinfo from the issuer URL; keep only scheme://host[:port]/path.","Verify with url.Parse locally that the URL yields a non-empty Host before configuring it."],"exampleFix":"// before\nIssuerURL: \"http://idp.internal.example.com?realm=main\"\n// after\nIssuerURL: \"https://idp.internal.example.com\"","handlingStrategy":"validation","validationCode":"// Go: mirror the kernel's issuer checks locally before configuring\nu, err := url.Parse(cfg.IssuerURL)\nif err != nil || u.Host == \"\" || u.User != nil || u.RawQuery != \"\" || u.Fragment != \"\" ||\n\t(u.Scheme != \"https\" && !util.IsLocalHostname(u.Hostname())) {\n\treturn errors.New(\"issuer must be a plain https URL (loopback http allowed)\")\n}","typeGuard":null,"tryCatchPattern":"// JavaScript caller\ntry {\n  await saveOIDCSettings(cfg);\n} catch (e) {\n  if (e.msg.includes(\"must use HTTPS\")) {\n    showHint(\"Use https:// (or a localhost issuer for development) with no query/fragment\");\n  } else { throw e; }\n}","preventionTips":["Always configure the bare https issuer without query or fragment","Do not paste the full .well-known discovery URL as the issuer","Put local dev IdPs on localhost so the loopback exemption applies","Sanitize URLs (strip ?/#, userinfo) before saving them into config"],"tags":["oidc","security","url-validation"],"backgroundTag":"invalid-url-format","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}