{"record":{"id":"a58a92b97aec1444","repo":"spring-projects/spring-security","slug":"failed-to-decode-samlresponse-a58a92","errorCode":null,"errorMessage":"Failed to decode SAMLResponse","messagePattern":"Failed to decode SAMLResponse","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2Utils.java","lineNumber":189,"sourceCode":"\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// in cases of an incomplete final chunk, ensure the unused bits are zero\n\t\t\t\tswitch (goodChars % 4) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\treturn true;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\treturn (lastGoodCharVal & 0b1111) == 0;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\treturn (lastGoodCharVal & 0b11) == 0;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid checkAcceptable(String ins) {\n\t\t\t\tif (!isAcceptable(ins)) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"Failed to decode SAMLResponse\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\tstatic class CappedOutputStream extends OutputStream {\n\n\t\tprivate static final long MAX_SIZE = 1024 * 1024;\n\n\t\tprivate final OutputStream delegate;\n\n\t\tprivate int size;\n\n\t\tCappedOutputStream(OutputStream delegate) {\n\t\t\tthis.delegate = delegate;\n\t\t}","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2Utils.java#L171-L207","documentation":"Base64Checker.checkAcceptable validates that a SAMLResponse parameter is an acceptable Base64 string before decoding: every character must be ignored-or-in-alphabet, the length mod 4 must not be 1, and the unused bits of an incomplete final chunk must be zero. When validation fails it throws IllegalArgumentException('Failed to decode SAMLResponse'). This guards the requireBase64(true) path used when reading the SAMLResponse request parameter.","triggerScenarios":"Passing a string that fails the Base64 structural check to Saml2Utils.withEncoded(...).requireBase64(true).decode(): e.g. length % 4 == 1, non-Base64 characters that decode ambiguously, non-zero padding bits in a trailing 2- or 3-character chunk, or a null/empty parameter being validated.","commonSituations":"IdP or intermediary HTML-escapes or corrupts the SAMLResponse parameter; a load balancer or framework truncates/re-encodes the form value; a client sends tampered or garbage samlResponse values (often malicious probing); custom code feeds a URL-encoded (still %-xx) string in without decoding it first.","solutions":["Log the failing samlResponse value (safe, it failed validation) and inspect for HTML entities (&#..;), '+' replaced by ' ', or truncation","URL-decode the parameter before Base64 checking if handling the raw query string yourself; the framework normally does this in request.getParameter","Compare the value length and charset with what the IdP actually sent (capture at the IdP or via network trace)","Treat it as an authentication failure: the Base64 check is a security control; do not bypass it — reject the response","If the IdP legitimately sends non-canonical Base64, verify with getSaml2AuthenticationTokenConverter configuration that requireBase64 applies to your binding"],"exampleFix":"// before: raw value contains HTML entities\nString encoded = request.getParameter(\"SAMLResponse\"); // \"PHNhbWxw...&#xA;\"\n// after: ensure parameter passed through the servlet's URL decoding, or strip whitespace/entity noise\nString encoded = request.getParameter(\"SAMLResponse\").replaceAll(\"\\\\s\", \"\");\nif (encoded == null || encoded.isEmpty()) {\n    throw new Saml2AuthenticationException(Saml2Error.invalidResponse(\"missing SAMLResponse\"), null);\n}","handlingStrategy":"validation","validationCode":"private static final java.util.regex.Pattern B64 = java.util.regex.Pattern.compile(\"^[A-Za-z0-9+/\\\\r\\\\n]+={0,2}$\");\nboolean acceptableBase64(String s) {\n    return s != null && !s.isEmpty() && s.length() % 4 != 1 && B64.matcher(s).matches();\n}\nif (!acceptableBase64(request.getParameter(\"SAMLResponse\"))) { reject(); }","typeGuard":"boolean isUsableSamlResponse(String s) { return s != null && s.length() > 8 && s.length() % 4 != 1; }","tryCatchPattern":"try { decoding.decode(); } catch (IllegalArgumentException ex) { throw new Saml2AuthenticationException(Saml2Error.invalidResponse(\"SAMLResponse is not valid Base64\"), ex); }","preventionTips":["URL-decode request parameters before Base64 validation when handling raw query strings","Reject empty or whitespace-only SAMLResponse parameters early with a clear error","Check for intermediaries (proxies, WAFs) rewriting or truncating the parameter","Never bypass the Base64 check — it is a security control against malformed input"],"tags":["saml","base64","validation","malformed-input"],"backgroundTag":"invalid-argument-format","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}