{"record":{"id":"171a34d1f09900a7","repo":"SonarSource/sonarqube","slug":"forbidden-access-to-gitlab-verify-your-token-s-pe","errorCode":null,"errorMessage":"Forbidden access to GitLab. Verify your token's permissions and IP restrictions.","messagePattern":"Forbidden access to GitLab\\. Verify your token's permissions and IP restrictions\\.","errorType":"exception","errorClass":"GitlabServerException","httpStatus":403,"severity":"error","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java","lineNumber":218,"sourceCode":"    }\n  }\n\n  protected static void checkResponseIsSuccessful(Response response) throws IOException {\n    checkResponseIsSuccessful(response, \"GitLab Merge Request did not happen, please check your configuration\");\n  }\n\n  protected static void checkResponseIsSuccessful(Response response, String errorMessage) throws IOException {\n    if (!response.isSuccessful()) {\n      String body = response.body().string();\n      LOG.error(\"Gitlab API call to [{}] failed with {} http code. gitlab response content : [{}]\", response.request().url(), response.code(), body);\n      if (isTokenRevoked(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token was revoked\");\n      } else if (isTokenExpired(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token is expired\");\n      } else if (isInsufficientScope(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token has insufficient scope\");\n      } else if (response.code() == HTTP_FORBIDDEN) {\n        throw new GitlabServerException(response.code(), \"Forbidden access to GitLab. Verify your token's permissions and IP restrictions.\");\n      } else if (response.code() == HTTP_TOO_MANY_REQUESTS) {\n        throw new GitlabServerException(response.code(), \"GitLab API rate limit exceeded. Try again later.\");\n      } else if (response.code() == HTTP_UNAUTHORIZED) {\n        throw new GitlabServerException(response.code(), \"Invalid personal access token\");\n      } else if (response.isRedirect()) {\n        throw new GitlabServerException(response.code(), \"Request was redirected, please provide the correct URL\");\n      } else {\n        throw new GitlabServerException(response.code(), errorMessage);\n      }\n    }\n  }\n\n  private static boolean isTokenRevoked(Response response, String body) {\n    if (response.code() == HTTP_UNAUTHORIZED) {\n      try {\n        Optional<GsonError> gitlabError = GsonError.parseOne(body);\n        return gitlabError.map(GsonError::getErrorDescription).map(description -> description.contains(\"Token was revoked\")).orElse(false);\n      } catch (JsonParseException e) {","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java#L200-L236","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Raise the token owner's role on the project to at least Maintainer.","Add the SonarQube server IP to the GitLab group/project IP allow-list (Settings > General > IP restrictions) or remove the restriction.","If SSO-enforced group, authorize the PAT for that group (Group Settings > SSO > token access).","Test the same call manually with curl to see GitLab's message body, which is logged by SonarQube at ERROR level."],"exampleFix":"// before: token owner role = Developer\nproject_member_role: Developer\n// after\nproject_member_role: Maintainer","handlingStrategy":"validation","validationCode":"// Verify the token owner can write to the project before enabling decoration\nResponse r = call(\"GET\", gitlabUrl + \"/api/v4/projects/\" + urlEncode(projectPath) + \"/members/all/\" + userId, token);\nif (r.code() == 200) {\n  int level = getJson(r).get(\"access_level\").getAsInt();\n  if (level < 40) throw new IllegalStateException(\"Requires Maintainer (40+) access level, got \" + level);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Grant the token owner at least Maintainer role on bound projects.","Add the SonarQube server egress IP to GitLab IP restriction allow-lists.","Authorize PATs for SSO-protected groups."],"tags":["gitlab","permissions","http-403","network","configuration"],"backgroundTag":"permission-denied","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}