{"record":{"id":"976b55c6d310a5a5","repo":"alibaba/nacos","slug":"empty-payload-after-enc","errorCode":null,"errorMessage":"empty payload after enc.","messagePattern":"empty payload after enc\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/com/alibaba/nacos/api/ai/model/NacosAiConfigKeyCodec.java","lineNumber":175,"sourceCode":"     */\n    public static String toPhysicalGroup(String canonicalGroup, String resourcePrefix) {\n        return fitToLength(canonicalGroup, MAX_GROUP_LENGTH, resourcePrefix);\n    }\n    \n    /**\n     * Decode a segment produced by {@link #encodeSegment(String)}.\n     * If not encoded with {@link #ENCODED_PREFIX}, returned unchanged.\n     */\n    public static String decodeSegment(String encoded) {\n        if (encoded == null || encoded.isEmpty()) {\n            return encoded;\n        }\n        if (!encoded.startsWith(ENCODED_PREFIX)) {\n            return encoded;\n        }\n        String hex = encoded.substring(ENCODED_PREFIX.length());\n        if (hex.isEmpty()) {\n            throw new IllegalArgumentException(\"empty payload after \" + ENCODED_PREFIX);\n        }\n        return new String(fromHex(hex), StandardCharsets.UTF_8);\n    }\n    \n    private static String toHex(byte[] bytes) {\n        StringBuilder sb = new StringBuilder(bytes.length * 2);\n        for (byte b : bytes) {\n            sb.append(Character.forDigit((b >> 4) & 0xF, 16));\n            sb.append(Character.forDigit(b & 0xF, 16));\n        }\n        return sb.toString();\n    }\n    \n    private static byte[] fromHex(String hex) {\n        int len = hex.length();\n        if ((len & 1) != 0) {\n            throw new IllegalArgumentException(\"illegal hex length: \" + len);\n        }","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/api/src/main/java/com/alibaba/nacos/api/ai/model/NacosAiConfigKeyCodec.java#L157-L193","documentation":"NacosAiConfigKeyCodec.decodeSegment received a string that starts with the reserved 'enc.' prefix but has zero characters after it. The encoder produces 'enc.' followed by lowercase hex UTF-8 bytes; an empty payload (just 'enc.' with no hex) is structurally invalid and cannot be decoded.","triggerScenarios":"Manually constructing or truncating an encoded key segment to exactly 'enc.'. Passing a corrupted or partially written config dataId/group that was cut off after the prefix. Database or network truncation that stripped the hex portion.","commonSituations":"A config key was manually edited or programmatically built with an empty encoded segment. A migration or copy operation truncated the dataId. A bug in custom serialization logic that emits 'enc.' for empty strings instead of leaving them unencoded.","solutions":["Do not prepend 'enc.' to empty strings; pass the empty or original value directly to decodeSegment which returns it unchanged.","Audit the data source that produced the malformed segment (database row, config store, serialized JSON).","If the segment is external/untrusted, validate it with hasReservedEncodedPrefix + length check before calling decodeSegment.","Re-encode the original logical value with encodeSegment to regenerate a valid key."],"exampleFix":"// before -- produces 'enc.' with no payload\nString key = NacosAiConfigKeyCodec.ENCODED_PREFIX + \"\";\nNacosAiConfigKeyCodec.decodeSegment(key); // throws\n\n// after -- let the encoder decide, or skip encoding for empty\nString key = original.isEmpty() ? original\n    : NacosAiConfigKeyCodec.encodeSegment(original);\nNacosAiConfigKeyCodec.decodeSegment(key); // ok","handlingStrategy":"validation","validationCode":"public static String safeDecodeSegment(String encoded) {\n    if (encoded == null || encoded.isEmpty()) return encoded;\n    if (!encoded.startsWith(NacosAiConfigKeyCodec.ENCODED_PREFIX)) return encoded;\n    String hex = encoded.substring(NacosAiConfigKeyCodec.ENCODED_PREFIX.length());\n    if (hex.isEmpty()) {\n        // corrupted: prefix with no payload — return as-is or re-encode\n        return encoded;\n    }\n    return NacosAiConfigKeyCodec.decodeSegment(encoded);\n}","typeGuard":"public static boolean isValidEncodedSegment(String s) {\n    if (s == null || s.isEmpty()) return true;\n    if (!s.startsWith(NacosAiConfigKeyCodec.ENCODED_PREFIX)) return true;\n    String hex = s.substring(NacosAiConfigKeyCodec.ENCODED_PREFIX.length());\n    return !hex.isEmpty() && hex.matches(\"[0-9a-fA-F]+\") && hex.length() % 2 == 0;\n}","tryCatchPattern":"try {\n    String decoded = NacosAiConfigKeyCodec.decodeSegment(segment);\n} catch (IllegalArgumentException e) {\n    logger.warn(\"Malformed encoded segment, using raw value: {}\", segment);\n    decoded = segment; // or re-encode from source\n}","preventionTips":["Never manually prepend 'enc.' — always use encodeSegment().","Validate external segments before decoding.","Audit database/config storage for truncated encoded keys."],"tags":["codec","config-key","validation","data-corruption"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}