{"record":{"id":"96e822421a588c64","repo":"apache/pulsar","slug":"name-is-null-empty-or-contains-a","errorCode":null,"errorMessage":"${name} is NULL, empty or contains a '&'","messagePattern":"(.+?) is NULL, empty or contains a '&'","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-broker-auth-sasl/src/main/java/org/apache/pulsar/broker/authentication/SaslRoleToken.java","lineNumber":94,"sourceCode":"\n    public SaslRoleToken(String userRole, String session, long expires) {\n        checkForIllegalArgument(userRole, \"userRole\");\n        checkForIllegalArgument(session, \"session\");\n        this.userRole = userRole;\n        this.session = session;\n        this.expires = expires;\n        generateToken();\n    }\n\n    /**\n     * Check if the provided value is invalid. Throw an error if it is invalid, NOP otherwise.\n     *\n     * @param value the value to check.\n     * @param name the parameter name to use in an error message if the value is invalid.\n     */\n    private static void checkForIllegalArgument(String value, String name) {\n        if (value == null || value.length() == 0 || value.contains(ATTR_SEPARATOR)) {\n            throw new IllegalArgumentException(name + ILLEGAL_ARG_MSG);\n        }\n    }\n\n    /**\n     * Sets the expiration of the token.\n     *\n     * @param expires expiration time of the token in milliseconds since the epoch.\n     */\n    public void setExpires(long expires) {\n        if (this != SaslRoleToken.ANONYMOUS) {\n            this.expires = expires;\n            generateToken();\n        }\n    }\n\n    /**\n     * Generates the token.\n     */","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker-auth-sasl/src/main/java/org/apache/pulsar/broker/authentication/SaslRoleToken.java#L76-L112","documentation":"IllegalArgumentException from SaslRoleToken.checkForIllegalArgument, thrown by the SaslRoleToken constructors when userRole or session is null, empty, or contains the '&' attribute separator character. '&' is the delimiter used in the serialized token string (u=..&i=..&e=..), so a value containing it would corrupt the token format and is rejected up front.","triggerScenarios":"new SaslRoleToken(userRole, session) with null/empty session or a session containing '&' (the 2-arg constructor checks session only); new SaslRoleToken(userRole, session, expires) with null/empty userRole or session or either containing '&' (e.g. a Kerberos principal like user/host@REALM isn't affected, but a name containing '&' is); SaslRoleToken.parse passing a value extracted from a malformed token string into the constructor.","commonSituations":"Caller builds a token from unvalidated user input where the username contains '&' (e.g. a display name or email-derived role); session id accidentally built by joining values with '&'; a null/empty session because the upstream authentication step produced no session identifier; unit/custom code constructing tokens by hand.","solutions":["Validate inputs before constructing: reject or sanitize values that are null, empty, or contain '&' (URL-encode or strip the character)","Use a generated session identifier (UUID or similar) rather than assembling one from user-controlled strings","If '&' must be preserved in userRole, escape/encode it (e.g. percent-encoding) before constructing the token and decode after parse","Catch IllegalArgumentException at the API boundary and return a 4xx-style authentication error identifying the offending parameter"],"exampleFix":"// before\nSaslRoleToken token = new SaslRoleToken(userName, sessionId); // throws if userName is null or contains '&'\n// after\nif (userName == null || userName.isEmpty() || userName.contains(\"&\")) {\n    throw new IllegalArgumentException(\"userRole is NULL, empty or contains a '&'\");\n}\nString safeUserName = URLEncoder.encode(userName, StandardCharsets.UTF_8);\nSaslRoleToken token = new SaslRoleToken(safeUserName, sessionId);","handlingStrategy":"validation","validationCode":"// run before new SaslRoleToken(userRole, session[, expires])\nstatic void validateTokenInput(String userRole, String session) {\n    if (userRole == null || userRole.isEmpty() || userRole.contains(\"&\")) {\n        throw new IllegalArgumentException(\"userRole is NULL, empty or contains a '&'\");\n    }\n    if (session == null || session.isEmpty() || session.contains(\"&\")) {\n        throw new IllegalArgumentException(\"session is NULL, empty or contains a '&'\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    SaslRoleToken token = new SaslRoleToken(userRole, session);\n} catch (IllegalArgumentException e) {\n    // parameter name is prefixed in the message (\"session is NULL, empty or contains a '&'\")\n    throw new AuthenticationException(\"Invalid token parameter: \" + e.getMessage());\n}","preventionTips":["Sanitize or URL-encode user-supplied role names so they never contain '&'","Generate session ids with UUID.randomUUID() instead of concatenating user data","Remember the 2-arg constructor validates only session — validate userRole yourself before calling it","Add a unit test covering null, empty, and '&' inputs for both constructors"],"tags":["sasl","token","validation","illegal-argument"],"backgroundTag":"invalid-token-attribute","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}