{"record":{"id":"25e1006a909237c4","repo":"apache/pulsar","slug":"unauthorized-to-validatenamespaceoperation-for-ope","errorCode":null,"errorMessage":"Unauthorized to validateNamespaceOperation for operation [%s] on namespace [%s]","messagePattern":"Unauthorized to validateNamespaceOperation for operation \\[(.+?)\\] on namespace \\[(.+?)\\]","errorType":"http","errorClass":"RestException","httpStatus":403,"severity":"warning","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/web/PulsarWebResource.java","lineNumber":1029,"sourceCode":"    public void validateNamespaceOperation(NamespaceName namespaceName, NamespaceOperation operation) {\n        sync(()-> validateNamespaceOperationAsync(namespaceName, operation));\n    }\n\n    public CompletableFuture<Void> validateNamespaceOperationAsync(NamespaceName namespaceName,\n                                                              NamespaceOperation operation) {\n        if (pulsar().getConfiguration().isAuthenticationEnabled()\n            && pulsar().getBrokerService().isAuthorizationEnabled()) {\n            if (!isClientAuthenticated(clientAppId())) {\n                return FutureUtil.failedFuture(\n                        new RestException(Status.FORBIDDEN, \"Need to authenticate to perform the request\"));\n            }\n\n            return pulsar().getBrokerService().getAuthorizationService()\n                    .allowNamespaceOperationAsync(namespaceName, operation, originalPrincipal(),\n                             clientAppId(), clientAuthData())\n                    .thenAccept(isAuthorized -> {\n                        if (!isAuthorized) {\n                            throw new RestException(Status.FORBIDDEN,\n                                    String.format(\"Unauthorized to validateNamespaceOperation for\"\n                                        + \" operation [%s] on namespace [%s]\", operation.toString(), namespaceName));\n                        }\n                    });\n        }\n        return CompletableFuture.completedFuture(null);\n    }\n\n    public void validateNamespacePolicyOperation(NamespaceName namespaceName, PolicyName policy,\n                                                 PolicyOperation operation) {\n        sync(()-> validateNamespacePolicyOperationAsync(namespaceName, policy, operation));\n    }\n\n    public CompletableFuture<Void> validateNamespacePolicyOperationAsync(NamespaceName namespaceName,\n                                                 PolicyName policy,\n                                                 PolicyOperation operation) {\n        if (pulsar().getConfiguration().isAuthenticationEnabled()\n                && pulsar().getBrokerService().isAuthorizationEnabled()) {","sourceCodeStart":1011,"sourceCodeEnd":1047,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/web/PulsarWebResource.java#L1011-L1047","documentation":"HTTP 403 FORBIDDEN thrown by validateNamespaceOperationAsync: the authorization service explicitly denied the requested namespace-level operation (e.g. produce, consume, subscribe, unload, get-topics) for the caller. Unlike tenant failures (401), this is a definitive policy denial on the namespace.","triggerScenarios":"Any admin/client operation that validates a namespace operation where allowNamespaceOperationAsync returns false — e.g. producing to a namespace without PRODUCE grant, calling unload/getPermissions without the needed operation grant.","commonSituations":"Namespace grants never applied to the new role; permissions revoked during policy cleanup; superUserRoles misconfigured on the broker; authorizationEnabled enabled later, exposing previously unauthenticated usage.","solutions":["Grant the needed NamespaceOperation to the role: admin.namespaces().grantPermissionOnNamespace(ns, role, EnumSet.of(operation))","Add the caller to broker's superUserRoles if it is an infrastructure identity","Verify with admin.namespaces().getPermissions(ns) which roles have which actions","Check whether the operation uses originalPrincipal via a proxy and grant that principal too"],"exampleFix":"// before: FORBIDDEN on namespace op\nadmin.namespaces().unload(NamespaceName.get(\"public/default\"));\n// after: grant the operation\nadmin.namespaces().grantPermissionOnNamespace(\"public/default\", \"ops-role\",\n    EnumSet.of(NamespaceOperation.UNLOAD, NamespaceOperation.GET_BUNDLE));","handlingStrategy":"validation","validationCode":"Map<String, Set<NamespaceOperation>> perms = admin.namespaces().getPermissions(ns);\nif (!perms.getOrDefault(myRole, Set.of()).contains(NamespaceOperation.PRODUCE)) {\n    throw new IllegalStateException(\"missing PRODUCE on \" + ns);\n}","typeGuard":null,"tryCatchPattern":"try {\n    nsOp(ns);\n} catch (PulsarAdminException e) {\n    if (e.getStatusCode() == 403 && e.getMessage().contains(\"validateNamespaceOperation\")) {\n        throw new SecurityException(\"grant NamespaceOperation to role\", e);\n    } else throw e;\n}","preventionTips":["Check getPermissions before performing namespace operations","Grant explicit operations rather than relying on superuser","Re-audit grants after enabling authorization"],"tags":["http-403","authorization","namespace","rbac"],"backgroundTag":"missing-permission-grant","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}