diff --git a/internal/models/spec.go b/internal/models/spec.go index 59de8e31..da718536 100644 --- a/internal/models/spec.go +++ b/internal/models/spec.go @@ -70,8 +70,16 @@ func (g *GraderConfig) UnmarshalYAML(node *yaml.Node) error { Parameters yaml.Node `yaml:"config,omitempty"` } + // Marshal the node back to bytes so we can use a strict decoder that rejects unknown fields. + nodeBytes, err := yaml.Marshal(node) + if err != nil { + return fmt.Errorf("failed to marshal grader node: %w", err) + } + decoder := yaml.NewDecoder(bytes.NewReader(nodeBytes)) + decoder.KnownFields(true) + var raw rawGraderConfig - if err := node.Decode(&raw); err != nil { + if err := decoder.Decode(&raw); err != nil { return err } diff --git a/internal/models/spec_test.go b/internal/models/spec_test.go index 73781d29..f2b27200 100644 --- a/internal/models/spec_test.go +++ b/internal/models/spec_test.go @@ -43,6 +43,40 @@ config: trials_per_task: 0 timeout_seconds: 60 executor: mock +`, + expectError: true, + }, + { + name: "unknown field inside grader entry", + specYAML: `name: valid +skill: test-skill +config: + trials_per_task: 1 + timeout_seconds: 60 + executor: mock +graders: + - name: my-grader + type: text + unknown_grader_field: should cause error + config: + contains: ["hello"] +`, + expectError: true, + }, + { + name: "unknown field inside grader config", + specYAML: `name: valid +skill: test-skill +config: + trials_per_task: 1 + timeout_seconds: 60 + executor: mock +graders: + - name: my-grader + type: text + config: + contains: ["hello"] + unknown_config_field: should cause error `, expectError: true, },