8000 Missing note-permission check in the Explore pin feature exposes private note metadata (title, tags, owner, activity) to any registered user, persistently · Issue #6950 · hedgedoc/hedgedoc · GitHub
[go: up one dir, main page]

Skip to content

Missing note-permission check in the Explore pin feature exposes private note metadata (title, tags, owner, activity) to any registered user, persistently #6950

Open

Description

@leograntquinn-crypto

Originally reported as security advisory GHSA-5542-9jq2-frq4; re-filing as a regular issue as requested there (per the security policy for the 2.0 development branch).

Affected: HedgeDoc 2.0 development codebase, 2.0.0-alpha.3 / commit c937fd2.

Summary

The Explore "pin" feature performs no note-permission check at any point. Any registered user can pin any note by its alias — including private notes they have never had access to — and the API immediately returns, and keeps returning, the note's live metadata: current title and tags (taken from the note's latest revision), primary alias, type, owner's username, and last-changed timestamp. Because pinned entries are always rendered from the latest revision, this is a persistent monitoring channel for the activity of private notes, not a one-time snapshot.

Details

  • backend/src/api/private/explore/explore.controller.ts is guarded only by SessionGuard (login). PUT explore/pin/:noteAlias (lines 107–127) resolves any note via noteService.getNoteIdByAlias() and calls setNotePinStatus without ever determining the requester's permission on the note; the resulting entry is returned in the response.
  • backend/src/explore/explore.service.ts:
    • setNotePinStatus (lines 454–481) inserts the (userId, noteId) pin and returns the note's explore entry — no permission check.
    • getMyPinnedNoteExploreEntries (lines 190–201), backing GET explore/pinned, filters only by the requester's userId.
    • The DTOs are built by transformQueryResultIntoDtos (lines 304–331) from the latest revision (title, tags) plus primary alias, type, owner username and lastChangedAt.
  • That this is an omission rather than a design decision is visible in the same file: the recently-visited path, checkPermissionsAndCleanUpRecentlyVisitedNotes (lines 271–296), re-checks determinePermission for every entry and deletes rows for notes the user can no longer access. No equivalent check or cleanup exists for pins (the pinned-notes table is written only by explore.service, and revocation never removes pins).

Attack scenarios

  • A — never had access: register an account → PUT /api/private/explore/pin/<alias of a private note> with isPinned=true → the response already contains the private note's title, tags, owner and last-changed time; GET /api/private/explore/pinned continues to return its up-to-date metadata indefinitely.
  • B — access revoked: a user who once could read a note and pinned it keeps receiving its current title/tags/activity after the owner revokes their access.

No note content is exposed — the impact is metadata disclosure (existence, titles, tags, ownership and activity timing of private notes), which for private notes is itself sensitive.

Suggested fix

Check determinePermission for the target note in setNotePinStatus (reject pinning notes the requester cannot read), and re-validate permissions when serving pinned entries — mirroring the existing recently-visited cleanup in the same service.

Activity

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