{"record":{"id":"79352e1ed9df1ad1","repo":"Leantime/leantime","slug":"32001-79352e","errorCode":"-32001","errorMessage":"You do not have access to this project's tags.","messagePattern":"You do not have access to this project's tags\\.","errorType":"exception","errorClass":"Leantime\\Core\\Exceptions\\AuthorizationException","httpStatus":403,"severity":"error","filePath":"app/Domain/Tags/Services/Tags.php","lineNumber":52,"sourceCode":"     * The JSON-RPC endpoint has no controller-level project gate (the retired\n     * Api\\Controllers\\Tags forced session('currentProject'), but a JSON-RPC caller\n     * can pass any projectId). isUserAssignedToProject() is the full access check —\n     * it allows admins/owners, org-wide (\"all\") and client-level projects, and\n     * directly assigned users — so this both preserves legitimate access and prevents\n     * cross-project tag enumeration.\n     *\n     * @param  int  $projectId  The project to read tags from\n     * @param  string  $term  Substring to filter tag suggestions by\n     * @return array Matching tag strings (an empty array means no matches, NOT no access)\n     *\n     * @throws AuthorizationException If the user cannot access the project (distinct from a no-match empty result)\n     *\n     * @api\n     */\n    public function getTags(int $projectId, string $term): array\n    {\n        if (! $this->projectRepository->isUserAssignedToProject((int) session('userdata.id'), $projectId)) {\n            throw new AuthorizationException('You do not have access to this project\\'s tags.');\n        }\n\n        $tags = [];\n\n        $ticketTags = $this->ticketRepository->getTags($projectId);\n        $tags = $this->explodeAndMergeTags($ticketTags, $tags);\n\n        $canvasTags = $this->canvasRepository->getTags($projectId);\n        $tags = $this->explodeAndMergeTags($canvasTags, $tags);\n        $unique = array_unique($tags);\n\n        $tagArray = [];\n        foreach ($unique as $tag) {\n            if (str_contains($tag, strip_tags($term))) {\n                $tagArray[] = $tag;\n            }\n        }\n","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Tags/Services/Tags.php#L34-L70","documentation":"Tags::getTags(projectId, term) gates on projectRepository->isUserAssignedToProject() for the CURRENT session user before merging ticket and canvas tags. That check admits admins/owners, org-wide ('all') and client-level projects, and direct assignments — everyone else gets AuthorizationException (JSON-RPC -32001). The gate exists because the JSON-RPC endpoint has no controller-level project context, so without it any caller could enumerate another project's tags by guessing ids.","triggerScenarios":"Calling leantime.rpc.Tags.Tags.getTags with a projectId the session user is not assigned to; a tag autocomplete wired to a project the user can view metadata about but is not a member of; passing 0 or an invalid project id; a stale session after the user was removed from the project.","commonSituations":"Project switchers where some projects are visible but not joined; scripts probing ids over JSON-RPC; users working from an old tab after their assignment was revoked.","solutions":["Only query tags for a project the current user is assigned to (use the project from session('currentProject') when in doubt)","Pre-check Projects::isUserAssignedToProject(userId, projectId) before requesting tag suggestions","Catch AuthorizationException (-32001) and silently degrade the autocomplete rather than crashing the editor"],"exampleFix":"// before\n$tags = $tagsService->getTags($projectId, $term);\n\n// after\nif (! $projectRepository->isUserAssignedToProject((int) session('userdata.id'), $projectId)) {\n    return []; // no suggestions rather than an error\n}\n$tags = $tagsService->getTags($projectId, $term);","handlingStrategy":"validation","validationCode":"$userId = (int) session('userdata.id');\nif (! $projectRepository->isUserAssignedToProject($userId, $projectId)) {\n    return []; // degrade autocomplete silently instead of erroring\n}\n$tags = $tagsService->getTags($projectId, $term);","typeGuard":null,"tryCatchPattern":"try {\n    $tags = $tagsService->getTags($projectId, $term);\n} catch (\\Leantime\\Core\\Exceptions\\AuthorizationException $e) {\n    // -32001: user not assigned to this project — return no suggestions\n    $tags = [];\n}","preventionTips":["Drive tag autocomplete from the user's actually-assigned projects (e.g. session('currentProject'))","Pre-check isUserAssignedToProject before switching the autocomplete target","Distinguish -32001 (no access) from an empty result (no matches) in your error handling"],"tags":["tags","authorization","project-access","json-rpc","autocomplete"],"backgroundTag":"insufficient-permissions","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}