{"record":{"id":"3c73b8537de1fb0b","repo":"XTLS/Xray-core","slug":"missing-key","errorCode":null,"errorMessage":"missing key","messagePattern":"missing key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/shadowsocks_2022/inbound_multi.go","lineNumber":72,"sourceCode":"\tmemUsers := []*protocol.MemoryUser{}\n\tfor i, user := range config.Users {\n\t\tif user.Email == \"\" {\n\t\t\tu := uuid.New()\n\t\t\tuser.Email = \"unnamed-user-\" + strconv.Itoa(i) + \"-\" + u.String()\n\t\t}\n\t\tu, err := user.ToMemoryUser()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to get shadowsocks user\").Base(err).AtError()\n\t\t}\n\t\tmemUsers = append(memUsers, u)\n\t}\n\n\tinbound := &MultiUserInbound{\n\t\tnetworks: networks,\n\t\tusers:    memUsers,\n\t}\n\tif config.Key == \"\" {\n\t\treturn nil, errors.New(\"missing key\")\n\t}\n\tpsk, err := base64.StdEncoding.DecodeString(config.Key)\n\tif err != nil {\n\t\treturn nil, errors.New(\"parse config\").Base(err)\n\t}\n\tservice, err := shadowaead_2022.NewMultiService[int](config.Method, psk, 500, inbound, nil)\n\tif err != nil {\n\t\treturn nil, errors.New(\"create service\").Base(err)\n\t}\n\terr = service.UpdateUsersWithPasswords(\n\t\tC.MapIndexed(memUsers, func(index int, it *protocol.MemoryUser) int { return index }),\n\t\tC.Map(memUsers, func(it *protocol.MemoryUser) string { return it.Account.(*MemoryAccount).Key }),\n\t)\n\tif err != nil {\n\t\treturn nil, errors.New(\"create service\").Base(err)\n\t}\n\n\tinbound.service = service","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/proxy/shadowsocks_2022/inbound_multi.go#L54-L90","documentation":"Hard guard in the multi-user 2022 inbound constructor: the server-level pre-shared key field (config.Key) is an empty string. Shadowsocks-2022 mandates a server PSK from which per-user identity keys are derived, so an empty key is rejected before base64 decoding or service creation. Startup fails immediately.","triggerScenarios":"Creating a shadowsocks-2022 multi-user inbound whose JSON omits `key` or sets it to \"\" — e.g. config generated by copying the single-user template where the field is named `password`, not `key`.","commonSituations":"Confusion between single-user inbound (`password` field) and multi-user inbound (`key` field); panel not emitting the key field; hand-written config missing the line.","solutions":["Set the inbound-level `key` to a base64 PSK of the length required by the method (32 bytes for 256-bit, 16 for 128-bit).","Do not put per-user keys in the inbound-level field; those go in users[].","Re-run xray run -test to confirm."],"exampleFix":"// before\n{ \"protocol\": \"shadowsocks-2022\", \"settings\": { \"method\": \"2022-blake3-aes-256-gcm\", \"key\": \"\" } }\n// after\n{ \"protocol\": \"shadowsocks-2022\", \"settings\": { \"method\": \"2022-blake3-aes-256-gcm\", \"key\": \"vJ+Q7vP4n3...==\" } }","handlingStrategy":"validation","validationCode":"func validateSS2022Inbound(cfg *MultiInboundConfig) error {\n  if strings.TrimSpace(cfg.Key) == \"\" {\n    return errors.New(\"shadowsocks-2022 multi inbound requires a server-level base64 `key`\")\n  }\n  return nil\n}","typeGuard":"func hasServerKey(key string) bool { return strings.TrimSpace(key) != \"\" }","tryCatchPattern":null,"preventionTips":["Remember field naming: single-user inbound uses `password`, multi-user uses `key`.","Template configs with the key field pre-marked required."],"tags":["shadowsocks-2022","config","psk","validation","startup"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}