{"record":{"id":"cb1e08253551f758","repo":"microsoft/garnet","slug":"invalid-custom-command-name-customname","errorCode":null,"errorMessage":"Invalid custom command name '{customName}'","messagePattern":"Invalid custom command name '(.+?)'","errorType":"exception","errorClass":"ACLException","httpStatus":null,"severity":"error","filePath":"libs/server/ACL/User.cs","lineNumber":375,"sourceCode":"            }\n            while ((prev = Interlocked.CompareExchange(ref this._enabledCommands, updated, oldPerms)) != oldPerms);\n        }\n\n        /// <summary>\n        /// Adds the given custom (extension) command name to the user's per-name allow list.\n        /// Custom commands aren't tracked in the RespCommand bitmap (their dynamic IDs fall outside it);\n        /// they live in a separate per-name allow/deny set with deny precedence at check time.\n        /// </summary>\n        /// <param name=\"customName\">Custom command name. Normalized to uppercase; matching is case-insensitive.</param>\n        public void AddCustomCommand(string customName)\n        {\n            ArgumentNullException.ThrowIfNull(customName);\n\n            // Reject anything ACLParser would reject so the persisted Description can't be poisoned\n            // by callers bypassing the parser (e.g. \"foo +@all\" would re-parse as two tokens on reload).\n            if (!ACLParser.IsValidCustomCommandName(customName))\n            {\n                throw new ACLException($\"Invalid custom command name '{customName}'\");\n            }\n\n            string normalized = customName.ToUpperInvariant();\n\n            CommandPermissionSet prev = this._enabledCommands;\n            string descUpdate = $\"+{normalized.ToLowerInvariant()}\";\n\n            CommandPermissionSet oldPerms;\n            CommandPermissionSet updated;\n            do\n            {\n                oldPerms = prev;\n\n                // No-op fast path: already allowed.\n                if (oldPerms == CommandPermissionSet.All ||\n                    (oldPerms.CustomAllowed.Contains(normalized) && !oldPerms.CustomDenied.Contains(normalized)))\n                {\n                    return;","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/server/ACL/User.cs#L357-L393","documentation":"Thrown by User.AddCustomCommand(customName) when the supplied name fails ACLParser.IsValidCustomCommandName validation. The validator requires the first character to be alphanumeric (A-Z a-z 0-9) and all subsequent characters to be alphanumeric or '.', '_', '-', '|'. This guard prevents callers from bypassing the parser with names that would re-parse as multiple tokens on reload (poisoning the persisted Description).","triggerScenarios":"Calling user.AddCustomCommand(\"foo +@all\") (embedded space re-parses as two tokens), AddCustomCommand(\"\") (empty), AddCustomCommand(\"$cmd\") (illegal first char '$'), or AddCustomCommand(\"cmd with space\"). The name is intended to mirror built-in subcommand notation like 'CLIENT|GETNAME'.","commonSituations":"Registering a custom/extension command under a user with a name containing whitespace, shell metacharacters, or other characters the ACL grammar treats specially; passing a raw command string instead of the cleaned command name.","solutions":["Sanitize the custom command name to contain only alphanumeric, '.', '_', '-', and '|' characters with an alphanumeric first character.","If the name contains a subcommand separator, use the pipe form (e.g. 'MYCMD|SUB') which is explicitly allowed.","Call ACLParser.IsValidCustomCommandName(name) in a unit test or precondition before invoking AddCustomCommand."],"exampleFix":"// before\nuser.AddCustomCommand(\"my cmd!\");\n\n// after\nuser.AddCustomCommand(\"my_cmd\");","handlingStrategy":"validation","validationCode":"// Mirror the internal validation before calling AddCustomCommand\nstatic bool IsValidCustomName(string name)\n{\n    if (string.IsNullOrEmpty(name)) return false;\n    if (!char.IsLetterOrDigit(name[0])) return false;\n    foreach (var c in name.Skip(1))\n        if (!(char.IsLetterOrDigit(c) || c == '.' || c == '_' || c == '-' || c == '|'))\n            return false;\n    return true;\n}\nif (!IsValidCustomName(customName)) throw new ArgumentException($\"Invalid custom command name '{customName}'\");","typeGuard":"static bool IsValidCustomCommandName(string name) =>\n    !string.IsNullOrEmpty(name) &&\n    char.IsLetterOrDigit(name[0]) &&\n    name.Skip(1).All(c => char.IsLetterOrDigit(c) || c is '.' or '_' or '-' or '|');","tryCatchPattern":null,"preventionTips":["Pre-validate custom command names with the same character rules as ACLParser.IsValidCustomCommandName.","Allow only alphanumeric, '.', '_', '-', and '|' in names.","Write unit tests for AddCustomCommand covering illegal-character inputs."],"tags":["acl","validation","custom-command"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}