{"record":{"id":"4827917c9ef0e8e0","repo":"projectdiscovery/nuclei","slug":"matcher-s-has-unexpected-fields-s","errorCode":null,"errorMessage":"matcher %s has unexpected fields: %s","messagePattern":"matcher (.+?) has unexpected fields: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/operators/matchers/validate.go","lineNumber":81,"sourceCode":"\t\t}\n\t}\n\treturn nil\n}\n\nfunc checkFields(m *Matcher, matcherMap map[string]interface{}, expectedFields ...string) error {\n\tvar foundUnexpectedFields []string\n\tfor marshaledFieldName := range matcherMap {\n\t\t// revert back the marshaled name to the original field\n\t\tstructFieldName, err := getFieldNameFromYamlTag(marshaledFieldName, *m)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !sliceutil.Contains(expectedFields, structFieldName) {\n\t\t\tfoundUnexpectedFields = append(foundUnexpectedFields, structFieldName)\n\t\t}\n\t}\n\tif len(foundUnexpectedFields) > 0 {\n\t\treturn fmt.Errorf(\"matcher %s has unexpected fields: %s\", m.matcherType, strings.Join(foundUnexpectedFields, \",\"))\n\t}\n\treturn nil\n}\n\nfunc getFieldNameFromYamlTag(tagName string, object interface{}) (string, error) {\n\treflectType := reflect.TypeOf(object)\n\tif reflectType.Kind() != reflect.Struct {\n\t\treturn \"\", errors.New(\"the object must be a struct\")\n\t}\n\tfor idx := 0; idx < reflectType.NumField(); idx++ {\n\t\tfield := reflectType.Field(idx)\n\t\ttagParts := strings.Split(field.Tag.Get(\"yaml\"), \",\")\n\t\tif len(tagParts) > 0 && tagParts[0] == tagName {\n\t\t\treturn field.Name, nil\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"field %s not found\", tagName)\n}","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/projectdiscovery/nuclei/blob/265b3a3dec374741614e342f813c10f8b38d2bb7/pkg/operators/matchers/validate.go#L63-L99","documentation":"Template validation error from Matcher.Validate -> checkFields (pkg/operators/matchers/validate.go:81). The matcher is marshaled to a map, and every YAML key is mapped back to a struct field via getFieldNameFromYamlTag; keys not in the expected field set for the matcher's type (validate.go:35-51, e.g. word: Words/Part/Encoding/CaseInsensitive + common fields) are reported as unexpected, comma-separated. This catches fields misplaced under the wrong matcher type.","triggerScenarios":"E.g. `type: status` with a `words:` list, `type: word` with `regex:` entries, or any key the Matcher struct does define but that type does not accept (also honors unknown keys that fail tag mapping). Note the loop returns early on a totally unknown key via getFieldNameFromYamlTag's error path.","commonSituations":"Copy-paste between matchers of different types without pruning the old field; tutorials mixing matcher fields; refactors that change type but leave stale keys; YAML anchors merging extra keys into a matcher.","solutions":["Keep only fields valid for the matcher type (word: words/part/encoding/case-insensitive; regex: regex/part/...; status: status; size: size; dsl: dsl; binary: binary/part/encoding)","Split mixed concerns into multiple matchers joined by condition instead of one matcher with foreign fields","Validate with `nuclei -validate -t template.yaml` which surfaces the full unexpected-field list","When generating templates programmatically, build typed structs instead of loose maps"],"exampleFix":"# before\nmatchers:\n  - type: status\n    status:\n      - 200\n    words:\n      - 'ok'\n# after\nmatchers:\n  - type: status\n    status:\n      - 200\n  - type: word\n    words:\n      - 'ok'","handlingStrategy":"validation","validationCode":"allowed := map[string][]string{\n\t\"word\": {\"words\", \"part\", \"encoding\", \"case-insensitive\", \"condition\", \"match-all\", \"name\", \"internal\"},\n\t\"status\": {\"status\", \"part\"},\n\t\"size\": {\"size\", \"part\"},\n\t\"regex\": {\"regex\", \"part\", \"encoding\", \"case-insensitive\", \"condition\"},\n\t\"binary\": {\"binary\", \"part\", \"encoding\"},\n\t\"dsl\": {\"dsl\", \"condition\"},\n\t\"xpath\": {\"xpath\", \"part\"},\n}\n// reject matcher maps containing keys outside allowed[type]","typeGuard":"func matcherKeysValid(t string, keys []string) bool {\n\tset := allowed[t]\n\tfor _, k := range keys { if !contains(set, k) { return false } }\n\treturn true\n}","tryCatchPattern":"if err := m.CompileMatchers(); err != nil {\n\tif ve, ok := m.Validate(); ok == false && strings.Contains(ve.Error(), \"unexpected fields\") {\n\t\t// list allowed fields for the matcher type in the error output\n\t}\n}","preventionTips":["One matcher, one type, only its own fields","Split multi-signal detections into multiple matchers with condition: and","Use -validate in CI to catch leftover fields after type changes"],"tags":["matcher","validation","template","yaml","compile-time"],"backgroundTag":null,"analyzedSha":"265b3a3dec374741614e342f813c10f8b38d2bb7","analyzedAt":"2026-08-15T20:05:51.855Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}