{"record":{"id":"b8d28f06957c7cf3","repo":"JuliusBrussee/caveman","slug":"envelope-organization-scope-is-required","errorCode":null,"errorMessage":"envelope: organization scope is required","messagePattern":"envelope: organization scope is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/envelope/envelope.go","lineNumber":173,"sourceCode":"\t}\n\tns := gcm.NonceSize()\n\tif len(ciphertext) < ns {\n\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":155,"sourceCodeEnd":190,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/envelope/envelope.go#L155-L190","documentation":"scopeAAD refuses to build the additional authenticated data because the Scope has an empty OrganizationID after trimming whitespace. Envelope encryption binds ciphertext to a tenant scope, so sealing or opening without an organization would produce or accept data with no tenant binding. The library fails closed instead.","triggerScenarios":"Calling SealForScope or OpenForScope with envelope.Scope{OrganizationID: \"\"} or one containing only whitespace (e.g. from an unset request header or empty tenant context).","commonSituations":"A request missing tenant context reaches the crypto layer; auth middleware did not populate the org ID; a struct literal was built with fields in the wrong order; tests pass a zero-value Scope.","solutions":["Populate Scope.OrganizationID with the authenticated tenant's ID before calling SealForScope/OpenForScope","Trim/validate org context upstream (middleware) and reject requests lacking it before reaching the envelope layer","Check struct-literal field ordering — Go unkeyed literals bind positionally; use keyed fields","Fix test fixtures to use a real, non-empty organization ID"],"exampleFix":"// before\nscope := envelope.Scope{\"\", projectID, \"document\"}\n// after\nif strings.TrimSpace(orgID) == \"\" { return errors.New(\"org required\") }\nscope := envelope.Scope{OrganizationID: orgID, ProjectID: projectID, Kind: \"document\"}","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(orgID) == \"\" {\n    return errors.New(\"organization id required before envelope operations\")\n}","typeGuard":"func scopeHasOrg(s envelope.Scope) bool { return strings.TrimSpace(s.OrganizationID) != \"\" }","tryCatchPattern":"aad, id, err := /* via SealForScope/OpenForScope */\nif err != nil && strings.Contains(err.Error(), \"organization scope is required\") {\n    return ErrMissingTenantContext\n}","preventionTips":["Populate tenant context in middleware before any crypto call","Always build Scope with keyed struct fields","Reject requests missing org context at the handler boundary"],"tags":["go","crypto","tenant-isolation","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"}