{"record":{"id":"b8ea0ba536a202b7","repo":"halo-dev/halo","slug":"you-have-no-permission-to-delete-this-notification","errorCode":null,"errorMessage":"You have no permission to delete this notification.","messagePattern":"You have no permission to delete this notification\\.","errorType":"http","errorClass":"AccessDeniedException","httpStatus":403,"severity":"error","filePath":"application/src/main/java/run/halo/app/notification/DefaultNotificationService.java","lineNumber":56,"sourceCode":"                    notification.getSpec().setLastReadAt(Instant.now());\n                    return client.update(notification);\n                });\n    }\n\n    @Override\n    public Flux<String> markSpecifiedAsRead(String username, List<String> names) {\n        return Flux.fromIterable(names)\n                .flatMap(name -> markAsRead(username, name))\n                .map(notification -> notification.getMetadata().getName());\n    }\n\n    @Override\n    public Mono<Notification> deleteByName(String username, String name) {\n        return client.get(Notification.class, name)\n                .doOnNext(notification -> {\n                    var recipient = notification.getSpec().getRecipient();\n                    if (!username.equals(recipient)) {\n                        throw new AccessDeniedException(\"You have no permission to delete this notification.\");\n                    }\n                })\n                .flatMap(client::delete);\n    }\n\n    static boolean isRecipient(Notification notification, String username) {\n        Assert.notNull(notification, \"Notification must not be null\");\n        Assert.notNull(username, \"Username must not be null\");\n        return username.equals(notification.getSpec().getRecipient());\n    }\n}\n","sourceCodeStart":38,"sourceCodeEnd":68,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/java/run/halo/app/notification/DefaultNotificationService.java#L38-L68","documentation":"Thrown as AccessDeniedException by DefaultNotificationService.deleteByName when the fetched Notification's spec.recipient does not equal the requesting username. Notifications are user-scoped; only the recipient may delete their own notification.","triggerScenarios":"An authenticated request to delete a notification whose spec.recipient is a different user than the current principal; e.g. a user guesses/iterates another user's notification name and calls delete.","commonSituations":"Client passing the wrong notification name; a shared/migrated account; frontend caching a stale notification name; a permission boundary bug where a non-owner action is exposed in the UI.","solutions":["Ensure the client only issues deletes for notifications owned by the current user.","Verify the notification name corresponds to the recipient before calling delete.","If legitimate admin deletion is needed, use an admin-scoped API rather than the user endpoint.","Check that the authenticated principal matches the expected username."],"exampleFix":"// before\nnotificationService.deleteByName(currentUser, someName).block();\n\n// after\nNotification n = client.get(Notification.class, someName).block();\nif (!currentUser.equals(n.getSpec().getRecipient())) {\n    return Mono.error(new AccessDeniedException(\"Not owner\"));\n}\nnotificationService.deleteByName(currentUser, someName).block();","handlingStrategy":"validation","validationCode":"Notification n = client.get(Notification.class, name).block();\nif (n == null || !currentUser.equals(n.getSpec().getRecipient())) {\n    return Mono.error(new AccessDeniedException(\"Not the recipient\"));\n}","typeGuard":"static boolean isOwner(Notification n, String user) {\n    return n != null && n.getSpec() != null && user.equals(n.getSpec().getRecipient());\n}","tryCatchPattern":"notificationService.deleteByName(currentUser, name)\n    .onErrorResume(AccessDeniedException.class, e -> ServerResponse.status(HttpStatus.FORBIDDEN).build());","preventionTips":["Drive deletes from lists scoped to the current user only.","Do not expose another user's notification names in the UI.","Treat AccessDeniedException as 403 and never leak which name was attempted."],"tags":["security","authorization","notification","permission","access-denied"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}