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
- 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.
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
- 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.
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
- Insufficient privileges
- Insufficient privileges
- Insufficient privileges
- Insufficient privileges
- Insufficient privileges
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)