VIEWER API key with only READ_ANY can execute notification mutations
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 113
- Forks
- 22
- Avg merge
- 10h 40m
- Merged PRs (30d)
- 13
Description
Environment
Unraid OS Version: 7.2.4
Unraid API: 4.29.2+c39b0b26
Authentication: API key, role VIEWER, granular permissions, READ_ANY only
Transport: local GraphQL endpoint
Are you using a reverse proxy? No — tested against the server directly.
No hostnames, addresses, API-key values, or registration data are included.
Pre-submission Checklist
- I have verified that my Unraid OS is up to date
- I have tested this issue by accessing my server directly (not through a reverse proxy)
- This is not an Unraid Connect related issue
Issue Description
An API key holding role VIEWER and only READ_ANY permissions can successfully
execute notification mutations. Every other resource correctly rejects mutations
for the same key, so this looks like a missing authorization guard on the notification
mutation resolvers rather than a problem with the permission model itself.
Key scope, as reported by the API when queried with the key under test:
query { me { roles permissions { resource actions } } }
roles: ["VIEWER"]
permissions: 17 resources, every one ["READ_ANY"]
ARRAY, DISK, DOCKER, LOGS, VARS, NETWORK, INFO, NOTIFICATIONS,
SERVICES, SHARE, VMS, OS, ONLINE, REGISTRATION, FLASH, DISPLAY, CONFIG
No CREATE_*, UPDATE_*, or DELETE_* action is granted on any resource. The
NOTIFICATIONS entry is exactly {"resource": "NOTIFICATIONS", "actions": ["READ_ANY"]}.
Steps to Reproduce
-
Create an API key with role
VIEWERand granular permissions including
NOTIFICATIONS:READ_ANY. Grant no write action on any resource. -
Confirm the key's scope contains only
READ_ANYentries:query { me { roles permissions { resource actions } } } -
Issue a notification mutation with that key:
mutation { createNotification(input: { title: "scope-test" subject: "scope-test" description: "permission probe" importance: INFO }) { id } }HTTP 200:
{ "data": { "createNotification": { "id": "..." } } }The notification is created and appears in the WebGUI.
-
Contrast with a correctly-guarded mutation using the same key:
mutation { array { setState(input: { desiredState: STOP }) { state } } }{ "errors": [ { "message": "Forbidden resource", "extensions": { "code": "FORBIDDEN", "originalError": { "statusCode": 403 } } } ], "data": null }
Expected Behavior
All mutations are rejected with Forbidden resource, consistent with a key that holds
only READ_ANY.
Actual Behavior
Notification mutations succeed. Mutations on every other resource tested are correctly
refused.
| Mutation | Expected | Actual |
|---|---|---|
createNotification |
Forbidden | Succeeds |
archiveAll |
Forbidden | Succeeds |
deleteArchivedNotifications |
Forbidden | Succeeds |
array { setState } |
Forbidden | Forbidden (correct) |
docker { start } |
Forbidden | Forbidden (correct) |
vm { start } |
Forbidden | Forbidden (correct) |
parityCheck { start } |
Forbidden | Forbidden (correct) |
apiKey { create } |
Forbidden | Forbidden (correct) |
updateSettings |
Forbidden | Forbidden (correct) |
Results were reproduced across two separate runs.
Impact
This breaks the security guarantee of the VIEWER role. A key documented and intended
as read-only can write to the notification subsystem.
The deletion path is the more meaningful half. deleteArchivedNotifications and
archiveAll let a read-only key destroy or hide notification history, including
warnings a user has not yet seen (SMART warnings, parity errors). That reads as an
alert-suppression / audit-integrity issue rather than a cosmetic one.
This is becoming more relevant as third-party integrations standardise on VIEWER
keys for monitoring — Home Assistant bridges, MCP servers, dashboards. Users granting
a VIEWER key to an external tool reasonably expect that tool cannot mutate anything.
Suggested Fix
Apply the authorization guard already used by the array / docker / vm / settings
mutation resolvers to the notification mutation resolvers, requiring an appropriate
write action (NOTIFICATIONS:CREATE_ANY / UPDATE_ANY / DELETE_ANY) rather than
being satisfied by READ_ANY.
Additional Context
Found while configuring a read-only integration against the GraphQL API. Any test
notification created during verification was archived and deleted afterwards; no other
server state was modified.
Possibly related, same authorization subsystem: #2060.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating the notification mutation resolvers and compare their authorization handling with the array, Docker, VM, and settings mutation resolvers mentioned in the report; review related authorization work in issue #2060. Reproduce the listed GraphQL mutations with a VIEWER key, then verify that createNotification, archiveAll, and deleteArchivedNotifications reject READ_ANY-only access while permitted reads still work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, typescript
- Domain
- api, authorization, backend, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100