Skip to content

fix: keep server-managed names out of reach of DAV clients (#38) - #41

Merged
gnacho merged 1 commit into
mainfrom
fix/38-reserved-resource-names
Oct 18, 2026
Merged

gnacho merged 1 commit into
mainfrom
fix/38-reserved-resource-names

Conversation

@gnacho

@gnacho gnacho commented Oct 18, 2026

Copy link
Copy Markdown
Owner

What was wrong

A DAV client could delete or overwrite a collection's own metadata file with a
single request. Verified against a local build before the fix:

  • DELETE /carddav/alice/def-addressbook/.Radicale.props -> 204, the file
    disappears from disk, the collection then answers 404, the item inside it
    answers 404, the collection vanishes from the home listing and
    MKCALENDAR on the same href answers 405. There is no way back over DAV.
  • PUT /caldav/alice/def-calendar/.Radicale.props with a valid iCalendar whose
    UID is .Radicale.props -> 201, metadata replaced by iCalendar bytes,
    collection gone.
  • Reproduced on a client-created collection too, and for any hidden name
    (.hidden.ics was accepted and listed).

What this changes

  • internal/store: new cleanName, used by every collection and object entry
    point (GetCollection, CreateCollection, DeleteCollection,
    UpdateCollectionProps, collDir, ReadItem, WriteItem, DeleteItem,
    ItemModTime). It rejects the names the server keeps for itself: a leading
    dot (.Radicale.props, the .tmp-* files of writeFileAtomic) or a trailing
    tilde. ListItems now skips those names instead of only .Radicale.props.
    This is the same rule Radicale applies through
    pathutils.is_safe_filesystem_path_component before it touches the
    filesystem (upload.py:40, delete.py:47, get.py:49,153 of v3.5.3).
  • internal/dav: reserved names are invisible in resolve (404, like any name
    that is not servable) and refused in resolveForWrite and resolveForCreate
    with 400, which is what Radicale answers to a PUT with such an href.
  • README (EN and ES): the reservation is now documented.

Tests

  • internal/store: TestReservedNamesAreRefused covers read, write, delete,
    collection lookup and creation, that the metadata file survives a delete
    attempt, and that the listing hides hidden names.
  • cmd/server: TestServerManagedNamesAreUnreachable drives the real handler
    over HTTP: PUT 400, DELETE 404, GET 404, hidden item 400, reserved collection
    400, metadata untouched and the collection still answering 207.

Also checked end to end against a copy of the production volume (temporary
directory, production untouched): the real item keeps being served, the attack
answers 404/404/400, an ordinary PUT/DELETE still answers 201/204, and the
inherited .Radicale.cache directories do not leak into listings.

Closes #38

@gnacho
gnacho merged commit 309f753 into main Oct 18, 2026
4 checks passed
@gnacho
gnacho deleted the fix/38-reserved-resource-names branch October 18, 2026 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reject DAV writes and deletes of server-managed files (.Radicale.props)

1 participant