{"record":{"id":"54d3c3df4dfb487d","repo":"JuliusBrussee/caveman","slug":"envelope-object-kind-is-required","errorCode":null,"errorMessage":"envelope: object kind is required","messagePattern":"envelope: object kind is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/envelope/envelope.go","lineNumber":176,"sourceCode":"\t\treturn nil, fmt.Errorf(\"envelope: ciphertext too short\")\n\t}\n\tnonce, ct := ciphertext[:ns], ciphertext[ns:]\n\tplaintext, err := gcm.Open(nil, nonce, ct, aad)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"envelope: open: %w\", err)\n\t}\n\treturn plaintext, nil\n}\n\nfunc scopeAAD(scope Scope) ([]byte, string, error) {\n\tscope.OrganizationID = strings.TrimSpace(scope.OrganizationID)\n\tscope.ProjectID = strings.TrimSpace(scope.ProjectID)\n\tscope.Kind = strings.TrimSpace(scope.Kind)\n\tif scope.OrganizationID == \"\" {\n\t\treturn nil, \"\", fmt.Errorf(\"envelope: organization scope is required\")\n\t}\n\tif scope.Kind == \"\" {\n\t\treturn nil, \"\", fmt.Errorf(\"envelope: object kind is required\")\n\t}\n\taad, err := json.Marshal(struct {\n\t\tVersion        int    `json:\"version\"`\n\t\tOrganizationID string `json:\"organization_id\"`\n\t\tProjectID      string `json:\"project_id\"`\n\t\tKind           string `json:\"kind\"`\n\t}{2, scope.OrganizationID, scope.ProjectID, scope.Kind})\n\tif err != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"envelope: encode scope: %w\", err)\n\t}\n\tsum := sha256.Sum256(aad)\n\treturn aad, hex.EncodeToString(sum[:]), nil\n}\n","sourceCodeStart":158,"sourceCodeEnd":190,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/envelope/envelope.go#L158-L190","documentation":"scopeAAD requires a non-empty Kind because the AAD binds the ciphertext to the type of object being encrypted, preventing ciphertext of one kind from being decrypted as another. The trimmed Kind was empty when SealForScope or OpenForScope was called. The library fails closed to preserve kind separation.","triggerScenarios":"Calling SealForScope or OpenForScope with envelope.Scope{Kind: \"\"} or Kind containing only whitespace — e.g. the caller forgot to set Kind or passed a variable that was never initialized.","commonSituations":"A generic encrypt helper forwards a caller-supplied scope without validating Kind; refactors renamed the Kind constant but a call site still passes an empty string; test fixtures build a zero-value Scope.","solutions":["Set Scope.Kind to the object's type string (e.g. \"document\", \"token\", \"webhook\") at every seal/open call site","Centralize scope construction in one helper that validates Kind before encryption","Confirm the constant/variable holding Kind is actually initialized (check for shadowed or renamed identifiers)","Ensure refactors didn't drop the Kind assignment in the struct literal"],"exampleFix":"// before\nscope := envelope.Scope{OrganizationID: orgID, ProjectID: projID}\n// after\nscope := envelope.Scope{OrganizationID: orgID, ProjectID: projID, Kind: \"document\"}","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(kind) == \"\" {\n    return errors.New(\"object kind required before envelope operations\")\n}","typeGuard":"func scopeHasKind(s envelope.Scope) bool { return strings.TrimSpace(s.Kind) != \"\" }","tryCatchPattern":"_, _, err := /* SealForScope/OpenForScope */\nif err != nil && strings.Contains(err.Error(), \"object kind is required\") {\n    return ErrMissingObjectKind\n}","preventionTips":["Define Kind constants in one package and use them everywhere","Wrap envelope calls in a helper that validates the full Scope once"],"tags":["go","crypto","aad-binding","validation"],"backgroundTag":"empty-required-field","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}