nextcloud / nextcloud/desktop

Silent server-side DELETE when path contains a diacritic stored on the server in NFD form

Open
#10,087 3 comments 0 reactions 0 assignees View on GitHub
1. to develop bug feature: :file_folder: file provider os: :apple: macOS
Dominant language
C++
Stars
3.9k
Forks
1k
Avg merge
1d 17h
Merged PRs (30d)
123

Description

### ⚠️ Before submitting, please verify the following: ⚠️

- [x] This is a **bug**, not a question or a configuration issue.
- [x] This issue is **not** already reported on Github (I have searched for it).
- [x] Nextcloud Server and Desktop Client are **up to date**. See [Server Maintenance and Release Schedule](https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule) and [Desktop Releases](https://nextcloud.com/install/#install-clients) for supported versions.
- [x] I agree to follow Nextcloud's [Code of Conduct](https://nextcloud.com/contribute/code-of-conduct/)

### Bug description

> **Authorship & verification disclaimer.** This report was drafted by an AI assistant (Anthropic Claude) working from a customer debug-bundle, with iterative human direction and verification. I — Fokklz — reviewed each claim, ran the suggested commands, and pushed back where things didn't add up; however, my own background in Unicode normalisation, Swift `String` semantics, and Realm-core internals isn't deep enough for me to independently audit every assertion to the byte level. Treat this as a well-evidenced tip rather than a fully self-audited bug report. **I have the complete customer debug bundles, the per-device forensic markdown reports, and the agent-by-agent verification artefacts (including the Swift execution output, the realm-swift → realm-core source trace, the Docker reproduction transcripts, and the JSONL byte-level verification) and am happy to share them privately as a Git repository on request** — please reach out on Discord (`@Fokklz`) or by email (`chat@fokklz.dev`). I'd rather not publish the raw bundles publicly because they contain server URLs and account identifiers that are obfuscated in this issue but present verbatim in the logs.

## TL;DR

On macOS, the file-provider extension's Realm cache is queried with a byte-level `==` predicate that does not Unicode-normalise. When `NSFileProviderManager` hands the FPE an NFD-form URL for a path the server stores in NFC (or vice versa), the Realm lookup at `FilesDatabaseManager.swift:179` misses, the FPE treats the item as new/conflicted, and the downstream conflict-resolution path issues a server-side `DELETE`. The main desktop log shows zero entries at Qt severity `error|critical|fatal`. The C++ sync engine that runs on Windows/Linux ships `QString::NormalizationForm_C` defences at six boundaries; the Swift FPE that runs on macOS-in-FPE-mode (the default and only supported mode on modern macOS) ships zero.

## Affected versions

- **Nextcloud Desktop**: 33.0.2 (macOS, cocoa), git revision `54f537e35f8bea165d396bdfb55ab0c02408755c`, Qt 6.10.2.
- **NextcloudKit**: 7.2.6 (pinned by `Package.resolved`).
- **macOS**: reproduced on Tahoe 26.2 and 26.4; also seen on Sequoia 15.
- **Server**: any (the bug is in the client). Customer was on Nextcloud Enterprise 32.0.6.1.
- **Auth**: webflow / Microsoft-federated OIDC; reproduced on 4 distinct accounts on the same server.

## Severity

**Critical — silent data loss.** No user-facing surfacing of the deletion.

## Confirmed deletions (anonymised)

Files DELETEd from the server, confirmed via the FPE's JSONL log. Both rows below were byte-verified — the URL fields in the JSONL alternated between NFD (`pre\xcc\x82t`) and NFC (`pr\xc3\xaat`) enumerator events in the minute before the DELETE fired.

Account | Deleted item | DELETE timestamp (CEST) | Confirmed NFD/NFC alternation in logs?
-- | -- | -- | --
A | …/MAMCO/AMRLEDER_KBCB_fiche_prêt_1sign_SignPB[47].pdf | 2026-04-20 10:20:54 | yes — 3 NFD events (pre\xcc\x82t), 11 NFC events (pr\xc3\xaat) for the same ocId 02208123oczue8n54sn9 within 70 s, then DELETE
A | …/MAMCO/AMRLEDER_KBCB_fiche_prêt_1sign_SignPB[75].pdf | 2026-04-20 10:20:50 | partial — captured logs show NFC enumerator events; NFD form present in upload-time message body
A | …/Constat MAMCO/Arrival/CR_Pasquart_Muster.docx | 2026-04-20 10:15:56 | no — pure ASCII filename. Listed here because it was also DELETEd in the same window, but it is NOT evidence for the NFC/NFD mechanism.
B | …/AMRLEDER_KBCB_fiche_prêt_1sign_SignPB.pdf | 2026-04-21 09:18:06 | yes — 2 NFD events, ≥6 NFC events for ocId 02209281oczue8n54sn9 within 51 s, then DELETE

## Verbatim log evidence — byte-level

Bundle `selma_pc/all_synced_but_missing_files/File Provider Domains//Logs/2026-04-20_09-05-06 (1332).jsonl`. The relevant lines (JSONL line numbers, timestamps verbatim, URL byte sequences verified via Python `repr().encode()`):

**Line 650** at `2026.04.20 10:19:44.436` — `Set up enumerator.` for ocId `02208123oczue8n54sn9`, URL bytes contain `pre\xcc\x82t` (NFD: `p r e U+0302 t`).

**Line 664** at `2026.04.20 10:19:58.328` (14 s later) — `Set up enumerator.` for the same ocId, URL bytes now contain `pr\xc3\xaat` (NFC: `p r U+00EA t`).

Lines 665, 683, 689, 692, 693, 716, 717 — further enumerator setups, all NFC, over the next 70 s.

**Line 720** at `2026.04.20 10:20:54.448`:

```json
{"category":"Item","date":"2026.04.20 10:20:54.448","details":{"item":"02208123oczue8n54sn9","url":"…/SignPB[47].pdf"},"level":"info","message":"Successfully deleted item."}
```

**Line 724** at `2026.04.20 10:20:55.442` — `Read of URL did fail.` with `NextcloudKit.NKError code 1` for the same path (the file is no longer on the server; PROPFIND 404s).

Same pattern (NFD enumerator events at upload time → NFC enumerator events → single DELETE) confirmed for `SignPB.pdf` in bundle `collection_pc/no_sync_pre_reset/File Provider Domains//Logs/2026-04-21_09-04-07 (1234).jsonl`.

Throughout both incidents the **main desktop log** (`Nextcloud.log.0`) shows zero entries at Qt severity `error|critical|fatal`:

```sh
$ grep -oE '\[ (debug|info|warning|critical|error|fatal) ' nextcloud.log.0 | sort | uniq -c
19865 [ info
192 [ warning
22 [ debug
```
(Detailed byte-level forensic report at `_VERIFICATION_jsonl.md` in the source bundle.)

### Steps to reproduce

## Reproducer (best-effort from observed evidence)

1. macOS 14+/15/26, Nextcloud Desktop 33.x in file-provider mode (default on macOS).
2. Server contains at least one file at a path whose name has an NFD-encoded diacritic. Engineer it by uploading via a Mac shell:
```sh
curl -u user:pass -T file.pdf "https://server/remote.php/dav/files/user/$(printf 'pre\xcc\x82t.pdf')"
```
3. Mount the account in Finder; navigate to the parent directory so the FPE enumerates it.
4. Trigger one or more re-enumerations (view-mode switch in Finder, thumbnail generation, Spotlight).
5. Watch `~/Library/Group Containers/.com.nextcloud.desktopclient/File Provider Domains//Logs/.jsonl` for `Successfully deleted item.` followed by PROPFIND 404s on the original URL.

(Source of the path: `FileManager+FileProviderDomainLogDirectory.swift:23-26` + `FileManager+applicationGroupContainer.swift:25`.)

### Expected behavior

Code archaeology — the defence exists but does not apply on macOS-in-FPE-mode


Important framing: the 33.x macOS client ships two parallel implementations. The C++ sync engine under src/ is what runs on Windows, on Linux, and on macOS in legacy (non-file-provider) mode. The Swift File Provider Extension under shell_integration/MacOSX/ is what runs on macOS in file-provider mode — the default and only supported mode on modern macOS. The C++ engine has Unicode-normalisation defences and always has; the Swift extension does not. On a macOS 33.x install with the file provider active, the C++ defences are dead code; the Swift extension owns every byte the server sees.


The C++ tree (verbatim in v4.0.5 and still present in v33.0.2 — these are NOT what runs on macOS-in-FPE-mode):

File:line | Boundary normalised
-- | --
src/libsync/owncloudpropagator.cpp:813 | case-clash detection. Comment: // Need to normalize to composited form because of QTBUG-39622/QTBUG-55896
src/common/syncjournaldb.cpp:1007 | every name written to the sync journal DB on macOS
src/gui/folderwatcher_mac.cpp:79 | every macOS FSEvents path
src/gui/folder.cpp:222 | sync folder canonical path (comment: // Workaround QTBUG-55896)
src/gui/socketapi/socketapi.cpp:376 | every line from the socket API
src/gui/macOS/fileprovidersocketcontroller.cpp:58 | every line from the legacy file-provider socket controller

The Swift File Provider tree:


$ grep -rEn 'precomposed|decomposed|NFC|NFD|canonicalMapping|Normalization' \

shell_integration/MacOSX/NextcloudFileProviderKit/ \
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/ \
--include='*.swift'
(no matches)

(The Objective-C FinderSync.m does call decomposedStringWithCanonicalMapping, but that's a separate overlay-badge subsystem and uses NFD on purpose because Finder hands it NFD paths. It does NOT touch sync data.)

### Which files are affected by this bug

…/MAMCO/AMRLEDER_KBCB_fiche_prêt_1sign_SignPB[47].pdf

### Operating system

macOS

### Which version of the operating system you are running.

Tahoe 26.2 and 26.4

### Installation method

Other

### Nextcloud Server version

Nextcloud Enterprise 32.0.6.1

### Nextcloud Desktop Client version

33.0.2 (macOS, cocoa), git revision `54f537e35f8bea165d396bdfb55ab0c02408755c`, Qt 6.10.2.

### Did this occur after an update or on a clean installation?

Major version update (i.e. 4.0.0 → 33.0.0)

### Are you using the Nextcloud Server Encryption module?

No

### Are you using an external user-backend?

- [ ] Default internal user-backend
- [ ] LDAP or Active Directory
- [x] SSO - SAML
- [ ] Other

### Nextcloud Server logs

```shell
The affected server is a Hetzner-managed Nextcloud Enterprise tenant; I do not have access to its server-side logs (PHP error log, Apache/nginx access log, audit log, `oc_files_*` table dumps). All evidence in this report is from the macOS client side: the file-provider extension's JSONL logs, the main desktop log, the Realm cache files, the local sync-folder state, and static analysis of the published client + server source.
```

### Additional info

## Latent NFD-in-Realm on a healthy device

`kvm_pc` (a customer device that's working fine) has a Realm whose `strings` dump contains both NFC and NFD spellings of `könnten` in the same folder name (`25.04. Wenn Stoffe sprechen könnten`). This shows that NFD strings can enter the Realm even without an overt failure; the conflict-resolution path doesn't fire on this device — yet.

## Open questions (not yet answered by available evidence)

1. **The exact downstream code path** that turns a Realm lookup miss into a server `DELETE`. The JSONL log confirms (a) repeated NFC/NFD enumerator alternation, (b) eventual `Successfully deleted item.` from `Item+Delete.swift`, but the *decision* to delete is made somewhere between the enumerator and `Item+Delete.swift` and a maintainer with FPE source access can trace it directly.
2. **The `CR_Pasquart_Muster.docx` deletion** (pure ASCII, no NFC/NFD ambiguity) which occurred in the same minute and same account — listed in the deletions table for transparency, but cannot be attributed to NFC/NFD. It may share a different trigger; suggests there's a secondary issue not characterised by this report.

## Cross-links

- nextcloud/desktop#8756 (`macOS VFS: Nextcloud keeps adding files to trash bin after emptying bin`) — symptoms consistent with a shared root cause.
- nextcloud/desktop#8010, #9417 — other macOS silent-delete reports; same family.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.