{"record":{"id":"47466f4333042fa5","repo":"grpc-ecosystem/grpc-gateway","slug":"security-s-declares-different-requirements","errorCode":null,"errorMessage":"security: %s declares different requirements","messagePattern":"security: (.+?) declares different requirements","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"openapiv3-merge/internal/merge/merge.go","lineNumber":416,"sourceCode":"\t}\n\tif len(out.Security) == 0 {\n\t\tout.Security = src.Security\n\t\treturn nil\n\t}\n\ta, err := json.Marshal(out.Security)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"security: %w\", err)\n\t}\n\tb, err := json.Marshal(src.Security)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"security: %w\", err)\n\t}\n\tsame, err := canonicalEqual(a, b)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"security: %w\", err)\n\t}\n\tif !same {\n\t\treturn fmt.Errorf(\"security: %s declares different requirements\", src.name)\n\t}\n\treturn nil\n}\n\n// mergeExtras applies first-wins to unknown top-level keys (notably\n// extensions `x-*`). Conflicting redeclarations from later inputs are\n// silently ignored, matching the policy for info/servers/etc.\nfunc mergeExtras(dst, src *orderedObject) {\n\tfor _, k := range src.keys {\n\t\tif _, ok := dst.get(k); ok {\n\t\t\tcontinue\n\t\t}\n\t\tdst.set(k, src.vals[k])\n\t}\n}\n\n// canonicalEqual reports whether a and b decode to the same JSON value\n// under canonical (sorted-key) encoding.","sourceCodeStart":398,"sourceCodeEnd":434,"githubUrl":"https://github.com/grpc-ecosystem/grpc-gateway/blob/a58a4436a376a4bcc7d8f10c4d4f919a8438bba9/openapiv3-merge/internal/merge/merge.go#L398-L434","documentation":"openapiv3-merge applies first-wins to the root `security` array, but only when later inputs agree. If a later input declares a non-empty `security` that differs (after canonical comparison) from the one already accumulated, the merge fails rather than silently changing which authentication the combined API requires.","triggerScenarios":"Running `openapiv3-merge a.json b.json` where a.json has root security e.g. [{\"apiKey\":[]}] and b.json has different non-empty security e.g. [{\"oauth2\":[\"read\"]}].","commonSituations":"Two services merged into one gateway spec but each declaring different auth schemes; one spec regenerated with an added or changed security requirement; a spec adding a second alternative requirement to an existing list.","solutions":["Make the root `security` arrays identical (canonically) across all input files","Remove the root `security` from all but one input so the first declaration wins","Split the merge into documents that genuinely share one auth model, or keep them separate","If alternatives should be unioned intentionally, preprocess the specs to agree on the combined list first"],"exampleFix":"// before: a.json {\"security\":[{\"apiKey\":[]}]}, b.json {\"security\":[{\"oauth2\":[\"read\"]}]}\n// after: b.json {\"security\":[{\"apiKey\":[]}]} (or delete b.json's security field)","handlingStrategy":"validation","validationCode":"func checkSecurityConflict(files []string) error {\n    var first json.RawMessage\n    for _, f := range files {\n        b, _ := os.ReadFile(f)\n        var doc struct {\n            Security json.RawMessage `json:\"security\"`\n        }\n        if err := json.Unmarshal(b, &doc); err != nil { return err }\n        if len(doc.Security) == 0 || string(doc.Security) == \"null\" { continue }\n        norm := normalizeJSON(doc.Security) // jq-style canonical form\n        if first == nil {\n            first = norm\n        } else if norm != first {\n            return fmt.Errorf(\"%s declares different security requirements\", f)\n        }\n    }\n    return nil\n}","typeGuard":"func hasSecurity(doc map[string]json.RawMessage) bool {\n    sec, ok := doc[\"security\"]\n    return ok && len(sec) > 0 && string(sec) != \"null\"\n}","tryCatchPattern":"if err := merge.Merge(inputs); err != nil {\n    if strings.Contains(err.Error(), \"declares different requirements\") {\n        // prompt user to unify security or drop it from conflicting inputs\n    }\n    return err\n}","preventionTips":["Keep one canonical security declaration; omit root security from all other inputs","Document the API-wide auth model in a single shared spec","Pre-check that all inputs' security arrays are canonically identical before merging","Automate spec generation so security sections never drift between services"],"tags":["go","openapi","merge-conflict","security"],"backgroundTag":"conflicting-security-requirements","analyzedSha":"a58a4436a376a4bcc7d8f10c4d4f919a8438bba9","analyzedAt":"2026-09-02T10:28:31.537Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T16:17:10.729Z"}