Repository navigation
Description
Description
(real bug, slop report: close if you don't like it)
After upgrading HedgeDoc from 1.12.0 to 1.12.2, many existing notes become inaccessible with 403 Forbidden.
One affected note was checked directly in PostgreSQL:
- The note still exists.
- Its permission is
limited. - Access through the browser returns 403 despite being logged in.
This appears to be an application permission-check regression, not data loss. PostgreSQL is the database used by the affected instance, but the identified code issue does not appear database-specific.
Root cause
Commit 3c39d81 (fix: permissions check) replaced the HTTP-specific permission check in lib/web/note/util.js with the shared checkViewPermission() function in lib/permissions.js.
The shared function was extracted from the realtime code, but HTTP and realtime callers use different representations of authentication and ownership.
1. Authentication representation mismatch
For limited and protected notes, the shared function checks:
return req.user && req.user.logged_inHowever, the HTTP Passport deserializer in lib/web/auth/index.js returns the database user object without setting logged_in. HTTP authentication was previously checked with:
req.isAuthenticated()Therefore, the new check rejects authenticated HTTP requests.
2. Ownership representation mismatch
For private notes, the shared function checks:
return req.user &&
req.user.logged_in &&
req.user.id === note.ownerIn the realtime note representation, note.owner is the owner UUID, populated from the database’s note.ownerId.
However, lib/web/note/util.js passes a Sequelize note instance. On that object:
- The owner UUID is
note.ownerId. note.owneris either absent or an associated user object, depending on the query.
Thus, merely fixing the login check would not fix private-note access: the ownership comparison is also incorrect for HTTP callers.
Scope
| Permission | Effect of the regression on HTTP access |
|---|---|
freely |
Unaffected |
editable |
Unaffected |
locked |
Unaffected |
limited |
Authenticated users incorrectly denied |
protected |
Authenticated users incorrectly denied |
private |
Owners incorrectly denied |
The production example confirms the limited case. The other affected cases follow from code inspection and an isolated execution of the shared permission function with HTTP-shaped objects.
Proposed fix
Make permission checks use the appropriate authentication and ownership representations for each caller.
A narrowly scoped fix is to restore an HTTP-specific permission check in lib/web/note/util.js, while preserving the security change that denies invalid permissions:
exports.checkViewPermission = function (req, note) {
switch (note.permission) {
case 'private':
return req.isAuthenticated() && note.ownerId === req.user.id
case 'limited':
case 'protected':
return req.isAuthenticated()
case 'freely':
case 'editable':
case 'locked':
return true
default:
return false
}
}Then use this function in the HTTP findNote() path instead of calling the realtime-oriented shared check:
if (!exports.checkViewPermission(req, note)) {
return errors.errorForbidden(res)
}The realtime callers can retain their current check, which uses their existing logged_in and owner-UUID representations.
Alternatively, normalize authentication and ownership at each call site and pass those normalized values into a shared permission function.
Do not simply restore the previous permissive fallback, since rejecting invalid permissions was an intentional security fix. Also, replacing logged_in with req.isAuthenticated() in the shared function without adapting realtime callers could break the socket path.
Suggested regression tests
Test HTTP and realtime callers separately:
limitedandprotected: allow authenticated users; deny anonymous users.private: allow only the authenticated owner.privateHTTP notes: work both with and without an eager-loadedownerassociation.freely,editable, andlocked: remain publicly readable.- Invalid, missing, and null permissions: remain denied.
- Realtime permission enforcement: remains unchanged.
Steps to reproduce
- On HedgeDoc 1.12.0, log in and create a note.
- Set its permission to
limited. - Verify that it is readable while logged in.
- Upgrade to 1.12.2.
- Log in and open the same note.
- The HTTP request returns 403 Forbidden.
- Code inspection also indicates that
protectedandprivatenotes are affected.
Expected behaviour
The note remains readable by any authenticated user.
Logs
Config
Your Setup
- Host OS: [e.g. Ubuntu 20.04]
- NodeJS version: [run
node --version] - HedgeDoc version: (Click "Releases" in the footer)
Additional context
No response
Activity
Fix checkViewPermissions() in 1.12.2