{"record":{"id":"512e37f699ca7fff","repo":"HMCL-dev/HMCL","slug":"missing-protected-payload-member-nonce","errorCode":null,"errorMessage":"Missing protected payload member: nonce","messagePattern":"Missing protected payload member: nonce","errorType":"validation","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/setting/ProtectedPayload.java","lineNumber":241,"sourceCode":"                }\n\n                String payloadText = JsonUtils.UGLY_GSON.toJson(payload);\n                byte[] payloadBytes = payloadText.getBytes(StandardCharsets.UTF_8);\n                byte[] encryptedPayload = encryptPayload(payloadBytes, nonce);\n                String actualPayload = Base64.getEncoder().encodeToString(encryptedPayload);\n\n                envelope.addProperty(PROPERTY_PROTECTION, id());\n                envelope.add(PROPERTY_PAYLOAD, splitObfuscatedPayload(actualPayload));\n                envelope.addProperty(PROPERTY_NONCE, Base64.getEncoder().encodeToString(nonce));\n            }\n\n            /// Reads the payload from the given envelope.\n            @Override\n            protected JsonElement readPayload(JsonObject envelope) {\n                try {\n                    String encodedNonce = JsonUtils.getString(envelope, PROPERTY_NONCE);\n                    if (encodedNonce == null) {\n                        throw new JsonParseException(\"Missing protected payload member: nonce\");\n                    }\n\n                    byte[] nonce = Base64.getDecoder().decode(encodedNonce);\n                    if (nonce.length != NONCE_SIZE) {\n                        throw new JsonParseException(\"Protected payload nonce has invalid length\");\n                    }\n\n                    String encodedPayload = joinObfuscatedPayload(envelope);\n                    byte[] encryptedPayload = Base64.getDecoder().decode(encodedPayload);\n                    byte[] payloadBytes = decryptPayload(encryptedPayload, nonce);\n                    return JsonParser.parseString(new String(payloadBytes, StandardCharsets.UTF_8));\n                } catch (IllegalArgumentException e) {\n                    throw new JsonParseException(\"Failed to reveal protected JSON payload\", e);\n                }\n            }\n        };\n\n        /// The serialized protection marker.","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/setting/ProtectedPayload.java#L223-L259","documentation":"Thrown by OBFUSCATED_V1.readPayload when the JSON envelope declares protection mode hmcl-obfuscated-v1 but has no \"nonce\" member (JsonUtils.getString returns null). Without the nonce stored alongside the ciphertext, the ChaCha20-Poly1305 payload cannot be decrypted, so the library fails fast with a descriptive JsonParseException.","triggerScenarios":"Calling ProtectedPayload.read(envelope, type) (or ProtectionMode.fromEnvelope(...).readPayload) on an envelope where envelope.get(\"nonce\") is absent or JSON null — e.g. a hand-written envelope, one produced by an older/other writer, or one where the nonce member was renamed or stripped by a sanitizer.","commonSituations":"Manually editing or partial-copying a settings JSON; a schema migration dropping unknown members; another tool round-tripping the JSON and omitting fields it does not understand; constructing the envelope in code and forgetting envelope.addProperty(\"nonce\", ...).","solutions":["Regenerate the envelope through ProtectionMode.writePayload so the nonce, payload lanes, and protection marker are all written consistently.","If the data is recoverable, restore the missing \"nonce\" member (Base64, 16 chars) from a backup of the original file.","Check the writer path: ensure no serializer/migration step strips the nonce member between write and read."],"exampleFix":"// before (hand-built envelope)\nJsonObject envelope = new JsonObject();\nenvelope.addProperty(\"protection\", \"hmcl-obfuscated-v1\");\nenvelope.add(\"payload\", lanes);\n// after\nmode.writePayload(envelope, payload); // writes protection, payload lanes, and nonce","handlingStrategy":"validation","validationCode":"static boolean hasNonce(JsonObject envelope) {\n    return envelope.has(\"nonce\") && envelope.get(\"nonce\").isJsonPrimitive()\n        && envelope.get(\"nonce\").getAsString() != null;\n}","typeGuard":"static boolean isObfuscatedEnvelopeWithNonce(JsonObject envelope) {\n    return envelope.has(\"protection\") && \"hmcl-obfuscated-v1\".equals(envelope.get(\"protection\").getAsString())\n        && envelope.has(\"nonce\");\n}","tryCatchPattern":"try {\n    return ProtectedPayload.read(envelope, JsonObject.class);\n} catch (JsonParseException e) {\n    LOG.warning(\"Envelope missing/corrupt members; rebuilding\", e);\n    return rebuildEnvelope();\n}","preventionTips":["Always build envelopes via ProtectionMode.writePayload instead of assembling JSON by hand.","Configure JSON migrations/serializers to preserve unknown members like \"nonce\".","Validate envelope completeness (protection, nonce, payload array) at load time before use."],"tags":["json","missing-field","crypto","envelope","hmcl"],"backgroundTag":"missing-required-config-field","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}