8000 Regression: 1.12.2 returns 403 for authenticated users accessing limited/protected notes; private notes also affected · Issue #6946 · hedgedoc/hedgedoc · GitHub
[go: up one dir, main page]

Skip to content

Regression: 1.12.2 returns 403 for authenticated users accessing limited/protected notes; private notes also affected #6946

Open

Description

@inducer

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_in

However, 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.owner

In 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.owner is 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:

  • limited and protected: allow authenticated users; deny anonymous users.
  • private: allow only the authenticated owner.
  • private HTTP notes: work both with and without an eager-loaded owner association.
  • freely, editable, and locked: remain publicly readable.
  • Invalid, missing, and null permissions: remain denied.
  • Realtime permission enforcement: remains unchanged.

Steps to reproduce

  1. On HedgeDoc 1.12.0, log in and create a note.
  2. Set its permission to limited.
  3. Verify that it is readable while logged in.
  4. Upgrade to 1.12.2.
  5. Log in and open the same note.
  6. The HTTP request returns 403 Forbidden.
  7. Code inspection also indicates that protected and private notes 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

  1. added a commit that references this issue on Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0