apolloconfig/apollo · error · AccessDeniedException
You don't have the permission to modify namespace
Error message
You don't have the permission to modify namespace: %s
What it means
HTTP 403 (AccessDeniedException). Thrown by ItemController.checkSyncPermissions when a bulk item-sync request (syncItems) targets one or more destination namespaces on which the authenticated caller lacks namespace-modify permission. The validator UnifiedPermissionValidator.hasModifyNamespacePermission is evaluated per target namespace; the FIRST namespace that fails is reported back in the message (its OpenNamespaceIdentifier toString). Portal SSO users and OpenAPI consumer tokens are both subject to this check.
Solutions
- Inspect the noPermissionNamespace value in the 403 body and grant ModifyNamespace (or EditNamespace) role on that exact appId/env/cluster/namespace to the caller.
- Remove or correct the offending entry from syncToNamespaces in the request payload so every target matches a namespace the caller is authorized to modify.
- If using an OpenAPI consumer token, recreate/extend the token's app scope to include every destination namespace.
- Retry the sync only after re-checking each target with the OpenAPI permission/role endpoints.
Example fix
// before: token only has modify on DEV
syncDTO.getSyncToNamespaces().add(ns(PROD)); // 403 on PROD
// after: drop targets the caller cannot modify, or grant the role first
List<OpenNamespaceIdentifier> targets = syncDTO.getSyncToNamespaces().stream()
.filter(t -> hasModifyPermission(token, t)) // client-side pre-check
.collect(Collectors.toList());
syncDTO.setSyncToNamespaces(targets); Defensive patterns
Strategy: validation
Validate before calling
// Before syncItems, pre-check modify permission on every target namespace.
boolean allOk = true;
OpenNamespaceIdentifier blocked = null;
for (OpenNamespaceIdentifier t : syncDTO.getSyncToNamespaces()) {
// GET /openapi/v1/apps/{appId}/envs/{env}/clusters/{cluster}/namespaces/{ns}/role
// or use a dedicated hasModify permission probe exposed by the portal.
if (!canModify(token, t.getAppId(), t.getEnv(), t.getClusterName(), t.getNamespaceName())) {
allOk = false; blocked = t; break;
}
}
if (!allOk) { /* drop blocked or grant role, do NOT call syncItems */ } Type guard
null
Try / catch
// Distinguish 403 (permission) from other failures.
try {
client.syncItems(appId, env, cluster, ns, syncDTO);
} catch (HttpServerErrorException.Forbidden e) {
// body contains "You don't have the permission to modify namespace: <id>"
String ns = extractNamespaceFrom403(e.getResponseBodyAsString());
log.warn("Missing modify permission on {}, grant role or drop target", ns);
} Prevention
- Grant ModifyNamespace on every destination namespace before configuring cross-env sync.
- Keep the token's app/env scope aligned with all sync targets.
- Sync from the least-privileged source; never assume destination rights from source rights.
When it happens
Trigger: POST /openapi/v1/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items/syncItems with an OpenNamespaceSyncDTO whose syncToNamespaces lists a namespace the caller cannot modify. Triggered when the caller has modify rights on the source namespace but not on at least one destination, or when a destination namespace was renamed/deleted and the permission grant is stale.
Common situations: Syncing config across environments (DEV->FAT->PRO) with a token scoped to only one env; a destination cluster/namespace name typo so the permission lookup misses; an admin revoked the ModifyNamespace role after the sync job was configured; cross-app sync where the token lacks the target app's role.
Related errors
- Access is denied
- Create namespace permission is required
- Delete namespace permission is required
- Access is denied
- Access is denied
AI-assisted analysis of apolloconfig/apollo@d95fc18d11 (2026-08-14).
Data as JSON: /api/errors/5e62123905e706e9.
Report an issue: GitHub.
Appendix: source
Thrown at apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/v1/controller/ItemController.java:391
return false;
}
return true;
}
private void checkSyncPermissions(OpenNamespaceSyncDTO model) {
OpenNamespaceIdentifier noPermissionNamespace = null;
boolean hasPermission = true;
for (OpenNamespaceIdentifier namespaceIdentifier : model.getSyncToNamespaces()) {
hasPermission = unifiedPermissionValidator.hasModifyNamespacePermission(
namespaceIdentifier.getAppId(), namespaceIdentifier.getEnv(),
namespaceIdentifier.getClusterName(), namespaceIdentifier.getNamespaceName());
if (!hasPermission) {
noPermissionNamespace = namespaceIdentifier;
break;
}
}
if (!hasPermission) {
throw new AccessDeniedException(String
.format("You don't have the permission to modify namespace: %s", noPermissionNamespace));
}
}
private String decodeBase64(String key) {
try {
return decodeBase64(key, Base64.getDecoder());
} catch (IllegalArgumentException standardBase64Exception) {
try {
return decodeBase64(key, Base64.getUrlDecoder());
} catch (IllegalArgumentException urlBase64Exception) {
throw new BadRequestException("Invalid encoded key");
}
}
}
private String decodeBase64(String key, Base64.Decoder decoder) {
return new String(decoder.decode(key), StandardCharsets.UTF_8);View on GitHub (pinned to d95fc18d11)