{"record":{"id":"91e3e436513b8cab","repo":"argoproj/argo-workflows","slug":"failed-to-unmarshal-webhook-client-s-w","errorCode":null,"errorMessage":"failed to unmarshal webhook client \"%s\": %w","messagePattern":"failed to unmarshal webhook client \"(.+?)\": %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/auth/webhook/interceptor.go","lineNumber":95,"sourceCode":"\t// we need to read the request body to check the signature, but we still need it for the GRPC request,\n\t// so read it all now, and then reinstate when we are done.\n\t// Limit to 2MB to prevent denial-of-service via oversized webhook payloads.\n\tconst maxWebhookSize = 2 * 1024 * 1024 // 2MB\n\tbuf, err2 := io.ReadAll(io.LimitReader(r.Body, maxWebhookSize+1))\n\tif err2 != nil {\n\t\treturn fmt.Errorf(\"failed to read webhook request body: %w\", err2)\n\t}\n\tif len(buf) > maxWebhookSize {\n\t\treturn fmt.Errorf(\"webhook request body exceeds maximum size of 2MB\")\n\t}\n\tdefer func() { r.Body = io.NopCloser(bytes.NewBuffer(buf)) }()\n\tserviceAccountInterface := kube.CoreV1().ServiceAccounts(namespace)\n\tfor serviceAccountName, data := range webhookClients.Data {\n\t\tr.Body = io.NopCloser(bytes.NewBuffer(buf))\n\t\tclient := &webhookClient{}\n\t\terr := yaml.Unmarshal(data, client)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal webhook client \\\"%s\\\": %w\", serviceAccountName, err)\n\t\t}\n\t\ti.logger.WithFields(logging.Fields{\"serviceAccountName\": serviceAccountName, \"webhookType\": client.Type}).Debug(r.Context(), \"Attempting to match webhook request\")\n\t\tok := webhookParsers[client.Type](client.Secret, r)\n\t\tif ok {\n\t\t\ti.logger.WithField(\"serviceAccountName\", serviceAccountName).Debug(r.Context(), \"Matched webhook request\")\n\t\t\tserviceAccount, err := serviceAccountInterface.Get(ctx, serviceAccountName, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to get service account \\\"%s\\\": %w\", serviceAccountName, err)\n\t\t\t}\n\t\t\ttokenSecret, err := secretsInterface.Get(ctx, secrets.TokenNameForServiceAccount(serviceAccount), metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to get token secret \\\"%s\\\": %w\", tokenSecret, err)\n\t\t\t}\n\t\t\tr.Header[\"Authorization\"] = []string{\"Bearer \" + string(tokenSecret.Data[\"token\"])}\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn nil","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/server/auth/webhook/interceptor.go#L77-L113","documentation":"During webhook request authentication, the interceptor iterates over the `webhook-clients` ConfigMap data entries and YAML-unmarshals each entry into a webhookClient struct. If an entry is not valid YAML or does not match the expected schema (type, secret fields), the loop aborts and wraps the parse error with the offending service account key name. This prevents authenticating the incoming webhook at all, so the request fails authorization.","triggerScenarios":"An incoming webhook (GitHub/Bitbucket/GitLab etc.) hits the Argo server's /api/v1/events/{namespace}/{discriminator} endpoint while some key in the `webhook-clients` ConfigMap contains malformed YAML, wrong indentation, tabs, or unknown/misspelled fields (e.g. `typ: github`), or non-string values where strings are expected.","commonSituations":"Hand-editing the ConfigMap with kubectl edit and breaking indentation; applying YAML parsed through a templating tool that emitted invalid output; pasting a secret containing special characters (e.g. `#`, `:`) without quoting; upgrading Argo with a ConfigMap written for an older schema.","solutions":["Run `kubectl get configmap webhook-clients -n <ns> -o yaml` and validate every data key parses as YAML (e.g. pipe each entry through `yamllint` or a local yaml.Unmarshal test).","Check each client entry has the expected schema: `type: <github|gitlab|bitbucket|bitbucketserver>` and a `secret:` string.","Quote secret values that contain YAML special characters (`:`, `#`, leading/trailing spaces).","Use tabs-free indentation (YAML forbids tabs); re-apply the ConfigMap after fixing.","Restart/retry the webhook call — the config is re-read per request, no restart needed."],"exampleFix":"# before (broken: unquoted colon, wrong key)\nmy-github:\n  typ: github\n  secret: foo:bar\n# after\nmy-github:\n  type: github\n  secret: \"foo:bar\"","handlingStrategy":"validation","validationCode":"// Validate every webhook-clients entry before applying\ncm, _ := clientset.CoreV1().ConfigMaps(ns).Get(ctx, \"webhook-clients\", metav1.GetOptions{})\nfor name, data := range cm.Data {\n\tvar c struct{ Type, Secret string }\n\tif err := yaml.Unmarshal([]byte(data), &c); err != nil {\n\t\treturn fmt.Errorf(\"entry %q invalid: %w\", name, err)\n\t}\n\tif c.Type == \"\" { return fmt.Errorf(\"entry %q missing type\", name) }\n}","typeGuard":"func validWebhookClient(raw []byte) (*webhookClient, bool) {\n\tvar c webhookClient\n\tif err := yaml.Unmarshal(raw, &c); err != nil || c.Type == \"\" || c.Secret == \"\" { return nil, false }\n\treturn &c, true\n}","tryCatchPattern":null,"preventionTips":["Lint the ConfigMap with a CI check that yaml.Unmarshals every data key","Quote all secret values containing YAML special characters","Use spaces, never tabs, in the ConfigMap entries","Keep the ConfigMap under Git with schema review"],"tags":["webhook","yaml","configmap","authentication"],"backgroundTag":"webhook-client-config-unmarshal-failed","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}