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

  1. 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.
  2. Percent-encode any literal '%' characters and remove control characters/whitespace from the $ref value.
  3. Verify the ref uses the expected form: a valid URL with the allowed 'base64' scheme for identity schemas (e.g. base64://<encoded schema>).
  4. 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

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


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)