[Bug]: Creating/renaming a system tag with a multibyte name >64 bytes throws 500 — byte-based substr() splits UTF-8 characters in SystemTagManager
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 36.9k
- Forks
- 5.2k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 713
Description
⚠️ This issue respects the following points: ⚠️
- This is not a troubleshooting question, general support matter, or webserver/proxy problem, but likely a bug (if unsure, ask the Community Help Forum).
- This issue is not already reported on Github OR solved at the Community Help Forum (I've searched!).
- I'm using a maintained major version of Nextcloud Server and tested against the latest patch level. (Supported major versions and current patch levels).
- I agree to follow Nextcloud's Code of Conduct.
- I've tried my best to provide clear reproduction steps that someone unfamiliar with this bug could use to reproduce it.
Bug description
OC\SystemTag\SystemTagManager truncates tag names to 64 bytes using the
byte-based substr($tagName, 0, 64). When the tag name contains multibyte
UTF-8 characters (e.g. Cyrillic, 2 bytes per character) and the 64th byte
falls inside a character, the truncated string ends with a dangling lead
byte (e.g. \xD0) — invalid UTF-8. MariaDB/MySQL in strict mode then rejects
the INSERT with error 1366, which surfaces to the user as an HTTP 500 on
POST /remote.php/dav/systemtags from the web UI.
There are three affected call sites (line numbers from v34.0.3):
| Line | Method | Code |
|---|---|---|
| 139 | getTag() |
$truncatedTagName = substr($tagName, 0, 64); |
| 184 | createTag() |
$truncatedTagName = substr($tagName, 0, 64); |
| 249 | updateTag() |
$truncatedNewName = substr($newName, 0, 64); |
Two distinct user-visible consequences:
- Hard failure (500): when the byte-64 cut splits a multibyte character
→ SQLSTATE 22007 / MySQL error 1366, unhandled, returned as 500. Any
Cyrillic tag name of 33+ characters can trigger this. Users see a generic
error and typically retry several times (visible in our access logs). - Silent corruption: when the cut happens to land on a character
boundary, a mangled, mid-word-truncated tag is created without any warning
(real examples from our instance:8 ерийalongside the intended
8 серий;Для платформ, триллер, черная комед). This also creates
near-duplicate tags that fragment file/tag associations.
Root cause:
substr() counts bytes and cannot know about UTF-8 character boundaries.
Since the limit here is a byte budget (the column is varchar(64)),
the correct function is mb_strcut(), which enforces the same byte limit
but backs off to the nearest character boundary and never emits an invalid
sequence.
Note: mb_substr() is not a correct replacement — it counts characters,
so 64 Cyrillic characters would be 128 bytes and overflow the column.
Proposed fix:
Replace all three occurrences:
// getTag(), createTag():
$truncatedTagName = mb_strcut($tagName, 0, 64, 'UTF-8');
// updateTag():
$truncatedNewName = mb_strcut($newName, 0, 64, 'UTF-8');
We have been running this patch in production since 2026-08-27: the 500 is
gone, over-long names are truncated at a clean character boundary, and
ASCII-only behavior is unchanged (mb_strcut is byte-identical to substr
for single-byte input). Happy to submit this as a PR if the approach is
acceptable.
A possible follow-up (out of scope for this fix): silently truncating user
input at all is questionable — returning a proper 400 with a meaningful
message, or letting the WebDAV endpoint reject over-long names, might be
preferable UX. The mb_strcut change is the minimal correctness fix either
way.
Steps to reproduce
- Use MySQL/MariaDB with strict mode (default) and utf8mb4 (default;
oc_systemtagisutf8mb4_bin— charset configuration is not the
issue, the payload is genuinely invalid UTF-8 after truncation). - Run:
# 63 ASCII bytes + one 2-byte Cyrillic letter => byte 64 splits "б"
sudo -u www-data php occ tag:add "$(python3 -c 'print("a"*63 + "б")')" public
Equivalently via the web UI: create a tag whose name is 63 a characters
followed by б (or any Cyrillic name where character 33 starts at
byte 64, e.g. many real-world descriptive Cyrillic tag names).
Expected behavior
Tag is created with the name truncated at a character boundary (≤64 bytes,
no split character) — or, arguably better, the request is rejected with a
clear validation error. In no case a 500.
Actual behavior:
An exception occurred while executing a query:
SQLSTATE[22007]: Invalid datetime format: 1366
Incorrect string value: '\xD0' for column <db>.oc_systemtag.name at row 1
`occ tag:add` throws the exception; the web UI request
`POST /remote.php/dav/systemtags` returns HTTP 500.
### Nextcloud Server version
34
### Operating system
Debian/Ubuntu
### PHP engine version
PHP 8.3
### Web server
Apache (supported)
### Database engine version
MariaDB
### Is this bug present after an update or on a fresh install?
Updated from a MINOR version (ex. 32.0.1 to 32.0.2)
### Are you using the Nextcloud Server Encryption module?
None
### What user-backends are you using?
- [x] Default user-backend _(database)_
- [ ] LDAP/ Active Directory
- [ ] SSO - SAML
- [ ] Other
### Configuration report
```json
{
"system": {
"serverid": "1",
"instanceid": "***REMOVED SENSITIVE VALUE***",
"passwordsalt": "***REMOVED SENSITIVE VALUE***",
"secret": "***REMOVED SENSITIVE VALUE***",
"allow_local_remote_servers": true,
"trusted_domains": [
"10.0.5.79",
"cd.m-pr.tv",
"localhost",
"10.0.4.72",
"localhost",
"talk.m-pr.tv"
],
"push_server_url": "https:\/\/cd.m-pr.tv\/push",
"datadirectory": "***REMOVED SENSITIVE VALUE***",
"dbtype": "mysql",
"version": "34.0.3.2",
"overwrite.cli.url": "https:\/\/cd.m-pr.tv",
"dbname": "***REMOVED SENSITIVE VALUE***",
"dbhost": "***REMOVED SENSITIVE VALUE***",
"dbport": "",
"dbtableprefix": "oc_",
"mysql.utf8mb4": true,
"dbuser": "***REMOVED SENSITIVE VALUE***",
"dbpassword": "***REMOVED SENSITIVE VALUE***",
"installed": true,
"maintenance": false,
"memcache.local": "\\OC\\Memcache\\APCu",
"memcache.distributed": "\\OC\\Memcache\\Redis",
"redis": {
"host": "***REMOVED SENSITIVE VALUE***",
"port": 6379
},
"memcache.locking": "\\OC\\Memcache\\Redis",
"theme": "",
"loglevel": 2,
"overwriteprotocol": "https",
"mail_smtpmode": "smtp",
"mail_sendmailmode": "smtp",
"mail_from_address": "***REMOVED SENSITIVE VALUE***",
"mail_domain": "***REMOVED SENSITIVE VALUE***",
"mail_smtphost": "***REMOVED SENSITIVE VALUE***",
"mail_smtpport": "587",
"default_locale": "ru_RU",
"default_phone_region": "RU",
"app_install_overwrite": {
"0": "documentserver_community",
"2": "files_rightclick",
"3": "metadata",
"4": "files_archive"
},
"enable_previews": true,
"preview_libreoffice_path": "\/usr\/bin\/libreoffice",
"apps_paths": [
{
"path": "\/var\/www\/html\/nextcloud\/apps",
"url": "\/apps",
"writable": false
},
{
"path": "\/var\/www\/html\/nextcloud\/custom_apps",
"url": "\/custom_apps",
"writable": true
}
],
"mail_smtpauth": true,
"mail_smtpname": "***REMOVED SENSITIVE VALUE***",
"mail_smtppassword": "***REMOVED SENSITIVE VALUE***",
"maintenance_window_start": 22,
"collabora\/code": "https:\/\/cd.m-pr.tv",
"fileuploadnew.chunk_size": "1G",
"fileuploadnew.max_filesize": "25G",
"memory_limit": "26G",
"timeout": 36000,
"enable_push": true,
"has_internet_connection": true,
"notify_push_base_endpoint": "https:\/\/cd.m-pr.tv\/push",
"overwritewebroot": "",
"mail_smtpauthtype": "LOGIN",
"mail_smtpstreamoptions": {
"ssl": {
"allow_self_signed": false,
"verify_peer": true,
"verify_peer_name": true
}
},
"logtimezone": "Europe\/Moscow",
"mail_smtptimeout": 60,
"trusted_proxies": "***REMOVED SENSITIVE VALUE***",
"overwritehost": "cd.m-pr.tv",
"preview_imaginary_url": "***REMOVED SENSITIVE VALUE***",
"enabledPreviewProviders": [
"OC\\Preview\\Imaginary",
"OC\\Preview\\ImaginaryPDF",
"OC\\Preview\\TXT",
"OC\\Preview\\MarkDown",
"OC\\Preview\\OpenDocument",
"OC\\Preview\\MSOffice2003",
"OC\\Preview\\MSOfficeDoc",
"OC\\Preview\\MSOffice2007",
"OC\\Preview\\Photoshop",
"OC\\Preview\\Font",
"OC\\Preview\\MP3",
"OC\\Preview\\Movie"
],
"preview_max_x": 2048,
"preview_max_y": 2048,
"preview_concurrency_all": 8,
"preview_concurrency_new": 4
}
}
List of activated Apps
Enabled:
- activity: 7.0.0
- admin_audit: 1.24.0
- admincockpit: 1.3.9
- analytics: 6.8.0
- announcementbanner: 2.7.0
- appstore: 1.0.0
- bruteforcesettings: 7.0.0
- calendar: 6.5.4
- cloud_federation_api: 1.18.0
- comments: 1.24.0
- contacts: 8.7.7
- contactsinteraction: 1.15.0
- dashboard: 7.14.0
- dav: 1.40.0
- deck: 1.18.3
- external: 9.0.1
- federatedfilesharing: 1.24.0
- federation: 1.24.0
- files: 2.6.0
- files_accesscontrol: 5.0.0
- files_automatedtagging: 5.0.0
- files_downloadlimit: 5.2.0
- files_external: 1.26.0
- files_lock: 34.0.1
- files_pdfviewer: 7.0.0-dev.0
- files_reminders: 1.7.0
- files_retention: 5.0.0
- files_sharing: 1.26.0
- files_trashbin: 1.24.0
- files_versions: 1.27.0
- flow_notifications: 5.0.0
- forms: 5.3.5
- guests: 4.9.0
- logcleaner: 1.6.3
- logreader: 7.0.0
- lookup_server_connector: 1.22.0
- nextcloud_announcements: 6.0.0
- notes: 6.0.2
- notifications: 7.0.0-dev.1
- notify_push: 1.4.0
- oauth2: 1.22.0
- onlyoffice: 10.1.2
- password_policy: 6.0.0-dev.0
- previewgenerator: 5.14.0
- privacy: 6.0.0-dev.1
- profile: 1.3.0
- provisioning_api: 1.24.0
- recommendations: 7.0.0
- related_resources: 5.0.0-dev.0
- serverinfo: 6.0.0
- settings: 1.17.0
- sharebymail: 1.24.0
- spreed: 24.0.4
- support: 6.0.0
- survey_client: 6.0.0-dev.0
- systemtags: 1.24.0
- tables: 2.3.0
- tasks: 0.18.1
- text: 8.0.0
- theming: 2.9.0
- thesearchpage: 2.4.5
- twofactor_backupcodes: 1.23.0
- twofactor_totp: 16.0.0
- updatenotification: 1.24.0
- user_status: 1.14.0
- viewer: 7.0.0-dev.0
- weather_status: 1.14.0
- webhook_listeners: 1.6.0
- workflow_script: 5.0.0
- workflowengine: 2.16.0
Disabled:
- app_api: 34.0.0 (installed 32.0.0)
- circles: 34.0.0 (installed 28.0.0)
- encryption: 2.22.0
- files_archive: 1.2.8 (installed 1.2.8)
- files_zip: 3.0.0 (installed 3.0.0)
- firstrunwizard: 7.0.0-dev.0 (installed 2.18.0)
- localpdf: 1.0.0 (installed 1.0.0)
- metadata: 0.24.0 (installed 0.24.0)
- office: 1.0.0 (installed 1.0.0)
- photos: 7.0.0 (installed 2.4.0)
- side_menu: 6.0.1 (installed 6.0.1)
- socialsharing_email: 4.1.0 (installed 4.1.0)
- socialsharing_telegram: 4.1.0 (installed 4.1.0)
- suspicious_login: 12.0.0-dev.0
- testing: 1.23.0
- twofactor_nextcloud_notification: 8.0.0
- user_ldap: 1.25.0
Nextcloud Signing status
Nextcloud Logs
### Nextcloud Logs
{"Exception":"OC\\DB\\Exceptions\\DbalException","Message":"An exception occurred while executing a query: SQLSTATE[22007]: Invalid datetime format: 1366 Incorrect string value: '\\xD0' for column `CloudDB`.`oc_systemtag`.`name` at row 1","Code":1366,"Trace":"#8 /var/www/html/nextcloud/lib/private/SystemTag/SystemTagManager.php(195): OC\\DB\\QueryBuilder\\QueryBuilder->executeStatement() #9 /var/www/html/nextcloud/apps/dav/lib/SystemTag/SystemTagPlugin.php(193): OC\\SystemTag\\SystemTagManager->createTag() #10 /var/www/html/nextcloud/apps/dav/lib/SystemTag/SystemTagPlugin.php(122): OCA\\DAV\\SystemTag\\SystemTagPlugin->createTag() #11 .../sabre/event/lib/WildcardEmitterTrait.php(89): OCA\\DAV\\SystemTag\\SystemTagPlugin->httpPost() ... #16 /var/www/html/nextcloud/remote.php(152)","CustomMessage":"Uncaught exception"}
Additional info
Additional info
- The misleading "Invalid datetime format" prefix is just Doctrine's generic
label for SQLSTATE class 22007; the actual error is MySQL 1366
(invalid string value). - PostgreSQL and SQLite users are likely affected differently (PostgreSQL
will also reject invalid UTF-8; SQLite may silently store the broken
bytes), but I have only verified MariaDB. - The silent-corruption variant (cut lands on a character boundary) means
instances that have had long multibyte tag names for a while probably
already contain mangled duplicate tags inoc_systemtag.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating OC\SystemTag\SystemTagManager and inspect getTag(), createTag(), and updateTag(), where the issue identifies the three byte truncation call sites. Run the provided occ tag:add reproduction with 63 ASCII characters followed by a Cyrillic character, then verify that long names end at a character boundary, remain within 64 bytes, and no longer produce HTTP 500 or invalid UTF-8 database errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- mariadb, php
- Domain
- backend, database
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 75/100