{"record":{"id":"6b8559fd634ebda1","repo":"SonarSource/sonarqube","slug":"insufficient-privileges-6b8559","errorCode":null,"errorMessage":"Insufficient privileges","messagePattern":"Insufficient privileges","errorType":"exception","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java","lineNumber":116,"sourceCode":"\n    action.createFieldsParam(singleton(\"category\"))\n      .setDescription(format(\"Comma-separated list of the additional fields to be returned in response. No additional field is returned by default. Possible values are:\" +\n        \"<ul>\" +\n        \"<li>%s - category as defined in the Update Center. A connection to the Update Center is needed</li>\" +\n        \"</ul>\", FIELD_CATEGORY))\n      .setSince(\"5.6\");\n\n    action.createParam(PARAM_TYPE)\n      .setInternal(true)\n      .setSince(\"8.5\")\n      .setPossibleValues(Type.values())\n      .setDescription(\"Allows to filter plugins by type\");\n  }\n\n  @Override\n  public void handle(Request request, Response response) throws Exception {\n    if (!userSession.isLoggedIn() && !userSession.hasPermission(GlobalPermission.SCAN)) {\n      throw insufficientPrivilegesException();\n    }\n\n    String typeParam = request.param(PARAM_TYPE);\n    SortedSet<ServerPlugin> installedPlugins = loadInstalledPlugins(typeParam);\n    Map<String, PluginDto> dtosByKey;\n    try (DbSession dbSession = dbClient.openSession(false)) {\n      dtosByKey = dbClient.pluginDao().selectAll(dbSession).stream().collect(toMap(PluginDto::getKee, Function.identity()));\n    }\n\n    List<String> additionalFields = request.paramAsStrings(WebService.Param.FIELDS);\n    Map<String, Plugin> updateCenterPlugins = (additionalFields == null || additionalFields.isEmpty()) ? emptyMap() : compatiblePluginsByKey(updateCenterMatrixFactory);\n\n    List<PluginDetails> pluginList = new LinkedList<>();\n\n    for (ServerPlugin installedPlugin : installedPlugins) {\n      PluginInfo pluginInfo = installedPlugin.getPluginInfo();\n      PluginDto pluginDto = dtosByKey.get(pluginInfo.getKey());\n      Objects.requireNonNull(pluginDto, () -> format(\"Plugin %s is installed but not in DB\", pluginInfo.getKey()));","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java#L98-L134","documentation":"api/plugins/installed requires the caller to be logged in with global Execute Analysis (SCAN) permission; anonymous users and unprivileged users get 'Insufficient privileges'. The check is performed at the top of handle before any plugin data is loaded.","triggerScenarios":"GET api/plugins/installed when userSession.isLoggedIn() is false and hasPermission(GlobalPermission.SCAN) is false — i.e., anonymous requests or logged-in users without global Execute Analysis.","commonSituations":"Anonymous monitoring scripts hitting the endpoint on a server with anonymous access disabled; a normal developer account listing installed plugins for version checks; SonarCloud-style tokens lacking global scan.","solutions":["Authenticate the request with a user token that has global 'Execute Analysis' permission.","If anonymous access is intended for monitoring, enable anonymous access and grant the 'Anyone' group Execute Analysis (not usually recommended).","Use an admin account's token for plugin inventory scripts."],"exampleFix":"// before\ncurl \"$SONAR/api/plugins/installed\"                       // anonymous -> 403\n// after\ncurl -u \"$SCAN_TOKEN:\" \"$SONAR/api/plugins/installed\"","handlingStrategy":"validation","validationCode":"const auth = await get('/api/authentication/validate', { auth: token });\nconst authz = await get('/api/permissions/authorization');\nif (!auth.valid || !authz.globalPermissions.includes('scan')) throw new Error('Token user needs global Execute Analysis');","typeGuard":"function hasGlobalScan(authz) {\n  return Boolean(authz && Array.isArray(authz.globalPermissions) && authz.globalPermissions.includes('scan'));\n}","tryCatchPattern":"try {\n  return await getInstalledPlugins();\n} catch (e) {\n  if (e.response && e.response.status === 403 && !isAuthenticated()) {\n    throw new Error('api/plugins/installed is not anonymous: use a token with global Execute Analysis', { cause: e });\n  }\n  throw e;\n}","preventionTips":["Never call plugin inventory endpoints anonymously; attach a token.","Give monitoring accounts global Execute Analysis if they must list plugins.","Check server setting: anonymous access may be disabled by policy."],"tags":["sonarqube","webapi","plugins","authorization"],"backgroundTag":"insufficient-permissions","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}