{"record":{"id":"191e769544fdb6c7","repo":"apolloconfig/apollo","slug":"you-don-t-have-the-permission-to-modify-namespace-191e76","errorCode":null,"errorMessage":"You don't have the permission to modify namespace: %s","messagePattern":"You don't have the permission to modify namespace: (.+?)","errorType":"http","errorClass":"AccessDeniedException","httpStatus":403,"severity":"warning","filePath":"apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ItemController.java","lineNumber":228,"sourceCode":"    NamespaceIdentifier noPermissionNamespace = null;\n    // check if user has every namespace's ModifyNamespace permission\n    boolean hasPermission = true;\n    for (NamespaceIdentifier namespaceIdentifier : model.getSyncToNamespaces()) {\n      // once user has not one of the namespace's ModifyNamespace permission, then break the loop\n      hasPermission = unifiedPermissionValidator.hasModifyNamespacePermission(\n          namespaceIdentifier.getAppId(), namespaceIdentifier.getEnv().getName(),\n          namespaceIdentifier.getClusterName(), namespaceIdentifier.getNamespaceName());\n      if (!hasPermission) {\n        noPermissionNamespace = namespaceIdentifier;\n        break;\n      }\n    }\n    if (hasPermission) {\n      configService.syncItems(model.getSyncToNamespaces(), model.getSyncItems(),\n          userInfoHolder.getUser().getUserId());\n      return ResponseEntity.status(HttpStatus.OK).build();\n    }\n    throw new AccessDeniedException(String\n        .format(\"You don't have the permission to modify namespace: %s\", noPermissionNamespace));\n  }\n\n  @PreAuthorize(\n      value = \"@unifiedPermissionValidator.hasModifyNamespacePermission(#appId, #env, #clusterName, #namespaceName)\")\n  @PostMapping(\n      value = \"/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/syntax-check\",\n      consumes = {\"application/json\"})\n  public ResponseEntity<Void> syntaxCheckText(@PathVariable String appId, @PathVariable String env,\n      @PathVariable String clusterName, @PathVariable String namespaceName,\n      @RequestBody NamespaceTextModel model) {\n\n    doSyntaxCheck(model);\n\n    return ResponseEntity.ok().build();\n  }\n\n  @PreAuthorize(","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/apolloconfig/apollo/blob/d95fc18d112589efc09ddcbe1507047584d55251/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ItemController.java#L210-L246","documentation":"AccessDeniedException (HTTP 403) from ItemController namespace sync. The handler iterates syncToNamespaces, checks hasModifyNamespacePermission for each target, and on the first namespace the user lacks permission to modify it records that NamespaceIdentifier and breaks; if not all targets are permitted it throws, formatting the offending identifier via NamespaceIdentifier.toString() (appId/env/cluster/namespace).","triggerScenarios":"POST the sync-items flow where model.syncToNamespaces contains at least one namespace the current user cannot Modify (missing ModifyNamespace role on that appId/env/cluster/namespace).","commonSituations":"User has modify rights on the source namespace but not on one target env/cluster; cross-env sync to a locked-down environment; role was revoked between opening the page and submitting.","solutions":["Request ModifyNamespace permission on the namespace named in the error (appId/env/cluster/namespace).","Remove the unauthorized target from syncToNamespaces and sync to permitted namespaces only.","Have an app admin grant the role or perform the sync on your behalf.","Check each target's permission before submitting (see validationCode) when a permission API is available."],"exampleFix":"// before\nsyncToNamespaces = [prod-allowed, locked-down-env]   // 403 on locked-down-env\n\n// after\nsyncToNamespaces = [prod-allowed]   // sync only permitted targets","handlingStrategy":"try-catch","validationCode":"// Best-effort pre-check: only keep targets the user can modify (requires a permission lookup API).\nList<NamespaceIdentifier> permitted = new ArrayList<>();\nfor (NamespaceIdentifier t : model.getSyncToNamespaces()) {\n  if (permissionApi.canModifyNamespace(t.getAppId(), t.getEnv().getName(),\n                                       t.getClusterName(), t.getNamespaceName())) {\n    permitted.add(t);\n  }\n}\nif (permitted.size() != model.getSyncToNamespaces().size()) {\n  // show user which targets they cannot modify, or sync only permitted ones\n}\nmodel.setSyncToNamespaces(permitted);","typeGuard":null,"tryCatchPattern":"// Namespace sync can 403 per-target.\ntry {\n  portal.syncItems(model);\n} catch (HttpClientErrorException.Forbidden e) {\n  // body contains the offending NamespaceIdentifier (appId/env/cluster/namespace)\n  reportMissingPermission(extractNamespaceIdentifier(e.getResponseBodyAsString()));\n}","preventionTips":["Pre-filter sync targets by the user's ModifyNamespace permission.","Request modify rights on all intended targets before syncing.","Handle 403 by surfacing the specific namespace to the user.","Avoid bulk-sync to envs the user cannot touch."],"tags":["permission","access-denied","namespace","sync","role"],"backgroundTag":null,"analyzedSha":"d95fc18d112589efc09ddcbe1507047584d55251","analyzedAt":"2026-08-14T04:00:05.477Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}