Silent server-side DELETE when path contains a diacritic stored on the server in NFD form
- 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
Assessment
This issue has not been assessed yet.