SonarSource/sonarqube · error · GitlabServerException

Forbidden access to GitLab. Verify your token's permissions

Error message

Forbidden access to GitLab. Verify your token's permissions and IP restrictions.

What it means

When a GitLab API call returns HTTP 403 (Forbidden) without a token-revocation/expiry/scope cause, checkResponseIsSuccessful throws GitlabServerException 'Forbidden access to GitLab. Verify your token's permissions and IP restrictions.' The token is valid but GitLab refuses the request, typically due to role level or IP allow-listing.

Source

Thrown at server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java:218

    }
  }

  protected static void checkResponseIsSuccessful(Response response) throws IOException {
    checkResponseIsSuccessful(response, "GitLab Merge Request did not happen, please check your configuration");
  }

  protected static void checkResponseIsSuccessful(Response response, String errorMessage) throws IOException {
    if (!response.isSuccessful()) {
      String body = response.body().string();
      LOG.error("Gitlab API call to [{}] failed with {} http code. gitlab response content : [{}]", response.request().url(), response.code(), body);
      if (isTokenRevoked(response, body)) {
        throw new GitlabServerException(response.code(), "Your GitLab token was revoked");
      } else if (isTokenExpired(response, body)) {
        throw new GitlabServerException(response.code(), "Your GitLab token is expired");
      } else if (isInsufficientScope(response, body)) {
        throw new GitlabServerException(response.code(), "Your GitLab token has insufficient scope");
      } else if (response.code() == HTTP_FORBIDDEN) {
        throw new GitlabServerException(response.code(), "Forbidden access to GitLab. Verify your token's permissions and IP restrictions.");
      } else if (response.code() == HTTP_TOO_MANY_REQUESTS) {
        throw new GitlabServerException(response.code(), "GitLab API rate limit exceeded. Try again later.");
      } else if (response.code() == HTTP_UNAUTHORIZED) {
        throw new GitlabServerException(response.code(), "Invalid personal access token");
      } else if (response.isRedirect()) {
        throw new GitlabServerException(response.code(), "Request was redirected, please provide the correct URL");
      } else {
        throw new GitlabServerException(response.code(), errorMessage);
      }
    }
  }

  private static boolean isTokenRevoked(Response response, String body) {
    if (response.code() == HTTP_UNAUTHORIZED) {
      try {
        Optional<GsonError> gitlabError = GsonError.parseOne(body);
        return gitlabError.map(GsonError::getErrorDescription).map(description -> description.contains("Token was revoked")).orElse(false);
      } catch (JsonParseException e) {

View on GitHub (pinned to 184c821202)

Solutions

  1. Raise the token owner's role on the project to at least Maintainer.
  2. Add the SonarQube server IP to the GitLab group/project IP allow-list (Settings > General > IP restrictions) or remove the restriction.
  3. If SSO-enforced group, authorize the PAT for that group (Group Settings > SSO > token access).
  4. Test the same call manually with curl to see GitLab's message body, which is logged by SonarQube at ERROR level.

Example fix

// before: token owner role = Developer
project_member_role: Developer
// after
project_member_role: Maintainer
Defensive patterns

Strategy: validation

Validate before calling

// Verify the token owner can write to the project before enabling decoration
Response r = call("GET", gitlabUrl + "/api/v4/projects/" + urlEncode(projectPath) + "/members/all/" + userId, token);
if (r.code() == 200) {
  int level = getJson(r).get("access_level").getAsInt();
  if (level < 40) throw new IllegalStateException("Requires Maintainer (40+) access level, got " + level);
}

Prevention

When it happens

Trigger: Any GitLab API call via checkResponseIsSuccessful returns 403 that does not match the insufficient_scope body pattern, e.g. role too low on the project or the caller IP blocked by GitLab's IP restriction settings (GitlabApplicationClient.java:218).

Common situations: Token owner is not Maintainer/Owner of the project; GitLab group/project IP allow-list excludes the SonarQube server's egress IP; group SSO enforcement blocks the token; GitLab rate/abuse protection flagging the server.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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