Skip to content

Cedar JSON parsing is case-insensitive, diverging from the Rust implementation #151

Description

@patjakdev

Summary

Cedar's JSON formats (entity escapes, policy AST, schema, entity data, request) are case-sensitive in the Rust reference implementation, but Go's encoding/json performs case-insensitive matching of JSON keys to struct field tags by default. As a result, cedar-go accepts JSON inputs with miscased keys that the Rust implementation rejects, producing cross-implementation divergence and, in some cases, ambiguous parses.

Scope

The root cause exists wherever a json:"..." struct tag is decoded. Highest-impact sites:

  1. Entity / extension escapestypes/json.go:25-38

    • __entity, __extn, fn, arg, type, id
    • Symptom: cedar-go emits EntityUIDs or extension functions where the Rust implementation emits Records.
  2. Cedar JSON policy ASTinternal/json/json.go

    • Value markers (capitalized by spec): Value (88), Var (91), Set (134), Record (137). The Cedar spec uses capitalization here as a discriminator against lowercase operators at the same level. {"value": ...}, {"set": [...]}, {"record": {...}} should be rejected.
    • camelCase operators: isEmpty, containsAll, containsAny, getTag, hasTag, if-then-else, plus neg, has, is, like, in, contains.
    • Policy structure keys: effect, principal, action, resource, conditions, annotations, op, entity, entities, entity_type, kind, body, left, right, arg, attr, pattern, if, then, else.
    • Note: nodeJSON.UnmarshalJSON (internal/json/json_unmarshal.go:274) already calls DisallowUnknownFields(). This is not sufficientDisallowUnknownFields rejects unknown keys but still performs case-insensitive matching for known fields. Miscasings of recognized tags pass through silently.
  3. Schema JSONx/exp/schema/internal/json/json.go

    • entityTypes, commonTypes, memberOfTypes, memberOf, appliesTo, principalTypes, resourceTypes, shape, tags, attributes, element, enum, required, annotations.
  4. Entity JSONtypes/entity.go:11-14, 33-36

    • uid, parents, attrs, tags.
  5. Request / Authorize JSONtypes/authorize.go:8-11, 47-84

    • principal, action, resource, context, reasons, errors, policy, position, message, filename, offset, line, column.

Impact

This issue can potentially cause cedar-go and the Rust implementation to produce different authorization decisions.

However, the likelihood of exploitation by a malicious requester is low. The application would have to accept a JSON payload directly from a malicious requester as part of the authorization context, which seems highly unlikely.

Possible fixes

Enforce case sensitivity with a common helper function

Introduce a single helper that enforces exact-case key matching, then route every Cedar-spec JSON decode through it:

// strictUnmarshal decodes b into v but rejects any JSON key whose case does
// not exactly match a `json:"..."` tag on v (or, for nested structs,
// recursively on their tags).
func strictUnmarshal(b []byte, v any) error { ... }

Implementation sketch: parse b into map[string]json.RawMessage, reflect over v's tag set to build the set of valid exact-case keys at this level, reject any input key not present in that set, then re-emit only the validated keys and json.Unmarshal into v. Recurse for fields that are themselves structs. The cost is one extra pass per decode; for Cedar JSON payloads this is negligible.

Migrate to json/v2

Unfortunately, Go's json v2 package, which enforces case sensitivity (amongst other niceties) still hasn't stabilized. However, we could wait to fix this bug until such time as it has.

Backwards compatibility

This is a tightening change: inputs that previously parsed or passed schema validation will now error in conformance with the Rust implementation. There is no desire to preserve backwards compatibility.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions