grpc/grpc-go · error

failed to unmarshal policy: %v

Error message

failed to unmarshal policy: %v

What it means

Raised by translatePolicy when the top-level policy JSON cannot be decoded. The decoder uses DisallowUnknownFields, so this fires both on malformed JSON AND on any field name not in the authorizationPolicy schema. The wrapped %v is the encoding/json error identifying the offending token/position.

Source

Thrown at authz/rbac_translator.go:367

	case v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW:
		return v3rbacpb.RBAC_AuditLoggingOptions_NONE
	case v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY_AND_ALLOW:
		return v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY
	default:
		return v3rbacpb.RBAC_AuditLoggingOptions_NONE
	}
}

// translatePolicy translates SDK authorization policy in JSON format to two
// Envoy RBAC polices (deny followed by allow policy) or only one Envoy RBAC
// allow policy. Also returns the overall policy name. If the input policy
// cannot be parsed or is invalid, an error will be returned.
func translatePolicy(policyStr string) ([]*v3rbacpb.RBAC, string, error) {
	policy := &authorizationPolicy{}
	d := json.NewDecoder(bytes.NewReader([]byte(policyStr)))
	d.DisallowUnknownFields()
	if err := d.Decode(policy); err != nil {
		return nil, "", fmt.Errorf("failed to unmarshal policy: %v", err)
	}
	if policy.Name == "" {
		return nil, "", fmt.Errorf(`"name" is not present`)
	}
	if len(policy.AllowRules) == 0 {
		return nil, "", fmt.Errorf(`"allow_rules" is not present`)
	}
	allowLogger, denyLogger, err := policy.AuditLoggingOptions.toProtos()
	if err != nil {
		return nil, "", err
	}
	rbacs := make([]*v3rbacpb.RBAC, 0, 2)
	if len(policy.DenyRules) > 0 {
		denyPolicies, err := parseRules(policy.DenyRules, policy.Name)
		if err != nil {
			return nil, "", fmt.Errorf(`"deny_rules" %v`, err)
		}
		denyRBAC := &v3rbacpb.RBAC{

View on GitHub (pinned to 03255a9237)

Solutions

  1. Run the policy string through `jq .` to confirm it is valid JSON.
  2. Compare top-level keys against the SDK's authorizationPolicy struct (Name, AllowRules, DenyRules, AuditLoggingOptions) — DisallowUnknownFields rejects anything else.
  3. Upgrade or align grpc-go and the authz SDK versions so the schema matches your policy.
  4. Wrap NewStaticInterceptors in a test that surfaces this error before runtime.

Example fix

// before (unknown field + trailing comma):
{ "name": "p", "allow_rule": [...] }   // 'allow_rule' unknown; trailing comma

// after:
{ "name": "p", "allow_rules": [ { "name": "a", "request": { "paths": ["/x"] } } ] }
Defensive patterns

Strategy: validation

Validate before calling

// Strict decode mirroring translatePolicy's DisallowUnknownFields + JSON
// validity, surfacing the exact offending token before the SDK rejects it.
func validatePolicyJSON(policyStr string) error {
    type authzPolicy struct {
        Name               string          `json:"name"`
        AllowRules         json.RawMessage `json:"allow_rules"`
        DenyRules          json.RawMessage `json:"deny_rules"`
        AuditLoggingOptions json.RawMessage `json:"audit_logging_options"`
    }
    d := json.NewDecoder(strings.NewReader(policyStr))
    d.DisallowUnknownFields()
    var p authzPolicy
    if err := d.Decode(&p); err != nil {
        return fmt.Errorf("failed to unmarshal policy: %v", err)
    }
    return nil
}

Prevention

When it happens

Trigger: Calling the authz SDK with a policy string that is invalid JSON, has a trailing comma, or contains an unknown top-level field (e.g. a typo like "allow_rule" instead of "allow_rules", or a field from a different schema version).

Common situations: YAML-to-JSON conversion producing trailing commas or comments; copy-pasting a field name from an Envoy RBAC config that doesn't exist in the gRPC SDK schema; using a field from a newer authz version on an older grpc-go; mismatched casing (AllowRules vs allow_rules).

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/8aede689e0727949. Report an issue: GitHub.