SonarSource/sonarqube · error · ForbiddenException

Insufficient privileges

Error message

Insufficient privileges

What it means

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.

Solutions

  1. Authenticate the request with a user token that has global 'Execute Analysis' permission.
  2. If anonymous access is intended for monitoring, enable anonymous access and grant the 'Anyone' group Execute Analysis (not usually recommended).
  3. Use an admin account's token for plugin inventory scripts.

Example fix

// before
curl "$SONAR/api/plugins/installed"                       // anonymous -> 403
// after
curl -u "$SCAN_TOKEN:" "$SONAR/api/plugins/installed"
Defensive patterns

Strategy: validation

Validate before calling

const auth = await get('/api/authentication/validate', { auth: token });
const authz = await get('/api/permissions/authorization');
if (!auth.valid || !authz.globalPermissions.includes('scan')) throw new Error('Token user needs global Execute Analysis');

Type guard

function hasGlobalScan(authz) {
  return Boolean(authz && Array.isArray(authz.globalPermissions) && authz.globalPermissions.includes('scan'));
}

Try / catch

try {
  return await getInstalledPlugins();
} catch (e) {
  if (e.response && e.response.status === 403 && !isAuthenticated()) {
    throw new Error('api/plugins/installed is not anonymous: use a token with global Execute Analysis', { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.

Related errors


AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09). Data as JSON: /api/errors/6b8559fd634ebda1. Report an issue: GitHub.

Appendix: source

Thrown at server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java:116

    action.createFieldsParam(singleton("category"))
      .setDescription(format("Comma-separated list of the additional fields to be returned in response. No additional field is returned by default. Possible values are:" +
        "<ul>" +
        "<li>%s - category as defined in the Update Center. A connection to the Update Center is needed</li>" +
        "</ul>", FIELD_CATEGORY))
      .setSince("5.6");

    action.createParam(PARAM_TYPE)
      .setInternal(true)
      .setSince("8.5")
      .setPossibleValues(Type.values())
      .setDescription("Allows to filter plugins by type");
  }

  @Override
  public void handle(Request request, Response response) throws Exception {
    if (!userSession.isLoggedIn() && !userSession.hasPermission(GlobalPermission.SCAN)) {
      throw insufficientPrivilegesException();
    }

    String typeParam = request.param(PARAM_TYPE);
    SortedSet<ServerPlugin> installedPlugins = loadInstalledPlugins(typeParam);
    Map<String, PluginDto> dtosByKey;
    try (DbSession dbSession = dbClient.openSession(false)) {
      dtosByKey = dbClient.pluginDao().selectAll(dbSession).stream().collect(toMap(PluginDto::getKee, Function.identity()));
    }

    List<String> additionalFields = request.paramAsStrings(WebService.Param.FIELDS);
    Map<String, Plugin> updateCenterPlugins = (additionalFields == null || additionalFields.isEmpty()) ? emptyMap() : compatiblePluginsByKey(updateCenterMatrixFactory);

    List<PluginDetails> pluginList = new LinkedList<>();

    for (ServerPlugin installedPlugin : installedPlugins) {
      PluginInfo pluginInfo = installedPlugin.getPluginInfo();
      PluginDto pluginDto = dtosByKey.get(pluginInfo.getKey());
      Objects.requireNonNull(pluginDto, () -> format("Plugin %s is installed but not in DB", pluginInfo.getKey()));

View on GitHub (pinned to 184c821202)