grpc-ecosystem/grpc-gateway · error
security: %s declares different requirements
Error message
security: %s declares different requirements
What it means
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.
Source
Thrown at openapiv3-merge/internal/merge/merge.go:416
}
if len(out.Security) == 0 {
out.Security = src.Security
return nil
}
a, err := json.Marshal(out.Security)
if err != nil {
return fmt.Errorf("security: %w", err)
}
b, err := json.Marshal(src.Security)
if err != nil {
return fmt.Errorf("security: %w", err)
}
same, err := canonicalEqual(a, b)
if err != nil {
return fmt.Errorf("security: %w", err)
}
if !same {
return fmt.Errorf("security: %s declares different requirements", src.name)
}
return nil
}
// mergeExtras applies first-wins to unknown top-level keys (notably
// extensions `x-*`). Conflicting redeclarations from later inputs are
// silently ignored, matching the policy for info/servers/etc.
func mergeExtras(dst, src *orderedObject) {
for _, k := range src.keys {
if _, ok := dst.get(k); ok {
continue
}
dst.set(k, src.vals[k])
}
}
// canonicalEqual reports whether a and b decode to the same JSON value
// under canonical (sorted-key) encoding.View on GitHub (pinned to a58a4436a3)
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
Example fix
// before: a.json {"security":[{"apiKey":[]}]}, b.json {"security":[{"oauth2":["read"]}]}
// after: b.json {"security":[{"apiKey":[]}]} (or delete b.json's security field) Defensive patterns
Strategy: validation
Validate before calling
func checkSecurityConflict(files []string) error {
var first json.RawMessage
for _, f := range files {
b, _ := os.ReadFile(f)
var doc struct {
Security json.RawMessage `json:"security"`
}
if err := json.Unmarshal(b, &doc); err != nil { return err }
if len(doc.Security) == 0 || string(doc.Security) == "null" { continue }
norm := normalizeJSON(doc.Security) // jq-style canonical form
if first == nil {
first = norm
} else if norm != first {
return fmt.Errorf("%s declares different security requirements", f)
}
}
return nil
} Type guard
func hasSecurity(doc map[string]json.RawMessage) bool {
sec, ok := doc["security"]
return ok && len(sec) > 0 && string(sec) != "null"
} Try / catch
if err := merge.Merge(inputs); err != nil {
if strings.Contains(err.Error(), "declares different requirements") {
// prompt user to unify security or drop it from conflicting inputs
}
return err
} Prevention
- 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
When it happens
Trigger: 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"]}].
Common situations: 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.
Related errors
- tags[%q]: %w
- tags[%q]: %s redefines a tag with different metadata
- %s: %w
- %s: expected JSON object at top level
- %s: %q: %w
AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02).
Data as JSON: /api/errors/47466f4333042fa5.
Report an issue: GitHub.