{"record":{"id":"8e1e2f7ef52f07ca","repo":"elastic/elasticsearch","slug":"unable-to-parse-policy-patch-for-layer","errorCode":null,"errorMessage":"Unable to parse policy patch for layer [{}]","messagePattern":"Unable to parse policy patch for layer \\[(.+?)\\]","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtils.java","lineNumber":119,"sourceCode":"            try {\n                var versionedPolicy = decodeEncodedPolicy(encodedPolicy, layerName, externalPlugin);\n                validatePolicyScopes(layerName, versionedPolicy.policy(), moduleNames, \"<patch>\");\n\n                // Empty versions defaults to \"any\"\n                if (versionedPolicy.versions().isEmpty() || versionedPolicy.versions().contains(version)) {\n                    logger.info(\"Using policy patch for layer [{}]\", layerName);\n                    return versionedPolicy.policy();\n                } else {\n                    logger.warn(\n                        \"Found a policy patch with version mismatch. The patch will not be applied. \"\n                            + \"Layer [{}]; policy versions [{}]; current version [{}]\",\n                        layerName,\n                        String.join(\",\", versionedPolicy.versions()),\n                        version\n                    );\n                }\n            } catch (Exception e) {\n                throw new IllegalStateException(\"Unable to parse policy patch for layer [\" + layerName + \"]\", e);\n            }\n        }\n        return null;\n    }\n\n    static VersionedPolicy decodeEncodedPolicy(String base64String, String layerName, boolean isExternalPlugin) throws IOException {\n        byte[] policyDefinition = Base64.getDecoder().decode(base64String);\n        return new PolicyParser(new ByteArrayInputStream(policyDefinition), layerName, isExternalPlugin).parseVersionedPolicy();\n    }\n\n    private static void validatePolicyScopes(String layerName, Policy policy, Set<String> moduleNames, String policyLocation) {\n        // TODO: should this check actually be part of the parser?\n        for (Scope scope : policy.scopes()) {\n            if (moduleNames.contains(scope.moduleName()) == false) {\n                throw new IllegalStateException(\n                    Strings.format(\n                        \"Invalid module name in policy: layer [%s] does not have module [%s]; available modules [%s]; policy path [%s]\",\n                        layerName,","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtils.java#L101-L137","documentation":"Wrapped exception from PolicyUtils.parseEncodedPolicyIfExists. It tries to base64-decode and JSON-parse a policy patch supplied (typically via a system property / agent arg) for a named layer; any failure during decode, parse, or scope validation is caught and rethrown as an IllegalStateException with this message and the original cause attached.","triggerScenarios":"A non-null encodedPolicy string is supplied to parseEncodedPolicyIfExists but it is not valid base64, or its decoded JSON is not a valid policy, or scope validation against moduleNames fails. The layer name in the message identifies which plugin/module layer caused it.","commonSituations":"Setting an entitlement policy patch via JVM system property with a malformed base64 payload; shipping a plugin whose embedded policy JSON has a syntax error; version mismatch where decode succeeds but downstream policy validation throws; copy-pasting a truncated base64 blob.","solutions":["Inspect the caused-by exception (the original IOException or PolicyParserException) for the real parse failure.","Regenerate the base64 policy payload from the canonical JSON (base64 encode without line wrapping) and ensure the JSON parses standalone.","If the failure is scope validation, confirm every module referenced in the policy exists in the layer's available module set.","Verify the encoded string has no trailing whitespace or newline when injected via system property."],"exampleFix":"// before\n-Des.entitlements.policy_patch.mymodule=$(cat broken.json | base64)\n\n// after\n-Des.entitlements.policy_patch.mymodule=$(jq -c . valid-policy.json | base64 -w0)","handlingStrategy":"try-catch","validationCode":"// Validate the base64 + JSON before injecting as a system property.\npublic static String encodePolicyPatch(Path policyJson) throws IOException {\n    byte[] json = Files.readAllBytes(policyJson);\n    // parse to confirm it's valid JSON / valid policy shape\n    try (var in = new ByteArrayInputStream(json)) {\n        new PolicyParser(in, \"precheck\", false).parsePolicy();\n    }\n    return Base64.getEncoder().encodeToString(json);\n}","typeGuard":null,"tryCatchPattern":"try {\n    Policy p = PolicyUtils.parseEncodedPolicyIfExists(encoded, version, external, layerName, moduleNames);\n} catch (IllegalStateException e) {\n    // message: \"Unable to parse policy patch for layer [...]\"\n    Throwable cause = e.getCause();\n    log.error(\"policy patch for {} failed: {}\", layerName, cause == null ? e : cause);\n    // fall back to no patch (null) or fail fast depending on operator policy\n}","preventionTips":["Always pre-parse the policy JSON before base64-encoding it.","Generate the base64 with `base64 -w0` (no line wrapping) when injecting via system property.","Keep the caused-by exception in logs — it has the real parse error.","Pin the policy `versions` field to your current ES version to avoid silent skips."],"tags":["entitlements","policy","base64","config"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}