ory/kratos · error
invalid $ref URL
Error message
invalid $ref URL %q: %w
What it means
loadRefURL validates each $ref URL in an identity schema before fetching it. If the raw URL cannot be parsed by net/url.Parse, the error is wrapped as "invalid $ref URL %q: %w". This guard exists so malformed references fail fast at the schema-loader level instead of failing deep inside jsonschema.LoadURL.
Solutions
- Inspect the exact %q value in the error message and fix the $ref string in the identity schema so it is a valid, parseable URL.
- Percent-encode any literal '%' characters and remove control characters/whitespace from the $ref value.
- Verify the ref uses the expected form: a valid URL with the allowed 'base64' scheme for identity schemas (e.g. base64://<encoded schema>).
- Validate the schema JSON offline (url.Parse the refs yourself) before submitting it to the schema compiler.
Example fix
// before "$ref": "base64://ewogIC8vIGJyb2tlbg%zz" // after "$ref": "base64://ewogIC8vIHNjaGVtYQ%3D%3D" (properly percent-encoded) or an otherwise valid URL
Defensive patterns
Strategy: validation
Validate before calling
for _, ref := range collectRefs(schema) {
if _, err := url.Parse(ref); err != nil {
return fmt.Errorf("schema contains invalid $ref %q: %w", ref, err)
}
} Try / catch
var urlErr *url.Error
if errors.As(err, &urlErr) {
// handle malformed $ref URL
} Prevention
- Validate schema $refs with url.Parse in CI before deployment
- Never build $ref strings by raw string interpolation; use url building helpers
- Keep $ref values as simple, well-formed URLs
When it happens
Trigger: An identity schema JSON contains a $ref whose value is not a parseable URL — e.g. a missing scheme like "$ref": "some/relative/path" is actually parseable, but broken percent-escapes ("%zz"), control characters, or a malformed IPv6 host in a URL will make url.Parse fail. Triggered whenever the schema compiler resolves refs via loadRefURL (identity schema compilation with $ref loader registered).
Common situations: Hand-edited identity schemas where a $ref was written as an invalid URL string (unescaped characters, stray spaces are tolerated by url.Parse but % sequences are not); templating tools that interpolated values into $ref leaving stray characters; copying refs between systems where quotes got mangled.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- identity schema rejected: self-referential $ref cycle
- $ref scheme is not permitted in identity schemas
- identity schema rejected: invalid regex in pattern
- identity schema rejected: invalid regex in…
- could not create request
AI-assisted analysis of ory/kratos@b86338da04 (2026-09-07).
Data as JSON: /api/errors/90c77dd8fd6cdf3d.
Report an issue: GitHub.
Appendix: source
Thrown at schema/loader.go:32
_ "github.com/ory/jsonschema/v3/base64loader"
_ "github.com/ory/jsonschema/v3/fileloader"
"github.com/ory/jsonschema/v3/httploader"
"github.com/pkg/errors"
"github.com/ory/jsonschema/v3"
"github.com/ory/x/httpx"
)
// loadRefURL resolves the URL of a `$ref` inside a schema. It enforces the
// scheme allowlist and then delegates to the jsonschema package's global
// loader table. The global `file` loader remains registered so that
// operator-configured top-level schema URLs (resolved outside the compiler)
// keep working.
func loadRefURL(ctx context.Context, raw string) (io.ReadCloser, error) {
u, err := url.Parse(raw)
if err != nil {
return nil, fmt.Errorf("invalid $ref URL %q: %w", raw, err)
}
if u.Scheme != "base64" {
return nil, fmt.Errorf("$ref scheme %q is not permitted in identity schemas", u.Scheme)
}
return jsonschema.LoadURL(ctx, raw)
}
// NewCompiler returns a jsonschema.Compiler. When disallowRefs is true, the
// compiler rejects `file://` URLs (and any other non-allowlisted scheme) in
// `$ref` values, preventing an attacker-supplied schema from reading local
// files on the Kratos host. When disallowRefs is false, the compiler uses
// the jsonschema library's default loader table, which preserves legacy
// behavior for operators who intentionally reference local files.
//
// The flag is controlled by `security.disallow_ref_in_identity_schemas`.
// Ory Network forces it on.
func NewCompiler(disallowRefs bool) *jsonschema.Compiler {
c := jsonschema.NewCompiler()View on GitHub (pinned to b86338da04)