apolloconfig/apollo · error · AccessDeniedException

Access is denied

Error message

Access is denied

What it means

Thrown by ItemController.requireConfigReadForUserToken when a USER_TOKEN identity attempts to read items/config for a namespace that is hidden from the current user. The shouldHideConfigToCurrentUser check evaluates the token holder's role against namespace visibility rules. Maps to HTTP 403 AccessDeniedException.

Solutions

  1. Grant the token holder's account edit or release permission on the target namespace through the Portal.
  2. Ensure the USER_TOKEN has config:read scope and is authorized for the target appId.
  3. Switch to a CONSUMER token with appropriate namespace permissions for the target app.
Defensive patterns

Strategy: validation

Validate before calling

// Before item read with USER_TOKEN, verify the namespace is not hidden
if (authType.equals("USER_TOKEN") && isNamespaceHidden(appId, env, clusterName, namespaceName)) {
    throw new SecurityException("Namespace is hidden from the current user. Grant edit/release permission.");
}

Try / catch

try {
    return client.get("/openapi/v1/envs/" + env + "/apps/" + appId + "/clusters/" + clusterName
        + "/namespaces/" + namespaceName + "/items");
} catch (AccessDeniedException e) {
    logger.warn("USER_TOKEN denied item read on namespace. Check namespace visibility and token scope.");
    throw e;
}

Prevention

When it happens

Trigger: GET item-by-namespace or item-diff endpoints with a USER_TOKEN where the namespace is restricted and the token holder lacks edit/release permission. The method only throws for USER_TOKEN; interactive USER identity gets silent empty results via shouldHideConfigToPortalUser instead.

Common situations: A personal access token scoped to one app is used to read items from a namespace in another app that is hidden. A namespace is marked as restricted after the token was issued, so previously-working reads now fail with 403.

Related errors


AI-assisted analysis of apolloconfig/apollo@d95fc18d11 (2026-08-14). Data as JSON: /api/errors/31518dea8cf2c578. Report an issue: GitHub.

Appendix: source

Thrown at apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/v1/controller/ItemController.java:312

      return operator;
    }

    throw new BadRequestException("Unsupported auth type: %s", authType);
  }

  private boolean shouldHideConfigToPortalUser(String appId, String env, String clusterName,
      String namespaceName) {
    return UserIdentityConstants.USER.equals(UserIdentityContextHolder.getAuthType())
        && unifiedPermissionValidator.shouldHideConfigToCurrentUser(appId, env, clusterName,
            namespaceName);
  }

  private void requireConfigReadForUserToken(String appId, String env, String clusterName,
      String namespaceName) {
    if (UserIdentityConstants.USER_TOKEN.equals(UserIdentityContextHolder.getAuthType())
        && unifiedPermissionValidator.shouldHideConfigToCurrentUser(appId, env, clusterName,
            namespaceName)) {
      throw new AccessDeniedException("Access is denied");
    }
  }

  private void requireSyncNamespacesReadableForUserToken(OpenNamespaceSyncDTO model) {
    if (!UserIdentityConstants.USER_TOKEN.equals(UserIdentityContextHolder.getAuthType())) {
      return;
    }
    for (OpenNamespaceIdentifier namespaceIdentifier : model.getSyncToNamespaces()) {
      requireConfigReadForUserToken(namespaceIdentifier.getAppId(), namespaceIdentifier.getEnv(),
          namespaceIdentifier.getClusterName(), namespaceIdentifier.getNamespaceName());
    }
  }

  private OpenItemPageDTO emptyPage(Integer page, Integer size) {
    OpenItemPageDTO result = new OpenItemPageDTO();
    result.setPage(page);
    result.setSize(size);
    result.setTotal(0L);

View on GitHub (pinned to d95fc18d11)