Skip to main content

S3 API - Releases

← App details

Nextcloud 35

S3 API 1.7.0
Release Details
UpdatedOct. 25, 2026, 6:20 a.m.
Changelog

Added

  • A folder picker in the personal settings. The folder path field has a Choose… button next to it that opens Nextcloud's file picker, restricted to folders: navigate into a folder and choose it. The home folder itself is not taken from the picker — a bucket on the whole home blocks every other one — so that one has to be typed as /. Typing a path by hand still works, and a path that does not exist is refused as before.
  • occ s3api:bucket:create pre-provisions bucket configs, for one or more users or for every member of a group, so an admin can roll the app out without each user clicking through the settings: bash occ s3api:bucket:create --user alice --folder /S3 --name files --create-folder occ s3api:bucket:create --group staff --folder /S3 --name files --create-folder --create-folder creates the folder in each user's home when it is missing; without it a missing folder is an error. No API keys are created — the users create their own on the bucket that then shows up in their settings.
  • Re-running is safe: a user whose bucket of that name already serves that folder is skipped. Any other clash — the name on a different folder (also after the user renamed the folder), or a folder inside or around another bucket — is an error for that user, and nothing is created for them, not even the folder.
  • An unknown user or group aborts the run before anything is created for anyone, and so does --folder /: a bucket on the whole home blocks every other bucket of that user, which for a group is a typo rather than a plan. Users who want it register it in their settings.
  • An incoming share at the path, or above it, is refused: whoever shares a folder named like the one picked for a group would otherwise receive everything its members upload. --allow-shared accepts it when that is the point.
  • A group covers its members at the time the command runs, so users who join later need another run. The exit code is non-zero when any user could not be provisioned.

Bucket configs created this way are logged like the ones from the settings page.

Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureT41gK7psK8uIR9HpVidmi0JvVveR2dX0lSrh/efxzU4XIfv6O+pbai9GMUDUK36UklkTI7dptddmqd84zVtMNPr64f5QcGRQsRNtEtbS+j920RoDBSacOZB79iSGM9y3BX5+jjSM666v5+pq20r0nWL7g8CUjPKqBDJuB+3K9DT6mmsW3gaYzoJkdU/S2kvrHzxo0wUpMP2ZWPuKRMhXEmdK3XhZJC5sYXIoinhWu/fuSpDb8Me5ZifbY1cVPxcIuaja3IxXSPa2YZRWZPWZwqjT5L8UClN93V9jZNZcDnRFktqN0I49uw4NEBk4K6+73VpQm+ZWcW4hxju/oiH6aEDccm9LmmfIPoUhhurQc2SoQMhB/wjOfEH5DipyUxkSsn84OxMTbZqthli3F0pbrEOAxadqFhJF0e4pc7NK6gfUT4e07jDvNi7THYLIOt8i0BrK+bfXUNDCUL1UBznScR35XIzyl9PL6kX92V44LOwg3EM5+A4s68OhUv49TsBg9PkM2HhMZM/ebsAtyoAxKr5Cnlt+j79HhNCkcnZVQM2eZtW0x9rSHAAyEFHLrqbhOoOMywRA2OYBDKZMwkeWIkSw9PGDphtvEtMZp2by+HM/xapYaGWUX/201T2JAB3uolGJk+UMNX95kO3YHIuy1vEi3SWcQ+MU9eysED2G8z0=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.6.2
Release Details
UpdatedOct. 24, 2026, 8:26 a.m.
Changelog

Hardening release. No new features: this works through a security review of 1.6.1 — no critical or high findings, a set of medium and low ones, all addressed. Updating is recommended. It carries one migration, which binds existing buckets to their folder's file id (see the first entry). Operator-visible: buckets on shares newer than the bucket stay unbound, DeleteObjects needs a signed checksum or payload hash, keys that are not NFC are refused, and uploads are bounded by 5 GiB and the owner's free space.

Security

  • A bucket was its folder's path, checked once at creation. Renamed, the folder stopped being served; whatever later appeared under the old name was served instead — a new folder, or a folder another user shared, which Nextcloud mounts under its own name. A key handed to a third party then read and wrote the other user's files. Buckets are now bound to the folder's file id and follow it through renames and moves; a folder in the trash answers NoSuchBucket. The migration binds existing buckets to what their path names now — except an incoming share that is newer than the bucket, since that is what the attack looks like once it has happened. Operator-visible: such a bucket, one on a federated share, and one whose folder cannot be found stay unbound with a warning in the log; binding is retried on first use by the same rule, otherwise the owner registers the folder again. Buckets on the owner's folders, group folders, external storage and on shares older than the bucket bind as before.
  • The XML bodies of DeleteObjects and CompleteMultipartUpload were not bound to the signature. They were read raw, without checking x-amz-content-sha256, so a captured request could be replayed within its fifteen minutes with another body: other keys deleted, another part list assembled. They are now read like uploads (decoded, chunk signatures checked) and checked against every claim the request makes about them — the payload hash, Content-MD5, and for DeleteObjects x-amz-checksum-* (CRC32, CRC32C, CRC64NVME, SHA-1, SHA-256). DeleteObjects also has to carry one the signature covers, as S3 requires a checksum there; a request with UNSIGNED-PAYLOAD and nothing else is refused with InvalidRequest. A checksum sent as an aws-chunked trailer (STREAMING-UNSIGNED-PAYLOAD-TRAILER) does not count, as trailers are not read; it takes a signed header or a literal payload hash.
  • Keys in another Unicode normalisation, or not UTF-8 at all, were accepted. Nextcloud stores names in NFC, so the NFD spelling of é was a second name for the same file: with a lock of its own, so two If-None-Match: * writes could both win; past the self-copy guard, which with file locking off truncated the object; past the bucket overlap check. Bytes that are not UTF-8 ended in a database error. Such keys, and keys with control characters, are refused with InvalidArgument. The overlap check compares folders by where they are now, NFC. Self-copy is detected by file id, which also covers case-insensitive storages.
  • Request bodies had no size limit of the app's own. A body went to the temp directory in full before the quota was looked at, so one upload could fill it for the whole instance; XML bodies were held in memory and parsed whatever their size. PutObject and UploadPart now take at most 5 GiB, as on S3, and no more than the bucket owner's free space (403 AccessDenied) — checked on the declared length before reading, and while reading. XML bodies stop at 4 MiB (MaxMessageLengthExceeded), CompleteMultipartUpload at 10000 parts. Completion checks the free space before assembling.
  • Listings hashed every file without a recorded ETag, in full. A readonly key could make one GET read a thousand large files. A page now hashes at most 256 MiB; past that, files are listed with Nextcloud's etag plus -0 until their content ETag is known — the suffix keeps tools from taking it for an MD5, which on some storages it looks like. A conditional write computes a missing ETag before taking the object lock, not while holding it.
  • ListParts re-read every part on each call, and CompleteMultipartUpload read them three times. Part ETags are recorded when the part is stored.
  • ListParts and ListMultipartUploads need a readwrite key.
  • The object lock is keyed on where the file really is (storage and path in it), not on the owner's view of it, so a writer coming through a share and one through the owner's own bucket now exclude each other.
  • A request URI that does not account for the routed path is refused (InvalidURI) instead of falling back to the routed path, which is the one that may have lost a slash.
  • XML responses replace what XML cannot carry instead of producing an unparseable document or an empty value.
  • Secrets are marked #[\SensitiveParameter], so a logged stack trace does not carry them. The settings page clears a new secret from the page when its dialog closes.

Changed

  • Operator-visible: a contended conditional write waits up to three seconds for the object lock, not ten, with backoff, before answering 503 SlowDown.
  • Failed signatures and unknown keys are logged as warnings with the key and address, and the brute force entry carries the key. Bucket and key changes and DeleteObjects are logged at info level and to admin_audit.
  • ListParts and ListMultipartUploads page (max-parts/part-number-marker, max-uploads/key-marker/upload-id-marker, prefix). At most 1000 uploads per bucket may be in flight; a bucket at that limit discards its expired uploads itself when the next one starts. Cleanup reclaims abandoned uploads 200 per run.
  • The settings API answers 400 instead of 500 to parameters of the wrong type and to labels over 255 characters.
  • Files whose names no key can spell are left out of listings.
  • README: trusted_proxies behind a reverse proxy, room for tempdirectory.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignaturefC64D77DhXVDBlfKpN1obqnKMaFIHWoLOlgIcui2mrlm4S7i1gb9KSMqoAAcWgsgYX4FhanvorI9fWfua1bR2Qn3j3LlgrPMUuaUnwMbSHJtOu9zL8BxFjxRIWGsHjFKtmAv6VORaE4hLkhmMlOqNNnXcs8/rK2BRBY3lryOz65e9WZY/5E0u4QK9cv3kfH+qk8IMWAsZZF0FY8hF1BX7I+k41i9oWhjQ8d3enaF2/4uSf/tSOdBrLvn9hSsPMkVYC/9HpxJZT2BrnpRgCEztXvsamXIvB88Ncty0oAaGa0I+VLT9EcT2CAHt487WM3B6Bz74092riF26AaEbfoLt3sZGBNEXY7DvQQ0FCUZ4uae+vsHtuxUgU+RcmwCyJb0403cY3qztyWxX4khcdJPF2WjHIfnXAiWZarBqPuKxJ4AjOc9bBK0X2V4YMmoW19fWSpUZL071Rr0jI7371yOpoo3+dCJa0SZre8Dec3rqSrcytIarOrSzkgQWzj+hOgWKQJ28qcjOj+/5mASxnDE9Y8Uka15hPePJ/F+d/uOyd/6kQEYuWLtEbzowr2ERHheu426IzUmPl26YHT30tp9SVN4+VkURvvlhHeHiU2K7+VkVjxnzlbtp7w1OLgmmVqYDZ5Tw7XZiCPTt9NOL0L0UlTyJaQeKbj1+ijSXt1cA+w=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.6.1
Release Details
UpdatedOct. 23, 2026, 7:44 a.m.
Changelog

Fixed

  • A listing prefix was resolved as a path without being checked like a key. Its folder part went straight to Nextcloud, which refuses .. (that surfaced as 403) but folds ./, // and \ into another folder: ./.s3-uploads/ listed the multipart scratch area, which is only hidden at the bucket root, and a/./ listed a/ under keys that do not exist. Such a prefix — and .s3-uploads/ itself — now matches nothing, as no key can contain those segments.
  • A lock held for more than two minutes could be broken by a waiting write. The write under the lock stores the whole body, which on slow storage takes longer than that for a big object; the next conditional write broke the lock and both went ahead, losing an update. A lock is now stale after fifteen minutes. Releasing deletes the request's own row rather than whatever row the key has, so a request whose lock was broken no longer lets a third one in on finishing. Operator-visible: if a PHP process dies while holding a lock, conditional writes to that one key now answer 503 SlowDown for up to fifteen minutes instead of two. Unconditional writes and reads are not affected. To clear it sooner, delete the key's row from oc_s3api_locks.
  • Folder names in a listing prefix that Nextcloud refuses to look up (an admin-forbidden name, a character the storage cannot hold) give an empty listing instead of a 500.

Changed

  • The key rules (segments, length, backslash, NUL, scratch area) live in one place, KeyRules, used both for keys and for listing prefixes.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureUx3QVtNkXhUvzVRAoVG9Gwpnfn2m9qY5WXuzQOjnPYbv90BG5GRDxdNk4999aFYF8GuNo02wf1lqXhxhRfPuZ0XznFxDQ1pRRLnCi3TeysvX00rPbbc3gtotlgVWINihp44Xk0bWNoIjruuxPG/HPsdO4U1vGtPrbEZnPIBUcseggKJ6CEVH1YcfrWHxB1TeT+lwboVjT4VVnePoCkhFgnyJeheSRVF7MgLwi65YjXlD96ErEN5ywyD3tMmfvWhSNem2y/EKA1rgWpoFGxq7pmVNRJwB/9/6bRn3TukS0g71fUf+OoMmFZTuJoV3H01tOivdgyzGCc6aoPNg/B9eulf8ZoD3Vo019GBXW7jI+ZAEJCR/Vi3MFd7A+w+jKzD/hBLaFVvnFo7RCgMet29Go0SuSVqe7c8BiYACE5ydVBZgur68xnMFXuup4JVMcC0hpHqask4vNgZAoiB8t/NIJeqMFvsLwLTZPewBIuxi2qn03EXcldru4c/A9rwP480sC9NPOJHAkVM5CyNgG3rq0sy6vWbbfJyaHmty68BdceXy8MpXJ50dh/9XNgr9LdNmo5dcvHfoq1e8iKj7fqI/+AkejGvwKhO9GISFbAPyifWlS42SWj5d83km2L1cA5lTBgX6YSyjh4K5dz7E6n6VDKu195BmeKsMd5BO2FGl/Kg=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.6.0
Release Details
UpdatedOct. 21, 2026, 11:09 a.m.
Changelog

Security

  • XML request bodies switched entity substitution on instead of off. DeleteObjects and CompleteMultipartUpload parsed their body with LIBXML_NOENT, which — despite the name, and despite the comment next to it — makes libxml substitute entities, and load external ones (file://, php://filter) to do so. DeleteObjects echoes every key back in its response, so outside of Nextcloud that combination reads any file the web server can read, config.php included, for anyone holding a readwrite key. Inside Nextcloud it was not exploitable: core installs an external entity loader that returns nothing, and the app's safety rested entirely on that default. Internal entities were expanded, though — a key could be spelled as &k;. The flag is gone, and a body that declares a DTD at all is refused with MalformedXML: no S3 request carries one, and without a DTD there is nothing to expand, whatever the defaults. The check runs on the parsed document, so a UTF-16 body cannot hide its <!DOCTYPE from it.
  • Keys of a disabled account kept working. Nextcloud serves a disabled user's folder to whoever asks for it, so the S3 endpoint stayed open after the login was closed. A key now authenticates only while its account is enabled. When an account is deleted, its buckets, keys and open uploads are deleted with it — before, a user created later under the same id would have inherited them, working keys included.
  • host and every x-amz-* header must be signed, as on AWS. Unsigned, anyone able to alter a request in flight could have added x-amz-copy-source to a captured PUT and turned it into a copy of another object. Refused with AccessDenied.
  • An unknown bucket name and another bucket of the same account now get the same 404 NoSuchBucket. A key could tell them apart before (403 for the latter) and so list its owner's other buckets, which ListBuckets deliberately does not show.
  • Wrong secrets and unknown access keys count towards Nextcloud's brute force protection (action s3_api_auth), so an address that keeps guessing is slowed down. A stale clock does not count.
  • Objects of a type a browser renders as a document — HTML, SVG, XML, JavaScript — are served with Content-Disposition: attachment, and every object with X-Content-Type-Options: nosniff. A presigned URL opens on the Nextcloud origin, where an HTML object could otherwise pose as a login page.
  • Request bodies, assembled multipart uploads and copied ranges are buffered in Nextcloud's tempdirectory instead of php://temp, which spills to the system temp directory. The file is unlinked as soon as it is open, so nothing is left behind even if PHP dies mid-request.

Fixed

  • A key with an empty segment addressed a different object. Nextcloud collapses runs of slashes in the request URI before routing, so a request for a//b — or /b, as in bucket//b — reached the app as a/b and was served from there; a DELETE removed a/b. The key is now taken from the raw request URI, where the empty segment is still visible, and refused with InvalidArgument like any other key that cannot be a path. Slashes between the endpoint and the bucket name are still tolerated, since a client configured with a trailing slash on the endpoint sends …/s3//bucket/key.
  • A backslash in a key is refused: Nextcloud reads it as a path separator, so a\b was a second name for a/b, with a lock of its own.
  • Keys longer than 1024 bytes answer KeyTooLongError, a path segment over 255 bytes InvalidArgument, and a name Nextcloud refuses (.htaccess) or a write it does not permit (a read-only share, a full quota) 403 AccessDenied — all of these used to be a 500, or for long keys whatever the database made of them.
  • DeleteObjects takes at most 1000 keys, as on S3.
  • An aws-chunked body must decode to the size given in x-amz-decoded-content-length. That also catches an unsigned body cut off before its final chunk, which is accepted when the size fits.
  • A query parameter with a numeric name (?123=x) crashed signature verification with a 500.
  • The source key of CopyObject and UploadPartCopy gets the same validation as a key in the request line. Before, only a .. anywhere in it was refused: a//b and a/./b copied from a/b, a source inside .s3-uploads/ read the parts of an upload in progress, and a valid key such as a..b could not be copied at all.
  • Multipart uploads filled the trash bin. Completing or aborting an upload deleted its scratch folder with an ordinary delete, which Nextcloud's trash bin intercepts: every upload left a full copy of its parts in the owner's trash, plus an empty .s3-uploads folder each time a bucket's last upload finished — twice the space until the trash expired. A part uploaded twice also left the first copy behind as a file version, which deleting the folder did not remove. Scratch data is now deleted past the trash bin (the app answers the trash bin's MoveToTrashEvent for exactly that folder), and a re-uploaded part replaces the old one instead of overwriting it, so no version is created. Objects deleted through DeleteObject still go to the trash, as before. What earlier releases left behind is not cleaned up: trash entries named after upload ids or .s3-uploads expire with the trash, and stray versions remain under files_versions/<bucket folder>/.s3-uploads/, where nothing reads them.

Added

  • An integration test suite (tests/). tests/run.sh starts a throwaway Nextcloud in a container, enables the app and runs pytest against the real endpoint: SigV4 and presigned URLs, conditional writes under concurrency, listings and pagination, ranged reads, multipart and UploadPartCopy, aws-chunked bodies with tampered chunk signatures, read-only keys, and the settings API. It also drives gits3 — its own S3 client and a real push / clone / gc cycle through it — when gits3.py is found next to this repository. The cleanup job and the object locks are covered by ageing rows in the test instance's database: abandoned uploads, stale and held locks, orphaned ETags. Not part of the release tarball.
  • NC_VERSION=master tests/run.sh runs the suite against Nextcloud's daily master build — the next major before it has an image — by swapping the daily tarball into the newest stable image.

Changed

  • Nextcloud 36 is in the supported range ahead of its release: max-version moves from 35 to
  • A major upgrade disables every app whose max-version is below the new server, so declaring it only after 36 ships would switch the app off for everyone upgrading on day one. No code changes were needed. Checked against the daily master build (36.0.0 dev): all OCP classes this app uses still exist and none is deprecated, no deprecated method is called, info.xml validates against master's schema (unchanged since stable35), the code lints clean under PHP 8.5 with E_ALL, the full test suite passes, and the server logs no deprecation for the app while it runs.
  • Nextcloud 32 is dropped; the range is now 33–36. Database results are read with fetchAssociative() and fetchFirstColumn(), which arrived in 33, instead of the PDO-style fetch() and fetchAll(PDO::FETCH_COLUMN). PHP 8.2 stays the floor, which is what 33 and 34 ask for.
  • The walgit example config no longer sets max_retries: walgit's S3 backend never reads it and retries through the AWS SDK's default of three attempts. The README now says what that means for the 503 SlowDown that racing writes into a new folder can get.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureXujSNfzA/FlvrLPf2/1FzN1HQeMtLW9HtARN2EQ9xvMVt5rv+NIWzrgJDwHTAs5FmNQTTpOZ3V2f40rrGGkVG7pwMtHfqsdvbjVJ2axQ2rIk0icDAfigeRncAWsy6kDPtQiZ7/tflRli6geJhduDKIlrGnGVSySXY4nPj2wroIoEwXO/xik1R44ODERlq/SBLLE1XM7uN4+XM7vuZxsHSdE33MhdeIXTbN+b7aWm7TEM1XnjpeImUUMLYuO4VB+cj6wR4y7KFb86J8O2Yy8k9HyTBnGb3GrT0BCIBN4iUcPQiOkHb5JojhcdP6CNYrriCnR+aqZ2I0EkAdu3ovCClmgK1Cm5DvLl4Ev/2YI9OXADI0tfY/RXvlGD+YHlimEOLujDcuZvGzrm5mNBNTXOhVW17dji0knLgBN5D/WoEBuHNaz9i2+Zd4tLySM8ioDTeQul7K98k8YpySqsIC/ISTIufs2OKcqeJqP5+rTx0xWjx5yarAPb8xC4JaDcbudEjL0nOezpgj4Gh0IziVmm6+lweRYNTvJa3uXoGbTLrmCb5YhdUkOeRCNP1fVB+p81APRZ8gyeABj+7/4R0pCWKHBLKCG24yx4/2Usmi4dBtWrrt325qOAcZ3V110tB1Lrn1zKJWCf4+VleajLNoLOIVzLqGkCzd39i+sBKa5kN6U=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.5.4
Release Details
UpdatedOct. 20, 2026, 6:23 p.m.
Changelog

Fixed

  • Deleting a key that names a folder moved the whole folder to the trash. DeleteObject and the bulk DeleteObjects resolved the key and called delete() on whatever node came back, without checking that it was a file. A DELETE on repos — while repos/a, repos/b, … exist — therefore removed the entire tree. Keys with a trailing slash were already rejected, but the bare prefix was not. In S3, repos does not exist just because repos/a does, so such a delete is now a no-op that answers 204 (or lists the key as deleted in a bulk request), matching what a missing key does. Reads already refused folders this way.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureNlcwlvSMTK5Y2Kj4igfIPrmGlaew8qMNtXC1rhMeyILFNps9IsGo4a0D0aZg6wSm4D7OhaXONfYh1GhLxJizATy/RnZ2Ty312KkJpDdZppjoH17oq7wGL1p8e1MW/d8c+KoY4jEog85yhFCNJhJvED5bxC5cf5zLNanFcPe9W5iNOj2jr0uPpgF06ooXdZHqs5D72/z3bVK13GkfbXR1fiwLDwxPuPcUo9dkVn48ZYNYuCVfy9QnYQzbLjd6wF1Hjtr4+2mGO7K4DxGQBLCUgLRVaUrgiq14jhUsGRrAQyacR4a0YyjTJ/afhBGdl6vEpOuP8dmKj4N7/kzDIEk5apYxSEoK0rUssso6fmGDIeDp7PwfLg7YBRlQ5umsIdMdkQNdUjAANxiR9/CEsbUcq8yLrJl0nE5qVAQdSzh3K2NuPKKjHdDDEh2NC9mUnuZ/csrv8RG9FAqGEFzYujNjFnRC72N7YafDa0Va4m0f9VGdA8Y3pvbo1LLcykle4r43MaP/89YIUWptV8lpxmGEiRM1OXUXjqnvuyOdRAlzpmr5voC88TBZheZkAplWrpnUi4eTH/IBiBYliW8Q6udsS5VRPnYgth9GuwZAhj6GEEz3uPN1UYr5jPjD5r7hulz9nyu+i5MbMPsz0qarXZITJEcMbNf4PyGd7BWTqszoJ68=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.5.3
Release Details
UpdatedSept. 7, 2026, 7:44 a.m.
Changelog

Fixed

  • A conditional write took its lock on the wrong identity, so compare-and-swap could lose an update. The lock key hashed the bucket id, but one file can be reached through two buckets: nothing stopped registering /Documents as foo and /Documents/sub as bar, after which foo/sub/x.txt and bar/x.txt are the same file with two independent locks. Two concurrent If-None-Match: * requests, one per bucket, would then both observe "absent" and both succeed — exactly the silent lost update that content-derived ETags were introduced in 1.2.0 to prevent. The lock is now keyed on the object's resolved storage path, which is what actually identifies the file. Note that hashing the bucket's folder would not have been enough either: in the example above the two buckets have different folders, and it is the full path of the object that has to agree.
  • Registering a folder that another bucket already serves is refused, including the nesting case above. Two names for one folder have no use that a single bucket with a key prefix does not cover better, and they leave the app treating one directory as two buckets — separate multipart scratch areas inside it, two ETag rows racing over one file id. Existing overlapping buckets are left alone: they still work, and now share a lock correctly. The comparison normalizes the path first, because /Documents//sub, /Documents/./sub and /Documents/other/../sub all pass the folder-exists check — Nextcloud collapses those itself — and would otherwise have slipped past the overlap check while naming a folder that is already served.
  • Creating a bucket whose name is already taken answers 409 with a readable message. It used to hit the unique index and surface as a 500 carrying the raw driver error, for what is the most ordinary mistake in that form. The index is still what decides — two racing requests can both pass the pre-check — and that collision now maps to the same 409 rather than a 500.
  • Unexpected failures while creating a bucket no longer return the exception message to the browser; it could carry a filesystem path or driver detail. They are logged instead.

Changed

  • PHP 8.2 is supported again, down from the 8.3 that 1.5.2 briefly required. <php min-version> is resolved against the server, so it has to be satisfiable on the oldest Nextcloud in the range — and 32 (which asks for 8.1) through 34 (8.2) all run on 8.2. Nextcloud 35 does need 8.3, but it enforces that itself, so the lower floor costs its users nothing while 32–34 keep working on the PHP their Nextcloud actually asks for. Verified by linting on 8.1, 8.2 and 8.3; nothing in the app uses a language feature past 8.1, and 8.1 stays out only because it is end of life.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureBxv7eS0izf6Tc6vZ8IQfPug86DhdLGRXQV7NCt8eXIHoFUhIFRx92T6RCoeEM2CnR8Um3S3IgA7zSCx08ckJ7ZITpZV5e2Q76lX9ql5O2sIXX/Rai0udBIl1rb6QXmFwTRFJ/E+zO/dE49hQgIft2LXs+Shctrha5OmvxFzOd8FI8FxjQCPHsYHXX+1lsXqKCUcGepxOof5YOCvDV34tq2jz9VqtmPBe0heUpR7eBJZoCvdPfQHyW4xDW09aAIe/5avZNtUg2EG38kqrPGKrTwOctVVwhNxHGhMQ7pTCgwHtm9SGdUCondbVCDebb23eTEA56ytNRAJKOO9P3DaP2FihIIgkTh8XhEajFbKyhI41EjV65ya4+UUAld0up+J1RNHOgjVQNZhQCPckV6zynBh+Qd2iE5hsmpkDZE/QfJ9SJaU+IkRIoIfdTYmQZsG3uKLTkzSCCL15YjfTNNxTRptoQ3J4u1BO7MSnexD7xqbGpUBwFyRQrA1N+mTQp9DrjvRAXEgNgoFyniYPoRyfyTDRIeyC7ETYKaZH4dyzi8jd7NiYai7dwQ6XuOkoNPw6nyiVSxPxdE0JyHOceb1owVlgfqAOCrefMovJ9EufqJnfSMXOLLcCfU6l4BHug0CjxtA+iAjXa5KzwELxGZazarGJZl/t0vYtolhjWGC8gAE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.5.2
Release Details
UpdatedSept. 7, 2026, 7:03 a.m.
Changelog

Changed

  • Nextcloud 35 is supported: max-version moves from 34 to 35. No code changes were needed — every OCP interface this app uses was checked against stable35 and all 37 of them still exist. TimedJob is the one worth naming: it dropped the deprecated execute() in 33, but this app implements run(), which is still the abstract method. Access control was already declared with #[NoAdminRequired], #[NoCSRFRequired] and #[PublicPage] attributes rather than the docblock annotations Nextcloud logs a deprecation for.
  • The supported range narrows to Nextcloud 32–35 and PHP 8.3+, from 30–34 and PHP 8.1+. Nextcloud 35 requires PHP 8.3, and the app store resolves <php min-version> against the server rather than against Nextcloud's own floor, so a single declared minimum has to cover the whole range. Choosing 8.3 means users on 32, 33 or 34 need a PHP newer than their Nextcloud asks for; that is the deliberate cost of testing against one interpreter instead of four, and PHP 8.1 is out of security support regardless. Nothing in the app needs a language feature past 8.1 — this bounds what is tested, not what parses. Nextcloud 30 and 31 are end of life and go with it.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
Signaturego7SuMzb3Y5g2yrfg9WxL9FjOI7BBbUsyJ0n8LHGRGTH8yus7Nv7zRXzOtP1CmVcR4idihqDAc79SBW8bKaS/ukkYmXAmxXlWUQ9dbF9TyqCPxDJHjsND2WiNS3vC8/TBNzMmHw17QHxF5XpnCV6NzKCqQO6PdinIPBGI83F8V9T2vYDPJY58sV01fXT2fcac4qbTZ7arxvbHjpUatbhwKQLKm6vCqwpuSDZtnFNM0eqNtI9ZWCkuBP170VQr+gZdGD0QDHs2V6JlNx3QWiVAhpdElU3n4+mwtpRFpKvPygB8JzwjBpeO41PywLAqdOv5FD2MhnR1W2ym14942m/IUQUK/kTHR68Q1GP37qn7Gbx5ZYwEhSaMeJLPB6wM21exZMRai46kWTHkxVTFB86w8xZX6ovKqc9SKtM4U+0oLUqia5N3H7EgTFpuT9NSon1VvqHDkjfWtrzCCHIOR3nWpc/rwC+htbDrqTimFM2TNxfAdVJwCIDI8GzHwoHe6biGI73W3Nb9ncNrwnkQS+WgEhHy2FwjA5h5yHhf6ELQckj6Dd21iNQXcs8iqVB5/VnCtWTjDoD5EFIhPdTZeU+n/1DpDlmzNxS4omPigo5w0RQa8m+2W7Ry37xeIvKQyIDRdpD1SUsz4q2OPjYG1wOJTGXkixdjbV1zcEFrJCfupE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.3.0

Nextcloud 34

S3 API 1.7.0
Release Details
UpdatedOct. 25, 2026, 6:20 a.m.
Changelog

Added

  • A folder picker in the personal settings. The folder path field has a Choose… button next to it that opens Nextcloud's file picker, restricted to folders: navigate into a folder and choose it. The home folder itself is not taken from the picker — a bucket on the whole home blocks every other one — so that one has to be typed as /. Typing a path by hand still works, and a path that does not exist is refused as before.
  • occ s3api:bucket:create pre-provisions bucket configs, for one or more users or for every member of a group, so an admin can roll the app out without each user clicking through the settings: bash occ s3api:bucket:create --user alice --folder /S3 --name files --create-folder occ s3api:bucket:create --group staff --folder /S3 --name files --create-folder --create-folder creates the folder in each user's home when it is missing; without it a missing folder is an error. No API keys are created — the users create their own on the bucket that then shows up in their settings.
  • Re-running is safe: a user whose bucket of that name already serves that folder is skipped. Any other clash — the name on a different folder (also after the user renamed the folder), or a folder inside or around another bucket — is an error for that user, and nothing is created for them, not even the folder.
  • An unknown user or group aborts the run before anything is created for anyone, and so does --folder /: a bucket on the whole home blocks every other bucket of that user, which for a group is a typo rather than a plan. Users who want it register it in their settings.
  • An incoming share at the path, or above it, is refused: whoever shares a folder named like the one picked for a group would otherwise receive everything its members upload. --allow-shared accepts it when that is the point.
  • A group covers its members at the time the command runs, so users who join later need another run. The exit code is non-zero when any user could not be provisioned.

Bucket configs created this way are logged like the ones from the settings page.

Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureT41gK7psK8uIR9HpVidmi0JvVveR2dX0lSrh/efxzU4XIfv6O+pbai9GMUDUK36UklkTI7dptddmqd84zVtMNPr64f5QcGRQsRNtEtbS+j920RoDBSacOZB79iSGM9y3BX5+jjSM666v5+pq20r0nWL7g8CUjPKqBDJuB+3K9DT6mmsW3gaYzoJkdU/S2kvrHzxo0wUpMP2ZWPuKRMhXEmdK3XhZJC5sYXIoinhWu/fuSpDb8Me5ZifbY1cVPxcIuaja3IxXSPa2YZRWZPWZwqjT5L8UClN93V9jZNZcDnRFktqN0I49uw4NEBk4K6+73VpQm+ZWcW4hxju/oiH6aEDccm9LmmfIPoUhhurQc2SoQMhB/wjOfEH5DipyUxkSsn84OxMTbZqthli3F0pbrEOAxadqFhJF0e4pc7NK6gfUT4e07jDvNi7THYLIOt8i0BrK+bfXUNDCUL1UBznScR35XIzyl9PL6kX92V44LOwg3EM5+A4s68OhUv49TsBg9PkM2HhMZM/ebsAtyoAxKr5Cnlt+j79HhNCkcnZVQM2eZtW0x9rSHAAyEFHLrqbhOoOMywRA2OYBDKZMwkeWIkSw9PGDphtvEtMZp2by+HM/xapYaGWUX/201T2JAB3uolGJk+UMNX95kO3YHIuy1vEi3SWcQ+MU9eysED2G8z0=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.6.2
Release Details
UpdatedOct. 24, 2026, 8:26 a.m.
Changelog

Hardening release. No new features: this works through a security review of 1.6.1 — no critical or high findings, a set of medium and low ones, all addressed. Updating is recommended. It carries one migration, which binds existing buckets to their folder's file id (see the first entry). Operator-visible: buckets on shares newer than the bucket stay unbound, DeleteObjects needs a signed checksum or payload hash, keys that are not NFC are refused, and uploads are bounded by 5 GiB and the owner's free space.

Security

  • A bucket was its folder's path, checked once at creation. Renamed, the folder stopped being served; whatever later appeared under the old name was served instead — a new folder, or a folder another user shared, which Nextcloud mounts under its own name. A key handed to a third party then read and wrote the other user's files. Buckets are now bound to the folder's file id and follow it through renames and moves; a folder in the trash answers NoSuchBucket. The migration binds existing buckets to what their path names now — except an incoming share that is newer than the bucket, since that is what the attack looks like once it has happened. Operator-visible: such a bucket, one on a federated share, and one whose folder cannot be found stay unbound with a warning in the log; binding is retried on first use by the same rule, otherwise the owner registers the folder again. Buckets on the owner's folders, group folders, external storage and on shares older than the bucket bind as before.
  • The XML bodies of DeleteObjects and CompleteMultipartUpload were not bound to the signature. They were read raw, without checking x-amz-content-sha256, so a captured request could be replayed within its fifteen minutes with another body: other keys deleted, another part list assembled. They are now read like uploads (decoded, chunk signatures checked) and checked against every claim the request makes about them — the payload hash, Content-MD5, and for DeleteObjects x-amz-checksum-* (CRC32, CRC32C, CRC64NVME, SHA-1, SHA-256). DeleteObjects also has to carry one the signature covers, as S3 requires a checksum there; a request with UNSIGNED-PAYLOAD and nothing else is refused with InvalidRequest. A checksum sent as an aws-chunked trailer (STREAMING-UNSIGNED-PAYLOAD-TRAILER) does not count, as trailers are not read; it takes a signed header or a literal payload hash.
  • Keys in another Unicode normalisation, or not UTF-8 at all, were accepted. Nextcloud stores names in NFC, so the NFD spelling of é was a second name for the same file: with a lock of its own, so two If-None-Match: * writes could both win; past the self-copy guard, which with file locking off truncated the object; past the bucket overlap check. Bytes that are not UTF-8 ended in a database error. Such keys, and keys with control characters, are refused with InvalidArgument. The overlap check compares folders by where they are now, NFC. Self-copy is detected by file id, which also covers case-insensitive storages.
  • Request bodies had no size limit of the app's own. A body went to the temp directory in full before the quota was looked at, so one upload could fill it for the whole instance; XML bodies were held in memory and parsed whatever their size. PutObject and UploadPart now take at most 5 GiB, as on S3, and no more than the bucket owner's free space (403 AccessDenied) — checked on the declared length before reading, and while reading. XML bodies stop at 4 MiB (MaxMessageLengthExceeded), CompleteMultipartUpload at 10000 parts. Completion checks the free space before assembling.
  • Listings hashed every file without a recorded ETag, in full. A readonly key could make one GET read a thousand large files. A page now hashes at most 256 MiB; past that, files are listed with Nextcloud's etag plus -0 until their content ETag is known — the suffix keeps tools from taking it for an MD5, which on some storages it looks like. A conditional write computes a missing ETag before taking the object lock, not while holding it.
  • ListParts re-read every part on each call, and CompleteMultipartUpload read them three times. Part ETags are recorded when the part is stored.
  • ListParts and ListMultipartUploads need a readwrite key.
  • The object lock is keyed on where the file really is (storage and path in it), not on the owner's view of it, so a writer coming through a share and one through the owner's own bucket now exclude each other.
  • A request URI that does not account for the routed path is refused (InvalidURI) instead of falling back to the routed path, which is the one that may have lost a slash.
  • XML responses replace what XML cannot carry instead of producing an unparseable document or an empty value.
  • Secrets are marked #[\SensitiveParameter], so a logged stack trace does not carry them. The settings page clears a new secret from the page when its dialog closes.

Changed

  • Operator-visible: a contended conditional write waits up to three seconds for the object lock, not ten, with backoff, before answering 503 SlowDown.
  • Failed signatures and unknown keys are logged as warnings with the key and address, and the brute force entry carries the key. Bucket and key changes and DeleteObjects are logged at info level and to admin_audit.
  • ListParts and ListMultipartUploads page (max-parts/part-number-marker, max-uploads/key-marker/upload-id-marker, prefix). At most 1000 uploads per bucket may be in flight; a bucket at that limit discards its expired uploads itself when the next one starts. Cleanup reclaims abandoned uploads 200 per run.
  • The settings API answers 400 instead of 500 to parameters of the wrong type and to labels over 255 characters.
  • Files whose names no key can spell are left out of listings.
  • README: trusted_proxies behind a reverse proxy, room for tempdirectory.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignaturefC64D77DhXVDBlfKpN1obqnKMaFIHWoLOlgIcui2mrlm4S7i1gb9KSMqoAAcWgsgYX4FhanvorI9fWfua1bR2Qn3j3LlgrPMUuaUnwMbSHJtOu9zL8BxFjxRIWGsHjFKtmAv6VORaE4hLkhmMlOqNNnXcs8/rK2BRBY3lryOz65e9WZY/5E0u4QK9cv3kfH+qk8IMWAsZZF0FY8hF1BX7I+k41i9oWhjQ8d3enaF2/4uSf/tSOdBrLvn9hSsPMkVYC/9HpxJZT2BrnpRgCEztXvsamXIvB88Ncty0oAaGa0I+VLT9EcT2CAHt487WM3B6Bz74092riF26AaEbfoLt3sZGBNEXY7DvQQ0FCUZ4uae+vsHtuxUgU+RcmwCyJb0403cY3qztyWxX4khcdJPF2WjHIfnXAiWZarBqPuKxJ4AjOc9bBK0X2V4YMmoW19fWSpUZL071Rr0jI7371yOpoo3+dCJa0SZre8Dec3rqSrcytIarOrSzkgQWzj+hOgWKQJ28qcjOj+/5mASxnDE9Y8Uka15hPePJ/F+d/uOyd/6kQEYuWLtEbzowr2ERHheu426IzUmPl26YHT30tp9SVN4+VkURvvlhHeHiU2K7+VkVjxnzlbtp7w1OLgmmVqYDZ5Tw7XZiCPTt9NOL0L0UlTyJaQeKbj1+ijSXt1cA+w=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.6.1
Release Details
UpdatedOct. 23, 2026, 7:44 a.m.
Changelog

Fixed

  • A listing prefix was resolved as a path without being checked like a key. Its folder part went straight to Nextcloud, which refuses .. (that surfaced as 403) but folds ./, // and \ into another folder: ./.s3-uploads/ listed the multipart scratch area, which is only hidden at the bucket root, and a/./ listed a/ under keys that do not exist. Such a prefix — and .s3-uploads/ itself — now matches nothing, as no key can contain those segments.
  • A lock held for more than two minutes could be broken by a waiting write. The write under the lock stores the whole body, which on slow storage takes longer than that for a big object; the next conditional write broke the lock and both went ahead, losing an update. A lock is now stale after fifteen minutes. Releasing deletes the request's own row rather than whatever row the key has, so a request whose lock was broken no longer lets a third one in on finishing. Operator-visible: if a PHP process dies while holding a lock, conditional writes to that one key now answer 503 SlowDown for up to fifteen minutes instead of two. Unconditional writes and reads are not affected. To clear it sooner, delete the key's row from oc_s3api_locks.
  • Folder names in a listing prefix that Nextcloud refuses to look up (an admin-forbidden name, a character the storage cannot hold) give an empty listing instead of a 500.

Changed

  • The key rules (segments, length, backslash, NUL, scratch area) live in one place, KeyRules, used both for keys and for listing prefixes.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureUx3QVtNkXhUvzVRAoVG9Gwpnfn2m9qY5WXuzQOjnPYbv90BG5GRDxdNk4999aFYF8GuNo02wf1lqXhxhRfPuZ0XznFxDQ1pRRLnCi3TeysvX00rPbbc3gtotlgVWINihp44Xk0bWNoIjruuxPG/HPsdO4U1vGtPrbEZnPIBUcseggKJ6CEVH1YcfrWHxB1TeT+lwboVjT4VVnePoCkhFgnyJeheSRVF7MgLwi65YjXlD96ErEN5ywyD3tMmfvWhSNem2y/EKA1rgWpoFGxq7pmVNRJwB/9/6bRn3TukS0g71fUf+OoMmFZTuJoV3H01tOivdgyzGCc6aoPNg/B9eulf8ZoD3Vo019GBXW7jI+ZAEJCR/Vi3MFd7A+w+jKzD/hBLaFVvnFo7RCgMet29Go0SuSVqe7c8BiYACE5ydVBZgur68xnMFXuup4JVMcC0hpHqask4vNgZAoiB8t/NIJeqMFvsLwLTZPewBIuxi2qn03EXcldru4c/A9rwP480sC9NPOJHAkVM5CyNgG3rq0sy6vWbbfJyaHmty68BdceXy8MpXJ50dh/9XNgr9LdNmo5dcvHfoq1e8iKj7fqI/+AkejGvwKhO9GISFbAPyifWlS42SWj5d83km2L1cA5lTBgX6YSyjh4K5dz7E6n6VDKu195BmeKsMd5BO2FGl/Kg=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.6.0
Release Details
UpdatedOct. 21, 2026, 11:09 a.m.
Changelog

Security

  • XML request bodies switched entity substitution on instead of off. DeleteObjects and CompleteMultipartUpload parsed their body with LIBXML_NOENT, which — despite the name, and despite the comment next to it — makes libxml substitute entities, and load external ones (file://, php://filter) to do so. DeleteObjects echoes every key back in its response, so outside of Nextcloud that combination reads any file the web server can read, config.php included, for anyone holding a readwrite key. Inside Nextcloud it was not exploitable: core installs an external entity loader that returns nothing, and the app's safety rested entirely on that default. Internal entities were expanded, though — a key could be spelled as &k;. The flag is gone, and a body that declares a DTD at all is refused with MalformedXML: no S3 request carries one, and without a DTD there is nothing to expand, whatever the defaults. The check runs on the parsed document, so a UTF-16 body cannot hide its <!DOCTYPE from it.
  • Keys of a disabled account kept working. Nextcloud serves a disabled user's folder to whoever asks for it, so the S3 endpoint stayed open after the login was closed. A key now authenticates only while its account is enabled. When an account is deleted, its buckets, keys and open uploads are deleted with it — before, a user created later under the same id would have inherited them, working keys included.
  • host and every x-amz-* header must be signed, as on AWS. Unsigned, anyone able to alter a request in flight could have added x-amz-copy-source to a captured PUT and turned it into a copy of another object. Refused with AccessDenied.
  • An unknown bucket name and another bucket of the same account now get the same 404 NoSuchBucket. A key could tell them apart before (403 for the latter) and so list its owner's other buckets, which ListBuckets deliberately does not show.
  • Wrong secrets and unknown access keys count towards Nextcloud's brute force protection (action s3_api_auth), so an address that keeps guessing is slowed down. A stale clock does not count.
  • Objects of a type a browser renders as a document — HTML, SVG, XML, JavaScript — are served with Content-Disposition: attachment, and every object with X-Content-Type-Options: nosniff. A presigned URL opens on the Nextcloud origin, where an HTML object could otherwise pose as a login page.
  • Request bodies, assembled multipart uploads and copied ranges are buffered in Nextcloud's tempdirectory instead of php://temp, which spills to the system temp directory. The file is unlinked as soon as it is open, so nothing is left behind even if PHP dies mid-request.

Fixed

  • A key with an empty segment addressed a different object. Nextcloud collapses runs of slashes in the request URI before routing, so a request for a//b — or /b, as in bucket//b — reached the app as a/b and was served from there; a DELETE removed a/b. The key is now taken from the raw request URI, where the empty segment is still visible, and refused with InvalidArgument like any other key that cannot be a path. Slashes between the endpoint and the bucket name are still tolerated, since a client configured with a trailing slash on the endpoint sends …/s3//bucket/key.
  • A backslash in a key is refused: Nextcloud reads it as a path separator, so a\b was a second name for a/b, with a lock of its own.
  • Keys longer than 1024 bytes answer KeyTooLongError, a path segment over 255 bytes InvalidArgument, and a name Nextcloud refuses (.htaccess) or a write it does not permit (a read-only share, a full quota) 403 AccessDenied — all of these used to be a 500, or for long keys whatever the database made of them.
  • DeleteObjects takes at most 1000 keys, as on S3.
  • An aws-chunked body must decode to the size given in x-amz-decoded-content-length. That also catches an unsigned body cut off before its final chunk, which is accepted when the size fits.
  • A query parameter with a numeric name (?123=x) crashed signature verification with a 500.
  • The source key of CopyObject and UploadPartCopy gets the same validation as a key in the request line. Before, only a .. anywhere in it was refused: a//b and a/./b copied from a/b, a source inside .s3-uploads/ read the parts of an upload in progress, and a valid key such as a..b could not be copied at all.
  • Multipart uploads filled the trash bin. Completing or aborting an upload deleted its scratch folder with an ordinary delete, which Nextcloud's trash bin intercepts: every upload left a full copy of its parts in the owner's trash, plus an empty .s3-uploads folder each time a bucket's last upload finished — twice the space until the trash expired. A part uploaded twice also left the first copy behind as a file version, which deleting the folder did not remove. Scratch data is now deleted past the trash bin (the app answers the trash bin's MoveToTrashEvent for exactly that folder), and a re-uploaded part replaces the old one instead of overwriting it, so no version is created. Objects deleted through DeleteObject still go to the trash, as before. What earlier releases left behind is not cleaned up: trash entries named after upload ids or .s3-uploads expire with the trash, and stray versions remain under files_versions/<bucket folder>/.s3-uploads/, where nothing reads them.

Added

  • An integration test suite (tests/). tests/run.sh starts a throwaway Nextcloud in a container, enables the app and runs pytest against the real endpoint: SigV4 and presigned URLs, conditional writes under concurrency, listings and pagination, ranged reads, multipart and UploadPartCopy, aws-chunked bodies with tampered chunk signatures, read-only keys, and the settings API. It also drives gits3 — its own S3 client and a real push / clone / gc cycle through it — when gits3.py is found next to this repository. The cleanup job and the object locks are covered by ageing rows in the test instance's database: abandoned uploads, stale and held locks, orphaned ETags. Not part of the release tarball.
  • NC_VERSION=master tests/run.sh runs the suite against Nextcloud's daily master build — the next major before it has an image — by swapping the daily tarball into the newest stable image.

Changed

  • Nextcloud 36 is in the supported range ahead of its release: max-version moves from 35 to
  • A major upgrade disables every app whose max-version is below the new server, so declaring it only after 36 ships would switch the app off for everyone upgrading on day one. No code changes were needed. Checked against the daily master build (36.0.0 dev): all OCP classes this app uses still exist and none is deprecated, no deprecated method is called, info.xml validates against master's schema (unchanged since stable35), the code lints clean under PHP 8.5 with E_ALL, the full test suite passes, and the server logs no deprecation for the app while it runs.
  • Nextcloud 32 is dropped; the range is now 33–36. Database results are read with fetchAssociative() and fetchFirstColumn(), which arrived in 33, instead of the PDO-style fetch() and fetchAll(PDO::FETCH_COLUMN). PHP 8.2 stays the floor, which is what 33 and 34 ask for.
  • The walgit example config no longer sets max_retries: walgit's S3 backend never reads it and retries through the AWS SDK's default of three attempts. The README now says what that means for the 503 SlowDown that racing writes into a new folder can get.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureXujSNfzA/FlvrLPf2/1FzN1HQeMtLW9HtARN2EQ9xvMVt5rv+NIWzrgJDwHTAs5FmNQTTpOZ3V2f40rrGGkVG7pwMtHfqsdvbjVJ2axQ2rIk0icDAfigeRncAWsy6kDPtQiZ7/tflRli6geJhduDKIlrGnGVSySXY4nPj2wroIoEwXO/xik1R44ODERlq/SBLLE1XM7uN4+XM7vuZxsHSdE33MhdeIXTbN+b7aWm7TEM1XnjpeImUUMLYuO4VB+cj6wR4y7KFb86J8O2Yy8k9HyTBnGb3GrT0BCIBN4iUcPQiOkHb5JojhcdP6CNYrriCnR+aqZ2I0EkAdu3ovCClmgK1Cm5DvLl4Ev/2YI9OXADI0tfY/RXvlGD+YHlimEOLujDcuZvGzrm5mNBNTXOhVW17dji0knLgBN5D/WoEBuHNaz9i2+Zd4tLySM8ioDTeQul7K98k8YpySqsIC/ISTIufs2OKcqeJqP5+rTx0xWjx5yarAPb8xC4JaDcbudEjL0nOezpgj4Gh0IziVmm6+lweRYNTvJa3uXoGbTLrmCb5YhdUkOeRCNP1fVB+p81APRZ8gyeABj+7/4R0pCWKHBLKCG24yx4/2Usmi4dBtWrrt325qOAcZ3V110tB1Lrn1zKJWCf4+VleajLNoLOIVzLqGkCzd39i+sBKa5kN6U=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.5.4
Release Details
UpdatedOct. 20, 2026, 6:23 p.m.
Changelog

Fixed

  • Deleting a key that names a folder moved the whole folder to the trash. DeleteObject and the bulk DeleteObjects resolved the key and called delete() on whatever node came back, without checking that it was a file. A DELETE on repos — while repos/a, repos/b, … exist — therefore removed the entire tree. Keys with a trailing slash were already rejected, but the bare prefix was not. In S3, repos does not exist just because repos/a does, so such a delete is now a no-op that answers 204 (or lists the key as deleted in a bulk request), matching what a missing key does. Reads already refused folders this way.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureNlcwlvSMTK5Y2Kj4igfIPrmGlaew8qMNtXC1rhMeyILFNps9IsGo4a0D0aZg6wSm4D7OhaXONfYh1GhLxJizATy/RnZ2Ty312KkJpDdZppjoH17oq7wGL1p8e1MW/d8c+KoY4jEog85yhFCNJhJvED5bxC5cf5zLNanFcPe9W5iNOj2jr0uPpgF06ooXdZHqs5D72/z3bVK13GkfbXR1fiwLDwxPuPcUo9dkVn48ZYNYuCVfy9QnYQzbLjd6wF1Hjtr4+2mGO7K4DxGQBLCUgLRVaUrgiq14jhUsGRrAQyacR4a0YyjTJ/afhBGdl6vEpOuP8dmKj4N7/kzDIEk5apYxSEoK0rUssso6fmGDIeDp7PwfLg7YBRlQ5umsIdMdkQNdUjAANxiR9/CEsbUcq8yLrJl0nE5qVAQdSzh3K2NuPKKjHdDDEh2NC9mUnuZ/csrv8RG9FAqGEFzYujNjFnRC72N7YafDa0Va4m0f9VGdA8Y3pvbo1LLcykle4r43MaP/89YIUWptV8lpxmGEiRM1OXUXjqnvuyOdRAlzpmr5voC88TBZheZkAplWrpnUi4eTH/IBiBYliW8Q6udsS5VRPnYgth9GuwZAhj6GEEz3uPN1UYr5jPjD5r7hulz9nyu+i5MbMPsz0qarXZITJEcMbNf4PyGd7BWTqszoJ68=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.5.3
Release Details
UpdatedSept. 7, 2026, 7:44 a.m.
Changelog

Fixed

  • A conditional write took its lock on the wrong identity, so compare-and-swap could lose an update. The lock key hashed the bucket id, but one file can be reached through two buckets: nothing stopped registering /Documents as foo and /Documents/sub as bar, after which foo/sub/x.txt and bar/x.txt are the same file with two independent locks. Two concurrent If-None-Match: * requests, one per bucket, would then both observe "absent" and both succeed — exactly the silent lost update that content-derived ETags were introduced in 1.2.0 to prevent. The lock is now keyed on the object's resolved storage path, which is what actually identifies the file. Note that hashing the bucket's folder would not have been enough either: in the example above the two buckets have different folders, and it is the full path of the object that has to agree.
  • Registering a folder that another bucket already serves is refused, including the nesting case above. Two names for one folder have no use that a single bucket with a key prefix does not cover better, and they leave the app treating one directory as two buckets — separate multipart scratch areas inside it, two ETag rows racing over one file id. Existing overlapping buckets are left alone: they still work, and now share a lock correctly. The comparison normalizes the path first, because /Documents//sub, /Documents/./sub and /Documents/other/../sub all pass the folder-exists check — Nextcloud collapses those itself — and would otherwise have slipped past the overlap check while naming a folder that is already served.
  • Creating a bucket whose name is already taken answers 409 with a readable message. It used to hit the unique index and surface as a 500 carrying the raw driver error, for what is the most ordinary mistake in that form. The index is still what decides — two racing requests can both pass the pre-check — and that collision now maps to the same 409 rather than a 500.
  • Unexpected failures while creating a bucket no longer return the exception message to the browser; it could carry a filesystem path or driver detail. They are logged instead.

Changed

  • PHP 8.2 is supported again, down from the 8.3 that 1.5.2 briefly required. <php min-version> is resolved against the server, so it has to be satisfiable on the oldest Nextcloud in the range — and 32 (which asks for 8.1) through 34 (8.2) all run on 8.2. Nextcloud 35 does need 8.3, but it enforces that itself, so the lower floor costs its users nothing while 32–34 keep working on the PHP their Nextcloud actually asks for. Verified by linting on 8.1, 8.2 and 8.3; nothing in the app uses a language feature past 8.1, and 8.1 stays out only because it is end of life.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureBxv7eS0izf6Tc6vZ8IQfPug86DhdLGRXQV7NCt8eXIHoFUhIFRx92T6RCoeEM2CnR8Um3S3IgA7zSCx08ckJ7ZITpZV5e2Q76lX9ql5O2sIXX/Rai0udBIl1rb6QXmFwTRFJ/E+zO/dE49hQgIft2LXs+Shctrha5OmvxFzOd8FI8FxjQCPHsYHXX+1lsXqKCUcGepxOof5YOCvDV34tq2jz9VqtmPBe0heUpR7eBJZoCvdPfQHyW4xDW09aAIe/5avZNtUg2EG38kqrPGKrTwOctVVwhNxHGhMQ7pTCgwHtm9SGdUCondbVCDebb23eTEA56ytNRAJKOO9P3DaP2FihIIgkTh8XhEajFbKyhI41EjV65ya4+UUAld0up+J1RNHOgjVQNZhQCPckV6zynBh+Qd2iE5hsmpkDZE/QfJ9SJaU+IkRIoIfdTYmQZsG3uKLTkzSCCL15YjfTNNxTRptoQ3J4u1BO7MSnexD7xqbGpUBwFyRQrA1N+mTQp9DrjvRAXEgNgoFyniYPoRyfyTDRIeyC7ETYKaZH4dyzi8jd7NiYai7dwQ6XuOkoNPw6nyiVSxPxdE0JyHOceb1owVlgfqAOCrefMovJ9EufqJnfSMXOLLcCfU6l4BHug0CjxtA+iAjXa5KzwELxGZazarGJZl/t0vYtolhjWGC8gAE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.5.2
Release Details
UpdatedSept. 7, 2026, 7:03 a.m.
Changelog

Changed

  • Nextcloud 35 is supported: max-version moves from 34 to 35. No code changes were needed — every OCP interface this app uses was checked against stable35 and all 37 of them still exist. TimedJob is the one worth naming: it dropped the deprecated execute() in 33, but this app implements run(), which is still the abstract method. Access control was already declared with #[NoAdminRequired], #[NoCSRFRequired] and #[PublicPage] attributes rather than the docblock annotations Nextcloud logs a deprecation for.
  • The supported range narrows to Nextcloud 32–35 and PHP 8.3+, from 30–34 and PHP 8.1+. Nextcloud 35 requires PHP 8.3, and the app store resolves <php min-version> against the server rather than against Nextcloud's own floor, so a single declared minimum has to cover the whole range. Choosing 8.3 means users on 32, 33 or 34 need a PHP newer than their Nextcloud asks for; that is the deliberate cost of testing against one interpreter instead of four, and PHP 8.1 is out of security support regardless. Nothing in the app needs a language feature past 8.1 — this bounds what is tested, not what parses. Nextcloud 30 and 31 are end of life and go with it.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
Signaturego7SuMzb3Y5g2yrfg9WxL9FjOI7BBbUsyJ0n8LHGRGTH8yus7Nv7zRXzOtP1CmVcR4idihqDAc79SBW8bKaS/ukkYmXAmxXlWUQ9dbF9TyqCPxDJHjsND2WiNS3vC8/TBNzMmHw17QHxF5XpnCV6NzKCqQO6PdinIPBGI83F8V9T2vYDPJY58sV01fXT2fcac4qbTZ7arxvbHjpUatbhwKQLKm6vCqwpuSDZtnFNM0eqNtI9ZWCkuBP170VQr+gZdGD0QDHs2V6JlNx3QWiVAhpdElU3n4+mwtpRFpKvPygB8JzwjBpeO41PywLAqdOv5FD2MhnR1W2ym14942m/IUQUK/kTHR68Q1GP37qn7Gbx5ZYwEhSaMeJLPB6wM21exZMRai46kWTHkxVTFB86w8xZX6ovKqc9SKtM4U+0oLUqia5N3H7EgTFpuT9NSon1VvqHDkjfWtrzCCHIOR3nWpc/rwC+htbDrqTimFM2TNxfAdVJwCIDI8GzHwoHe6biGI73W3Nb9ncNrwnkQS+WgEhHy2FwjA5h5yHhf6ELQckj6Dd21iNQXcs8iqVB5/VnCtWTjDoD5EFIhPdTZeU+n/1DpDlmzNxS4omPigo5w0RQa8m+2W7Ry37xeIvKQyIDRdpD1SUsz4q2OPjYG1wOJTGXkixdjbV1zcEFrJCfupE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.3.0
S3 API 1.5.1
Release Details
UpdatedSept. 5, 2026, 6:51 a.m.
Changelog

Changed

  • First release published to the Nextcloud app store. No code changes: the app metadata gained the bugs element the store requires, an SPDX licence identifier in place of the deprecated agpl shorthand, and the element order the XSD prescribes — info.xml did not validate before. The store also reads CHANGELOG.md and LICENSE from inside the archive, and neither existed at the v1.5.0 tag. Cutting a version rather than moving that tag leaves what v1.5.0 points at unchanged for anyone who already fetched it.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureTafOcWH7r66+n77ARNqExXbcfbgUe7mAjFnI2ZJekbTpEELaNuWcxbWhZJ5cPs7UJLOFRYtI4lWBYFfN46Dlq68SrGetxpgXsQ/fGDrPVOjuM3XWZdMG35uBtOJnOzrr56M/kdhHZ7ppN/kE3cJPFJfeLLXQtHnqpUsnb+LBP0w70uHdih+a/PZ+VZRGI+9C6qcM9GGVvwxl+OP2kH0UYA5ZTF7lNUaNu6t6IGsxW3Kfiv+h5RGsxFTrYJw3iUlgxoLkqO+IxgwbuwoxfFvwhkZuxm2NKMDVhykrmMyeqUntpmgccscZ9YY7h3isS8r8oI0aJEEjNPLOSgeExpV9KQXe8xO55uV+7MXtQaiO+W8j30qAz6loGW6rapfhd6ONFDgd0sOQ7+ScC/5txn7z8n7IX0yFyCdAHwyUhwF93TdUn1Cr/95+uv7EI+jWcXGDIq+3JY7ppFIZg8QJ6fTmjt3J0+iPfhIokY+QL+TBcYWukt01EkcjJJc9NLBDgMqVyl3sV2v5iQfV1IiWClpPvhGyc4uq+JwZx96bPoLuvGEVlvyLz/3vu4TwdFESmUraroNVjzLKx6xfnrHPMZ/IfAqrVAmSqXEijm1L7InKsGm0lmO+yFlpB4Sn1C/MV8tLNIm5tzrB9AspQLxTJqdlXtVpVgVgRdNUwjaczPLXCPA=
Signature digestsha512
Dependencies
Required Nextcloud versions >=30.0.0,<35.0.0
Minimum Integer bits32
PHP>=8.1.0

Nextcloud 33

S3 API 1.7.0
Release Details
UpdatedOct. 25, 2026, 6:20 a.m.
Changelog

Added

  • A folder picker in the personal settings. The folder path field has a Choose… button next to it that opens Nextcloud's file picker, restricted to folders: navigate into a folder and choose it. The home folder itself is not taken from the picker — a bucket on the whole home blocks every other one — so that one has to be typed as /. Typing a path by hand still works, and a path that does not exist is refused as before.
  • occ s3api:bucket:create pre-provisions bucket configs, for one or more users or for every member of a group, so an admin can roll the app out without each user clicking through the settings: bash occ s3api:bucket:create --user alice --folder /S3 --name files --create-folder occ s3api:bucket:create --group staff --folder /S3 --name files --create-folder --create-folder creates the folder in each user's home when it is missing; without it a missing folder is an error. No API keys are created — the users create their own on the bucket that then shows up in their settings.
  • Re-running is safe: a user whose bucket of that name already serves that folder is skipped. Any other clash — the name on a different folder (also after the user renamed the folder), or a folder inside or around another bucket — is an error for that user, and nothing is created for them, not even the folder.
  • An unknown user or group aborts the run before anything is created for anyone, and so does --folder /: a bucket on the whole home blocks every other bucket of that user, which for a group is a typo rather than a plan. Users who want it register it in their settings.
  • An incoming share at the path, or above it, is refused: whoever shares a folder named like the one picked for a group would otherwise receive everything its members upload. --allow-shared accepts it when that is the point.
  • A group covers its members at the time the command runs, so users who join later need another run. The exit code is non-zero when any user could not be provisioned.

Bucket configs created this way are logged like the ones from the settings page.

Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureT41gK7psK8uIR9HpVidmi0JvVveR2dX0lSrh/efxzU4XIfv6O+pbai9GMUDUK36UklkTI7dptddmqd84zVtMNPr64f5QcGRQsRNtEtbS+j920RoDBSacOZB79iSGM9y3BX5+jjSM666v5+pq20r0nWL7g8CUjPKqBDJuB+3K9DT6mmsW3gaYzoJkdU/S2kvrHzxo0wUpMP2ZWPuKRMhXEmdK3XhZJC5sYXIoinhWu/fuSpDb8Me5ZifbY1cVPxcIuaja3IxXSPa2YZRWZPWZwqjT5L8UClN93V9jZNZcDnRFktqN0I49uw4NEBk4K6+73VpQm+ZWcW4hxju/oiH6aEDccm9LmmfIPoUhhurQc2SoQMhB/wjOfEH5DipyUxkSsn84OxMTbZqthli3F0pbrEOAxadqFhJF0e4pc7NK6gfUT4e07jDvNi7THYLIOt8i0BrK+bfXUNDCUL1UBznScR35XIzyl9PL6kX92V44LOwg3EM5+A4s68OhUv49TsBg9PkM2HhMZM/ebsAtyoAxKr5Cnlt+j79HhNCkcnZVQM2eZtW0x9rSHAAyEFHLrqbhOoOMywRA2OYBDKZMwkeWIkSw9PGDphtvEtMZp2by+HM/xapYaGWUX/201T2JAB3uolGJk+UMNX95kO3YHIuy1vEi3SWcQ+MU9eysED2G8z0=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.6.2
Release Details
UpdatedOct. 24, 2026, 8:26 a.m.
Changelog

Hardening release. No new features: this works through a security review of 1.6.1 — no critical or high findings, a set of medium and low ones, all addressed. Updating is recommended. It carries one migration, which binds existing buckets to their folder's file id (see the first entry). Operator-visible: buckets on shares newer than the bucket stay unbound, DeleteObjects needs a signed checksum or payload hash, keys that are not NFC are refused, and uploads are bounded by 5 GiB and the owner's free space.

Security

  • A bucket was its folder's path, checked once at creation. Renamed, the folder stopped being served; whatever later appeared under the old name was served instead — a new folder, or a folder another user shared, which Nextcloud mounts under its own name. A key handed to a third party then read and wrote the other user's files. Buckets are now bound to the folder's file id and follow it through renames and moves; a folder in the trash answers NoSuchBucket. The migration binds existing buckets to what their path names now — except an incoming share that is newer than the bucket, since that is what the attack looks like once it has happened. Operator-visible: such a bucket, one on a federated share, and one whose folder cannot be found stay unbound with a warning in the log; binding is retried on first use by the same rule, otherwise the owner registers the folder again. Buckets on the owner's folders, group folders, external storage and on shares older than the bucket bind as before.
  • The XML bodies of DeleteObjects and CompleteMultipartUpload were not bound to the signature. They were read raw, without checking x-amz-content-sha256, so a captured request could be replayed within its fifteen minutes with another body: other keys deleted, another part list assembled. They are now read like uploads (decoded, chunk signatures checked) and checked against every claim the request makes about them — the payload hash, Content-MD5, and for DeleteObjects x-amz-checksum-* (CRC32, CRC32C, CRC64NVME, SHA-1, SHA-256). DeleteObjects also has to carry one the signature covers, as S3 requires a checksum there; a request with UNSIGNED-PAYLOAD and nothing else is refused with InvalidRequest. A checksum sent as an aws-chunked trailer (STREAMING-UNSIGNED-PAYLOAD-TRAILER) does not count, as trailers are not read; it takes a signed header or a literal payload hash.
  • Keys in another Unicode normalisation, or not UTF-8 at all, were accepted. Nextcloud stores names in NFC, so the NFD spelling of é was a second name for the same file: with a lock of its own, so two If-None-Match: * writes could both win; past the self-copy guard, which with file locking off truncated the object; past the bucket overlap check. Bytes that are not UTF-8 ended in a database error. Such keys, and keys with control characters, are refused with InvalidArgument. The overlap check compares folders by where they are now, NFC. Self-copy is detected by file id, which also covers case-insensitive storages.
  • Request bodies had no size limit of the app's own. A body went to the temp directory in full before the quota was looked at, so one upload could fill it for the whole instance; XML bodies were held in memory and parsed whatever their size. PutObject and UploadPart now take at most 5 GiB, as on S3, and no more than the bucket owner's free space (403 AccessDenied) — checked on the declared length before reading, and while reading. XML bodies stop at 4 MiB (MaxMessageLengthExceeded), CompleteMultipartUpload at 10000 parts. Completion checks the free space before assembling.
  • Listings hashed every file without a recorded ETag, in full. A readonly key could make one GET read a thousand large files. A page now hashes at most 256 MiB; past that, files are listed with Nextcloud's etag plus -0 until their content ETag is known — the suffix keeps tools from taking it for an MD5, which on some storages it looks like. A conditional write computes a missing ETag before taking the object lock, not while holding it.
  • ListParts re-read every part on each call, and CompleteMultipartUpload read them three times. Part ETags are recorded when the part is stored.
  • ListParts and ListMultipartUploads need a readwrite key.
  • The object lock is keyed on where the file really is (storage and path in it), not on the owner's view of it, so a writer coming through a share and one through the owner's own bucket now exclude each other.
  • A request URI that does not account for the routed path is refused (InvalidURI) instead of falling back to the routed path, which is the one that may have lost a slash.
  • XML responses replace what XML cannot carry instead of producing an unparseable document or an empty value.
  • Secrets are marked #[\SensitiveParameter], so a logged stack trace does not carry them. The settings page clears a new secret from the page when its dialog closes.

Changed

  • Operator-visible: a contended conditional write waits up to three seconds for the object lock, not ten, with backoff, before answering 503 SlowDown.
  • Failed signatures and unknown keys are logged as warnings with the key and address, and the brute force entry carries the key. Bucket and key changes and DeleteObjects are logged at info level and to admin_audit.
  • ListParts and ListMultipartUploads page (max-parts/part-number-marker, max-uploads/key-marker/upload-id-marker, prefix). At most 1000 uploads per bucket may be in flight; a bucket at that limit discards its expired uploads itself when the next one starts. Cleanup reclaims abandoned uploads 200 per run.
  • The settings API answers 400 instead of 500 to parameters of the wrong type and to labels over 255 characters.
  • Files whose names no key can spell are left out of listings.
  • README: trusted_proxies behind a reverse proxy, room for tempdirectory.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignaturefC64D77DhXVDBlfKpN1obqnKMaFIHWoLOlgIcui2mrlm4S7i1gb9KSMqoAAcWgsgYX4FhanvorI9fWfua1bR2Qn3j3LlgrPMUuaUnwMbSHJtOu9zL8BxFjxRIWGsHjFKtmAv6VORaE4hLkhmMlOqNNnXcs8/rK2BRBY3lryOz65e9WZY/5E0u4QK9cv3kfH+qk8IMWAsZZF0FY8hF1BX7I+k41i9oWhjQ8d3enaF2/4uSf/tSOdBrLvn9hSsPMkVYC/9HpxJZT2BrnpRgCEztXvsamXIvB88Ncty0oAaGa0I+VLT9EcT2CAHt487WM3B6Bz74092riF26AaEbfoLt3sZGBNEXY7DvQQ0FCUZ4uae+vsHtuxUgU+RcmwCyJb0403cY3qztyWxX4khcdJPF2WjHIfnXAiWZarBqPuKxJ4AjOc9bBK0X2V4YMmoW19fWSpUZL071Rr0jI7371yOpoo3+dCJa0SZre8Dec3rqSrcytIarOrSzkgQWzj+hOgWKQJ28qcjOj+/5mASxnDE9Y8Uka15hPePJ/F+d/uOyd/6kQEYuWLtEbzowr2ERHheu426IzUmPl26YHT30tp9SVN4+VkURvvlhHeHiU2K7+VkVjxnzlbtp7w1OLgmmVqYDZ5Tw7XZiCPTt9NOL0L0UlTyJaQeKbj1+ijSXt1cA+w=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.6.1
Release Details
UpdatedOct. 23, 2026, 7:44 a.m.
Changelog

Fixed

  • A listing prefix was resolved as a path without being checked like a key. Its folder part went straight to Nextcloud, which refuses .. (that surfaced as 403) but folds ./, // and \ into another folder: ./.s3-uploads/ listed the multipart scratch area, which is only hidden at the bucket root, and a/./ listed a/ under keys that do not exist. Such a prefix — and .s3-uploads/ itself — now matches nothing, as no key can contain those segments.
  • A lock held for more than two minutes could be broken by a waiting write. The write under the lock stores the whole body, which on slow storage takes longer than that for a big object; the next conditional write broke the lock and both went ahead, losing an update. A lock is now stale after fifteen minutes. Releasing deletes the request's own row rather than whatever row the key has, so a request whose lock was broken no longer lets a third one in on finishing. Operator-visible: if a PHP process dies while holding a lock, conditional writes to that one key now answer 503 SlowDown for up to fifteen minutes instead of two. Unconditional writes and reads are not affected. To clear it sooner, delete the key's row from oc_s3api_locks.
  • Folder names in a listing prefix that Nextcloud refuses to look up (an admin-forbidden name, a character the storage cannot hold) give an empty listing instead of a 500.

Changed

  • The key rules (segments, length, backslash, NUL, scratch area) live in one place, KeyRules, used both for keys and for listing prefixes.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureUx3QVtNkXhUvzVRAoVG9Gwpnfn2m9qY5WXuzQOjnPYbv90BG5GRDxdNk4999aFYF8GuNo02wf1lqXhxhRfPuZ0XznFxDQ1pRRLnCi3TeysvX00rPbbc3gtotlgVWINihp44Xk0bWNoIjruuxPG/HPsdO4U1vGtPrbEZnPIBUcseggKJ6CEVH1YcfrWHxB1TeT+lwboVjT4VVnePoCkhFgnyJeheSRVF7MgLwi65YjXlD96ErEN5ywyD3tMmfvWhSNem2y/EKA1rgWpoFGxq7pmVNRJwB/9/6bRn3TukS0g71fUf+OoMmFZTuJoV3H01tOivdgyzGCc6aoPNg/B9eulf8ZoD3Vo019GBXW7jI+ZAEJCR/Vi3MFd7A+w+jKzD/hBLaFVvnFo7RCgMet29Go0SuSVqe7c8BiYACE5ydVBZgur68xnMFXuup4JVMcC0hpHqask4vNgZAoiB8t/NIJeqMFvsLwLTZPewBIuxi2qn03EXcldru4c/A9rwP480sC9NPOJHAkVM5CyNgG3rq0sy6vWbbfJyaHmty68BdceXy8MpXJ50dh/9XNgr9LdNmo5dcvHfoq1e8iKj7fqI/+AkejGvwKhO9GISFbAPyifWlS42SWj5d83km2L1cA5lTBgX6YSyjh4K5dz7E6n6VDKu195BmeKsMd5BO2FGl/Kg=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.6.0
Release Details
UpdatedOct. 21, 2026, 11:09 a.m.
Changelog

Security

  • XML request bodies switched entity substitution on instead of off. DeleteObjects and CompleteMultipartUpload parsed their body with LIBXML_NOENT, which — despite the name, and despite the comment next to it — makes libxml substitute entities, and load external ones (file://, php://filter) to do so. DeleteObjects echoes every key back in its response, so outside of Nextcloud that combination reads any file the web server can read, config.php included, for anyone holding a readwrite key. Inside Nextcloud it was not exploitable: core installs an external entity loader that returns nothing, and the app's safety rested entirely on that default. Internal entities were expanded, though — a key could be spelled as &k;. The flag is gone, and a body that declares a DTD at all is refused with MalformedXML: no S3 request carries one, and without a DTD there is nothing to expand, whatever the defaults. The check runs on the parsed document, so a UTF-16 body cannot hide its <!DOCTYPE from it.
  • Keys of a disabled account kept working. Nextcloud serves a disabled user's folder to whoever asks for it, so the S3 endpoint stayed open after the login was closed. A key now authenticates only while its account is enabled. When an account is deleted, its buckets, keys and open uploads are deleted with it — before, a user created later under the same id would have inherited them, working keys included.
  • host and every x-amz-* header must be signed, as on AWS. Unsigned, anyone able to alter a request in flight could have added x-amz-copy-source to a captured PUT and turned it into a copy of another object. Refused with AccessDenied.
  • An unknown bucket name and another bucket of the same account now get the same 404 NoSuchBucket. A key could tell them apart before (403 for the latter) and so list its owner's other buckets, which ListBuckets deliberately does not show.
  • Wrong secrets and unknown access keys count towards Nextcloud's brute force protection (action s3_api_auth), so an address that keeps guessing is slowed down. A stale clock does not count.
  • Objects of a type a browser renders as a document — HTML, SVG, XML, JavaScript — are served with Content-Disposition: attachment, and every object with X-Content-Type-Options: nosniff. A presigned URL opens on the Nextcloud origin, where an HTML object could otherwise pose as a login page.
  • Request bodies, assembled multipart uploads and copied ranges are buffered in Nextcloud's tempdirectory instead of php://temp, which spills to the system temp directory. The file is unlinked as soon as it is open, so nothing is left behind even if PHP dies mid-request.

Fixed

  • A key with an empty segment addressed a different object. Nextcloud collapses runs of slashes in the request URI before routing, so a request for a//b — or /b, as in bucket//b — reached the app as a/b and was served from there; a DELETE removed a/b. The key is now taken from the raw request URI, where the empty segment is still visible, and refused with InvalidArgument like any other key that cannot be a path. Slashes between the endpoint and the bucket name are still tolerated, since a client configured with a trailing slash on the endpoint sends …/s3//bucket/key.
  • A backslash in a key is refused: Nextcloud reads it as a path separator, so a\b was a second name for a/b, with a lock of its own.
  • Keys longer than 1024 bytes answer KeyTooLongError, a path segment over 255 bytes InvalidArgument, and a name Nextcloud refuses (.htaccess) or a write it does not permit (a read-only share, a full quota) 403 AccessDenied — all of these used to be a 500, or for long keys whatever the database made of them.
  • DeleteObjects takes at most 1000 keys, as on S3.
  • An aws-chunked body must decode to the size given in x-amz-decoded-content-length. That also catches an unsigned body cut off before its final chunk, which is accepted when the size fits.
  • A query parameter with a numeric name (?123=x) crashed signature verification with a 500.
  • The source key of CopyObject and UploadPartCopy gets the same validation as a key in the request line. Before, only a .. anywhere in it was refused: a//b and a/./b copied from a/b, a source inside .s3-uploads/ read the parts of an upload in progress, and a valid key such as a..b could not be copied at all.
  • Multipart uploads filled the trash bin. Completing or aborting an upload deleted its scratch folder with an ordinary delete, which Nextcloud's trash bin intercepts: every upload left a full copy of its parts in the owner's trash, plus an empty .s3-uploads folder each time a bucket's last upload finished — twice the space until the trash expired. A part uploaded twice also left the first copy behind as a file version, which deleting the folder did not remove. Scratch data is now deleted past the trash bin (the app answers the trash bin's MoveToTrashEvent for exactly that folder), and a re-uploaded part replaces the old one instead of overwriting it, so no version is created. Objects deleted through DeleteObject still go to the trash, as before. What earlier releases left behind is not cleaned up: trash entries named after upload ids or .s3-uploads expire with the trash, and stray versions remain under files_versions/<bucket folder>/.s3-uploads/, where nothing reads them.

Added

  • An integration test suite (tests/). tests/run.sh starts a throwaway Nextcloud in a container, enables the app and runs pytest against the real endpoint: SigV4 and presigned URLs, conditional writes under concurrency, listings and pagination, ranged reads, multipart and UploadPartCopy, aws-chunked bodies with tampered chunk signatures, read-only keys, and the settings API. It also drives gits3 — its own S3 client and a real push / clone / gc cycle through it — when gits3.py is found next to this repository. The cleanup job and the object locks are covered by ageing rows in the test instance's database: abandoned uploads, stale and held locks, orphaned ETags. Not part of the release tarball.
  • NC_VERSION=master tests/run.sh runs the suite against Nextcloud's daily master build — the next major before it has an image — by swapping the daily tarball into the newest stable image.

Changed

  • Nextcloud 36 is in the supported range ahead of its release: max-version moves from 35 to
  • A major upgrade disables every app whose max-version is below the new server, so declaring it only after 36 ships would switch the app off for everyone upgrading on day one. No code changes were needed. Checked against the daily master build (36.0.0 dev): all OCP classes this app uses still exist and none is deprecated, no deprecated method is called, info.xml validates against master's schema (unchanged since stable35), the code lints clean under PHP 8.5 with E_ALL, the full test suite passes, and the server logs no deprecation for the app while it runs.
  • Nextcloud 32 is dropped; the range is now 33–36. Database results are read with fetchAssociative() and fetchFirstColumn(), which arrived in 33, instead of the PDO-style fetch() and fetchAll(PDO::FETCH_COLUMN). PHP 8.2 stays the floor, which is what 33 and 34 ask for.
  • The walgit example config no longer sets max_retries: walgit's S3 backend never reads it and retries through the AWS SDK's default of three attempts. The README now says what that means for the 503 SlowDown that racing writes into a new folder can get.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureXujSNfzA/FlvrLPf2/1FzN1HQeMtLW9HtARN2EQ9xvMVt5rv+NIWzrgJDwHTAs5FmNQTTpOZ3V2f40rrGGkVG7pwMtHfqsdvbjVJ2axQ2rIk0icDAfigeRncAWsy6kDPtQiZ7/tflRli6geJhduDKIlrGnGVSySXY4nPj2wroIoEwXO/xik1R44ODERlq/SBLLE1XM7uN4+XM7vuZxsHSdE33MhdeIXTbN+b7aWm7TEM1XnjpeImUUMLYuO4VB+cj6wR4y7KFb86J8O2Yy8k9HyTBnGb3GrT0BCIBN4iUcPQiOkHb5JojhcdP6CNYrriCnR+aqZ2I0EkAdu3ovCClmgK1Cm5DvLl4Ev/2YI9OXADI0tfY/RXvlGD+YHlimEOLujDcuZvGzrm5mNBNTXOhVW17dji0knLgBN5D/WoEBuHNaz9i2+Zd4tLySM8ioDTeQul7K98k8YpySqsIC/ISTIufs2OKcqeJqP5+rTx0xWjx5yarAPb8xC4JaDcbudEjL0nOezpgj4Gh0IziVmm6+lweRYNTvJa3uXoGbTLrmCb5YhdUkOeRCNP1fVB+p81APRZ8gyeABj+7/4R0pCWKHBLKCG24yx4/2Usmi4dBtWrrt325qOAcZ3V110tB1Lrn1zKJWCf4+VleajLNoLOIVzLqGkCzd39i+sBKa5kN6U=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<37.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.5.4
Release Details
UpdatedOct. 20, 2026, 6:23 p.m.
Changelog

Fixed

  • Deleting a key that names a folder moved the whole folder to the trash. DeleteObject and the bulk DeleteObjects resolved the key and called delete() on whatever node came back, without checking that it was a file. A DELETE on repos — while repos/a, repos/b, … exist — therefore removed the entire tree. Keys with a trailing slash were already rejected, but the bare prefix was not. In S3, repos does not exist just because repos/a does, so such a delete is now a no-op that answers 204 (or lists the key as deleted in a bulk request), matching what a missing key does. Reads already refused folders this way.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureNlcwlvSMTK5Y2Kj4igfIPrmGlaew8qMNtXC1rhMeyILFNps9IsGo4a0D0aZg6wSm4D7OhaXONfYh1GhLxJizATy/RnZ2Ty312KkJpDdZppjoH17oq7wGL1p8e1MW/d8c+KoY4jEog85yhFCNJhJvED5bxC5cf5zLNanFcPe9W5iNOj2jr0uPpgF06ooXdZHqs5D72/z3bVK13GkfbXR1fiwLDwxPuPcUo9dkVn48ZYNYuCVfy9QnYQzbLjd6wF1Hjtr4+2mGO7K4DxGQBLCUgLRVaUrgiq14jhUsGRrAQyacR4a0YyjTJ/afhBGdl6vEpOuP8dmKj4N7/kzDIEk5apYxSEoK0rUssso6fmGDIeDp7PwfLg7YBRlQ5umsIdMdkQNdUjAANxiR9/CEsbUcq8yLrJl0nE5qVAQdSzh3K2NuPKKjHdDDEh2NC9mUnuZ/csrv8RG9FAqGEFzYujNjFnRC72N7YafDa0Va4m0f9VGdA8Y3pvbo1LLcykle4r43MaP/89YIUWptV8lpxmGEiRM1OXUXjqnvuyOdRAlzpmr5voC88TBZheZkAplWrpnUi4eTH/IBiBYliW8Q6udsS5VRPnYgth9GuwZAhj6GEEz3uPN1UYr5jPjD5r7hulz9nyu+i5MbMPsz0qarXZITJEcMbNf4PyGd7BWTqszoJ68=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.5.3
Release Details
UpdatedSept. 7, 2026, 7:44 a.m.
Changelog

Fixed

  • A conditional write took its lock on the wrong identity, so compare-and-swap could lose an update. The lock key hashed the bucket id, but one file can be reached through two buckets: nothing stopped registering /Documents as foo and /Documents/sub as bar, after which foo/sub/x.txt and bar/x.txt are the same file with two independent locks. Two concurrent If-None-Match: * requests, one per bucket, would then both observe "absent" and both succeed — exactly the silent lost update that content-derived ETags were introduced in 1.2.0 to prevent. The lock is now keyed on the object's resolved storage path, which is what actually identifies the file. Note that hashing the bucket's folder would not have been enough either: in the example above the two buckets have different folders, and it is the full path of the object that has to agree.
  • Registering a folder that another bucket already serves is refused, including the nesting case above. Two names for one folder have no use that a single bucket with a key prefix does not cover better, and they leave the app treating one directory as two buckets — separate multipart scratch areas inside it, two ETag rows racing over one file id. Existing overlapping buckets are left alone: they still work, and now share a lock correctly. The comparison normalizes the path first, because /Documents//sub, /Documents/./sub and /Documents/other/../sub all pass the folder-exists check — Nextcloud collapses those itself — and would otherwise have slipped past the overlap check while naming a folder that is already served.
  • Creating a bucket whose name is already taken answers 409 with a readable message. It used to hit the unique index and surface as a 500 carrying the raw driver error, for what is the most ordinary mistake in that form. The index is still what decides — two racing requests can both pass the pre-check — and that collision now maps to the same 409 rather than a 500.
  • Unexpected failures while creating a bucket no longer return the exception message to the browser; it could carry a filesystem path or driver detail. They are logged instead.

Changed

  • PHP 8.2 is supported again, down from the 8.3 that 1.5.2 briefly required. <php min-version> is resolved against the server, so it has to be satisfiable on the oldest Nextcloud in the range — and 32 (which asks for 8.1) through 34 (8.2) all run on 8.2. Nextcloud 35 does need 8.3, but it enforces that itself, so the lower floor costs its users nothing while 32–34 keep working on the PHP their Nextcloud actually asks for. Verified by linting on 8.1, 8.2 and 8.3; nothing in the app uses a language feature past 8.1, and 8.1 stays out only because it is end of life.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureBxv7eS0izf6Tc6vZ8IQfPug86DhdLGRXQV7NCt8eXIHoFUhIFRx92T6RCoeEM2CnR8Um3S3IgA7zSCx08ckJ7ZITpZV5e2Q76lX9ql5O2sIXX/Rai0udBIl1rb6QXmFwTRFJ/E+zO/dE49hQgIft2LXs+Shctrha5OmvxFzOd8FI8FxjQCPHsYHXX+1lsXqKCUcGepxOof5YOCvDV34tq2jz9VqtmPBe0heUpR7eBJZoCvdPfQHyW4xDW09aAIe/5avZNtUg2EG38kqrPGKrTwOctVVwhNxHGhMQ7pTCgwHtm9SGdUCondbVCDebb23eTEA56ytNRAJKOO9P3DaP2FihIIgkTh8XhEajFbKyhI41EjV65ya4+UUAld0up+J1RNHOgjVQNZhQCPckV6zynBh+Qd2iE5hsmpkDZE/QfJ9SJaU+IkRIoIfdTYmQZsG3uKLTkzSCCL15YjfTNNxTRptoQ3J4u1BO7MSnexD7xqbGpUBwFyRQrA1N+mTQp9DrjvRAXEgNgoFyniYPoRyfyTDRIeyC7ETYKaZH4dyzi8jd7NiYai7dwQ6XuOkoNPw6nyiVSxPxdE0JyHOceb1owVlgfqAOCrefMovJ9EufqJnfSMXOLLcCfU6l4BHug0CjxtA+iAjXa5KzwELxGZazarGJZl/t0vYtolhjWGC8gAE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.5.2
Release Details
UpdatedSept. 7, 2026, 7:03 a.m.
Changelog

Changed

  • Nextcloud 35 is supported: max-version moves from 34 to 35. No code changes were needed — every OCP interface this app uses was checked against stable35 and all 37 of them still exist. TimedJob is the one worth naming: it dropped the deprecated execute() in 33, but this app implements run(), which is still the abstract method. Access control was already declared with #[NoAdminRequired], #[NoCSRFRequired] and #[PublicPage] attributes rather than the docblock annotations Nextcloud logs a deprecation for.
  • The supported range narrows to Nextcloud 32–35 and PHP 8.3+, from 30–34 and PHP 8.1+. Nextcloud 35 requires PHP 8.3, and the app store resolves <php min-version> against the server rather than against Nextcloud's own floor, so a single declared minimum has to cover the whole range. Choosing 8.3 means users on 32, 33 or 34 need a PHP newer than their Nextcloud asks for; that is the deliberate cost of testing against one interpreter instead of four, and PHP 8.1 is out of security support regardless. Nothing in the app needs a language feature past 8.1 — this bounds what is tested, not what parses. Nextcloud 30 and 31 are end of life and go with it.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
Signaturego7SuMzb3Y5g2yrfg9WxL9FjOI7BBbUsyJ0n8LHGRGTH8yus7Nv7zRXzOtP1CmVcR4idihqDAc79SBW8bKaS/ukkYmXAmxXlWUQ9dbF9TyqCPxDJHjsND2WiNS3vC8/TBNzMmHw17QHxF5XpnCV6NzKCqQO6PdinIPBGI83F8V9T2vYDPJY58sV01fXT2fcac4qbTZ7arxvbHjpUatbhwKQLKm6vCqwpuSDZtnFNM0eqNtI9ZWCkuBP170VQr+gZdGD0QDHs2V6JlNx3QWiVAhpdElU3n4+mwtpRFpKvPygB8JzwjBpeO41PywLAqdOv5FD2MhnR1W2ym14942m/IUQUK/kTHR68Q1GP37qn7Gbx5ZYwEhSaMeJLPB6wM21exZMRai46kWTHkxVTFB86w8xZX6ovKqc9SKtM4U+0oLUqia5N3H7EgTFpuT9NSon1VvqHDkjfWtrzCCHIOR3nWpc/rwC+htbDrqTimFM2TNxfAdVJwCIDI8GzHwoHe6biGI73W3Nb9ncNrwnkQS+WgEhHy2FwjA5h5yHhf6ELQckj6Dd21iNQXcs8iqVB5/VnCtWTjDoD5EFIhPdTZeU+n/1DpDlmzNxS4omPigo5w0RQa8m+2W7Ry37xeIvKQyIDRdpD1SUsz4q2OPjYG1wOJTGXkixdjbV1zcEFrJCfupE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.3.0
S3 API 1.5.1
Release Details
UpdatedSept. 5, 2026, 6:51 a.m.
Changelog

Changed

  • First release published to the Nextcloud app store. No code changes: the app metadata gained the bugs element the store requires, an SPDX licence identifier in place of the deprecated agpl shorthand, and the element order the XSD prescribes — info.xml did not validate before. The store also reads CHANGELOG.md and LICENSE from inside the archive, and neither existed at the v1.5.0 tag. Cutting a version rather than moving that tag leaves what v1.5.0 points at unchanged for anyone who already fetched it.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureTafOcWH7r66+n77ARNqExXbcfbgUe7mAjFnI2ZJekbTpEELaNuWcxbWhZJ5cPs7UJLOFRYtI4lWBYFfN46Dlq68SrGetxpgXsQ/fGDrPVOjuM3XWZdMG35uBtOJnOzrr56M/kdhHZ7ppN/kE3cJPFJfeLLXQtHnqpUsnb+LBP0w70uHdih+a/PZ+VZRGI+9C6qcM9GGVvwxl+OP2kH0UYA5ZTF7lNUaNu6t6IGsxW3Kfiv+h5RGsxFTrYJw3iUlgxoLkqO+IxgwbuwoxfFvwhkZuxm2NKMDVhykrmMyeqUntpmgccscZ9YY7h3isS8r8oI0aJEEjNPLOSgeExpV9KQXe8xO55uV+7MXtQaiO+W8j30qAz6loGW6rapfhd6ONFDgd0sOQ7+ScC/5txn7z8n7IX0yFyCdAHwyUhwF93TdUn1Cr/95+uv7EI+jWcXGDIq+3JY7ppFIZg8QJ6fTmjt3J0+iPfhIokY+QL+TBcYWukt01EkcjJJc9NLBDgMqVyl3sV2v5iQfV1IiWClpPvhGyc4uq+JwZx96bPoLuvGEVlvyLz/3vu4TwdFESmUraroNVjzLKx6xfnrHPMZ/IfAqrVAmSqXEijm1L7InKsGm0lmO+yFlpB4Sn1C/MV8tLNIm5tzrB9AspQLxTJqdlXtVpVgVgRdNUwjaczPLXCPA=
Signature digestsha512
Dependencies
Required Nextcloud versions >=30.0.0,<35.0.0
Minimum Integer bits32
PHP>=8.1.0

Nextcloud 32

S3 API 1.5.4
Release Details
UpdatedOct. 20, 2026, 6:23 p.m.
Changelog

Fixed

  • Deleting a key that names a folder moved the whole folder to the trash. DeleteObject and the bulk DeleteObjects resolved the key and called delete() on whatever node came back, without checking that it was a file. A DELETE on repos — while repos/a, repos/b, … exist — therefore removed the entire tree. Keys with a trailing slash were already rejected, but the bare prefix was not. In S3, repos does not exist just because repos/a does, so such a delete is now a no-op that answers 204 (or lists the key as deleted in a bulk request), matching what a missing key does. Reads already refused folders this way.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureNlcwlvSMTK5Y2Kj4igfIPrmGlaew8qMNtXC1rhMeyILFNps9IsGo4a0D0aZg6wSm4D7OhaXONfYh1GhLxJizATy/RnZ2Ty312KkJpDdZppjoH17oq7wGL1p8e1MW/d8c+KoY4jEog85yhFCNJhJvED5bxC5cf5zLNanFcPe9W5iNOj2jr0uPpgF06ooXdZHqs5D72/z3bVK13GkfbXR1fiwLDwxPuPcUo9dkVn48ZYNYuCVfy9QnYQzbLjd6wF1Hjtr4+2mGO7K4DxGQBLCUgLRVaUrgiq14jhUsGRrAQyacR4a0YyjTJ/afhBGdl6vEpOuP8dmKj4N7/kzDIEk5apYxSEoK0rUssso6fmGDIeDp7PwfLg7YBRlQ5umsIdMdkQNdUjAANxiR9/CEsbUcq8yLrJl0nE5qVAQdSzh3K2NuPKKjHdDDEh2NC9mUnuZ/csrv8RG9FAqGEFzYujNjFnRC72N7YafDa0Va4m0f9VGdA8Y3pvbo1LLcykle4r43MaP/89YIUWptV8lpxmGEiRM1OXUXjqnvuyOdRAlzpmr5voC88TBZheZkAplWrpnUi4eTH/IBiBYliW8Q6udsS5VRPnYgth9GuwZAhj6GEEz3uPN1UYr5jPjD5r7hulz9nyu+i5MbMPsz0qarXZITJEcMbNf4PyGd7BWTqszoJ68=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.5.3
Release Details
UpdatedSept. 7, 2026, 7:44 a.m.
Changelog

Fixed

  • A conditional write took its lock on the wrong identity, so compare-and-swap could lose an update. The lock key hashed the bucket id, but one file can be reached through two buckets: nothing stopped registering /Documents as foo and /Documents/sub as bar, after which foo/sub/x.txt and bar/x.txt are the same file with two independent locks. Two concurrent If-None-Match: * requests, one per bucket, would then both observe "absent" and both succeed — exactly the silent lost update that content-derived ETags were introduced in 1.2.0 to prevent. The lock is now keyed on the object's resolved storage path, which is what actually identifies the file. Note that hashing the bucket's folder would not have been enough either: in the example above the two buckets have different folders, and it is the full path of the object that has to agree.
  • Registering a folder that another bucket already serves is refused, including the nesting case above. Two names for one folder have no use that a single bucket with a key prefix does not cover better, and they leave the app treating one directory as two buckets — separate multipart scratch areas inside it, two ETag rows racing over one file id. Existing overlapping buckets are left alone: they still work, and now share a lock correctly. The comparison normalizes the path first, because /Documents//sub, /Documents/./sub and /Documents/other/../sub all pass the folder-exists check — Nextcloud collapses those itself — and would otherwise have slipped past the overlap check while naming a folder that is already served.
  • Creating a bucket whose name is already taken answers 409 with a readable message. It used to hit the unique index and surface as a 500 carrying the raw driver error, for what is the most ordinary mistake in that form. The index is still what decides — two racing requests can both pass the pre-check — and that collision now maps to the same 409 rather than a 500.
  • Unexpected failures while creating a bucket no longer return the exception message to the browser; it could carry a filesystem path or driver detail. They are logged instead.

Changed

  • PHP 8.2 is supported again, down from the 8.3 that 1.5.2 briefly required. <php min-version> is resolved against the server, so it has to be satisfiable on the oldest Nextcloud in the range — and 32 (which asks for 8.1) through 34 (8.2) all run on 8.2. Nextcloud 35 does need 8.3, but it enforces that itself, so the lower floor costs its users nothing while 32–34 keep working on the PHP their Nextcloud actually asks for. Verified by linting on 8.1, 8.2 and 8.3; nothing in the app uses a language feature past 8.1, and 8.1 stays out only because it is end of life.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureBxv7eS0izf6Tc6vZ8IQfPug86DhdLGRXQV7NCt8eXIHoFUhIFRx92T6RCoeEM2CnR8Um3S3IgA7zSCx08ckJ7ZITpZV5e2Q76lX9ql5O2sIXX/Rai0udBIl1rb6QXmFwTRFJ/E+zO/dE49hQgIft2LXs+Shctrha5OmvxFzOd8FI8FxjQCPHsYHXX+1lsXqKCUcGepxOof5YOCvDV34tq2jz9VqtmPBe0heUpR7eBJZoCvdPfQHyW4xDW09aAIe/5avZNtUg2EG38kqrPGKrTwOctVVwhNxHGhMQ7pTCgwHtm9SGdUCondbVCDebb23eTEA56ytNRAJKOO9P3DaP2FihIIgkTh8XhEajFbKyhI41EjV65ya4+UUAld0up+J1RNHOgjVQNZhQCPckV6zynBh+Qd2iE5hsmpkDZE/QfJ9SJaU+IkRIoIfdTYmQZsG3uKLTkzSCCL15YjfTNNxTRptoQ3J4u1BO7MSnexD7xqbGpUBwFyRQrA1N+mTQp9DrjvRAXEgNgoFyniYPoRyfyTDRIeyC7ETYKaZH4dyzi8jd7NiYai7dwQ6XuOkoNPw6nyiVSxPxdE0JyHOceb1owVlgfqAOCrefMovJ9EufqJnfSMXOLLcCfU6l4BHug0CjxtA+iAjXa5KzwELxGZazarGJZl/t0vYtolhjWGC8gAE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.2.0
S3 API 1.5.2
Release Details
UpdatedSept. 7, 2026, 7:03 a.m.
Changelog

Changed

  • Nextcloud 35 is supported: max-version moves from 34 to 35. No code changes were needed — every OCP interface this app uses was checked against stable35 and all 37 of them still exist. TimedJob is the one worth naming: it dropped the deprecated execute() in 33, but this app implements run(), which is still the abstract method. Access control was already declared with #[NoAdminRequired], #[NoCSRFRequired] and #[PublicPage] attributes rather than the docblock annotations Nextcloud logs a deprecation for.
  • The supported range narrows to Nextcloud 32–35 and PHP 8.3+, from 30–34 and PHP 8.1+. Nextcloud 35 requires PHP 8.3, and the app store resolves <php min-version> against the server rather than against Nextcloud's own floor, so a single declared minimum has to cover the whole range. Choosing 8.3 means users on 32, 33 or 34 need a PHP newer than their Nextcloud asks for; that is the deliberate cost of testing against one interpreter instead of four, and PHP 8.1 is out of security support regardless. Nothing in the app needs a language feature past 8.1 — this bounds what is tested, not what parses. Nextcloud 30 and 31 are end of life and go with it.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
Signaturego7SuMzb3Y5g2yrfg9WxL9FjOI7BBbUsyJ0n8LHGRGTH8yus7Nv7zRXzOtP1CmVcR4idihqDAc79SBW8bKaS/ukkYmXAmxXlWUQ9dbF9TyqCPxDJHjsND2WiNS3vC8/TBNzMmHw17QHxF5XpnCV6NzKCqQO6PdinIPBGI83F8V9T2vYDPJY58sV01fXT2fcac4qbTZ7arxvbHjpUatbhwKQLKm6vCqwpuSDZtnFNM0eqNtI9ZWCkuBP170VQr+gZdGD0QDHs2V6JlNx3QWiVAhpdElU3n4+mwtpRFpKvPygB8JzwjBpeO41PywLAqdOv5FD2MhnR1W2ym14942m/IUQUK/kTHR68Q1GP37qn7Gbx5ZYwEhSaMeJLPB6wM21exZMRai46kWTHkxVTFB86w8xZX6ovKqc9SKtM4U+0oLUqia5N3H7EgTFpuT9NSon1VvqHDkjfWtrzCCHIOR3nWpc/rwC+htbDrqTimFM2TNxfAdVJwCIDI8GzHwoHe6biGI73W3Nb9ncNrwnkQS+WgEhHy2FwjA5h5yHhf6ELQckj6Dd21iNQXcs8iqVB5/VnCtWTjDoD5EFIhPdTZeU+n/1DpDlmzNxS4omPigo5w0RQa8m+2W7Ry37xeIvKQyIDRdpD1SUsz4q2OPjYG1wOJTGXkixdjbV1zcEFrJCfupE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=32.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.3.0
S3 API 1.5.1
Release Details
UpdatedSept. 5, 2026, 6:51 a.m.
Changelog

Changed

  • First release published to the Nextcloud app store. No code changes: the app metadata gained the bugs element the store requires, an SPDX licence identifier in place of the deprecated agpl shorthand, and the element order the XSD prescribes — info.xml did not validate before. The store also reads CHANGELOG.md and LICENSE from inside the archive, and neither existed at the v1.5.0 tag. Cutting a version rather than moving that tag leaves what v1.5.0 points at unchanged for anyone who already fetched it.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureTafOcWH7r66+n77ARNqExXbcfbgUe7mAjFnI2ZJekbTpEELaNuWcxbWhZJ5cPs7UJLOFRYtI4lWBYFfN46Dlq68SrGetxpgXsQ/fGDrPVOjuM3XWZdMG35uBtOJnOzrr56M/kdhHZ7ppN/kE3cJPFJfeLLXQtHnqpUsnb+LBP0w70uHdih+a/PZ+VZRGI+9C6qcM9GGVvwxl+OP2kH0UYA5ZTF7lNUaNu6t6IGsxW3Kfiv+h5RGsxFTrYJw3iUlgxoLkqO+IxgwbuwoxfFvwhkZuxm2NKMDVhykrmMyeqUntpmgccscZ9YY7h3isS8r8oI0aJEEjNPLOSgeExpV9KQXe8xO55uV+7MXtQaiO+W8j30qAz6loGW6rapfhd6ONFDgd0sOQ7+ScC/5txn7z8n7IX0yFyCdAHwyUhwF93TdUn1Cr/95+uv7EI+jWcXGDIq+3JY7ppFIZg8QJ6fTmjt3J0+iPfhIokY+QL+TBcYWukt01EkcjJJc9NLBDgMqVyl3sV2v5iQfV1IiWClpPvhGyc4uq+JwZx96bPoLuvGEVlvyLz/3vu4TwdFESmUraroNVjzLKx6xfnrHPMZ/IfAqrVAmSqXEijm1L7InKsGm0lmO+yFlpB4Sn1C/MV8tLNIm5tzrB9AspQLxTJqdlXtVpVgVgRdNUwjaczPLXCPA=
Signature digestsha512
Dependencies
Required Nextcloud versions >=30.0.0,<35.0.0
Minimum Integer bits32
PHP>=8.1.0

Nextcloud 31

S3 API 1.5.1
Release Details
UpdatedSept. 5, 2026, 6:51 a.m.
Changelog

Changed

  • First release published to the Nextcloud app store. No code changes: the app metadata gained the bugs element the store requires, an SPDX licence identifier in place of the deprecated agpl shorthand, and the element order the XSD prescribes — info.xml did not validate before. The store also reads CHANGELOG.md and LICENSE from inside the archive, and neither existed at the v1.5.0 tag. Cutting a version rather than moving that tag leaves what v1.5.0 points at unchanged for anyone who already fetched it.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureTafOcWH7r66+n77ARNqExXbcfbgUe7mAjFnI2ZJekbTpEELaNuWcxbWhZJ5cPs7UJLOFRYtI4lWBYFfN46Dlq68SrGetxpgXsQ/fGDrPVOjuM3XWZdMG35uBtOJnOzrr56M/kdhHZ7ppN/kE3cJPFJfeLLXQtHnqpUsnb+LBP0w70uHdih+a/PZ+VZRGI+9C6qcM9GGVvwxl+OP2kH0UYA5ZTF7lNUaNu6t6IGsxW3Kfiv+h5RGsxFTrYJw3iUlgxoLkqO+IxgwbuwoxfFvwhkZuxm2NKMDVhykrmMyeqUntpmgccscZ9YY7h3isS8r8oI0aJEEjNPLOSgeExpV9KQXe8xO55uV+7MXtQaiO+W8j30qAz6loGW6rapfhd6ONFDgd0sOQ7+ScC/5txn7z8n7IX0yFyCdAHwyUhwF93TdUn1Cr/95+uv7EI+jWcXGDIq+3JY7ppFIZg8QJ6fTmjt3J0+iPfhIokY+QL+TBcYWukt01EkcjJJc9NLBDgMqVyl3sV2v5iQfV1IiWClpPvhGyc4uq+JwZx96bPoLuvGEVlvyLz/3vu4TwdFESmUraroNVjzLKx6xfnrHPMZ/IfAqrVAmSqXEijm1L7InKsGm0lmO+yFlpB4Sn1C/MV8tLNIm5tzrB9AspQLxTJqdlXtVpVgVgRdNUwjaczPLXCPA=
Signature digestsha512
Dependencies
Required Nextcloud versions >=30.0.0,<35.0.0
Minimum Integer bits32
PHP>=8.1.0

Nextcloud 30

S3 API 1.5.1
Release Details
UpdatedSept. 5, 2026, 6:51 a.m.
Changelog

Changed

  • First release published to the Nextcloud app store. No code changes: the app metadata gained the bugs element the store requires, an SPDX licence identifier in place of the deprecated agpl shorthand, and the element order the XSD prescribes — info.xml did not validate before. The store also reads CHANGELOG.md and LICENSE from inside the archive, and neither existed at the v1.5.0 tag. Cutting a version rather than moving that tag leaves what v1.5.0 points at unchanged for anyone who already fetched it.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEATCCAukCAhP5MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxMzIxWhcNMzYxMjEwMTMxMzIxWjARMQ8wDQYD
VQQDDAZzM19hcGkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+U2GK
/dqKekpMDR1KZtVG6e7tditjS8Q1HteEZSu3Tk/vgK0X8IIocrp7V3cFLIQ1dasq
Kw4dWd+SVEfDL8+DRYfTgyHz+MFB0FrFCUFIT4aLL02/0NCJqk7/bkWjFYf/lwRc
kRZIgbecsM1XNjGu5vaehQJU95LL/J3FkHd4QBbKaIEVqagER8wcixnGrDwAwmye
25qXeM+NYC6xiaHfxKsuqHbFWvHYkqvlmtxZ4uKmG9C8XriU850f2dj2NcrygQY4
Oiyp2qvtoaJHnSrld4ihh4X8U0jlWRGyTIa2PU15vFBWvHAq78I39XLeiuwB05eV
r2O7YDkLuBhw2u3X78jgZlCQNky4hsBVeGf9mUpi/K228mq1wkt91ABJe5CDPAjB
lVW8h6ODPS7iNL1jMijioUbmRaDwI41dzmVLTw8sTNizDcJNDBUDtKFr3YPJWP7F
BY4adIhNqrZ2GvOks6O0zCdTZg1a8LJmgBFk7qmX6hn9l8Me6miE6sZVvdK0r31z
N/wPTw8SM9Nt1ggOKGo2XRVEqtt/liQr3b0YZwfDF5bPbkKDNDRyVNi7Ggwo6t9E
veYhWliNGMm10JIvLDcfEWaLLrCf9wcZUEGWEVvj7oc47vYFnMSaS5Ny8TbhtapQ
vOu3EEK8ontze/7bwV5ZHT9Xi9qXIX/7kuEA4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQBApDNQ3r9+6OOEO3BcnyL1RoRErSU7A4anT6XzQO6xSFmTfYopg6T+i9he
Rq3hhF0eVxB5dRRUxTslCGipAAk/rVilycfggwdjbmt4gD5g/FKvcMqfWHjo/Pdi
F6bEvicyKU88i74XTVMxAsrXITOXtcz90QTZOcweBQ8PIPIOWHKjBzo8+0Ytq8o2
GAcb0RBLQcs9wWDAWnXUxy1307tztCrQDvmGHvv+uDe7jPcu2S/DSDH9niVZEzFS
25W9ueKZsmV6ob4bBMYzdl8x/MOL38ZNXFMnsHuYkvmVdYGuND8Wr/N7wbEJquNW
o0eLPhV1dUod0Fqy4ZswszXQhA10
-----END CERTIFICATE-----
SignatureTafOcWH7r66+n77ARNqExXbcfbgUe7mAjFnI2ZJekbTpEELaNuWcxbWhZJ5cPs7UJLOFRYtI4lWBYFfN46Dlq68SrGetxpgXsQ/fGDrPVOjuM3XWZdMG35uBtOJnOzrr56M/kdhHZ7ppN/kE3cJPFJfeLLXQtHnqpUsnb+LBP0w70uHdih+a/PZ+VZRGI+9C6qcM9GGVvwxl+OP2kH0UYA5ZTF7lNUaNu6t6IGsxW3Kfiv+h5RGsxFTrYJw3iUlgxoLkqO+IxgwbuwoxfFvwhkZuxm2NKMDVhykrmMyeqUntpmgccscZ9YY7h3isS8r8oI0aJEEjNPLOSgeExpV9KQXe8xO55uV+7MXtQaiO+W8j30qAz6loGW6rapfhd6ONFDgd0sOQ7+ScC/5txn7z8n7IX0yFyCdAHwyUhwF93TdUn1Cr/95+uv7EI+jWcXGDIq+3JY7ppFIZg8QJ6fTmjt3J0+iPfhIokY+QL+TBcYWukt01EkcjJJc9NLBDgMqVyl3sV2v5iQfV1IiWClpPvhGyc4uq+JwZx96bPoLuvGEVlvyLz/3vu4TwdFESmUraroNVjzLKx6xfnrHPMZ/IfAqrVAmSqXEijm1L7InKsGm0lmO+yFlpB4Sn1C/MV8tLNIm5tzrB9AspQLxTJqdlXtVpVgVgRdNUwjaczPLXCPA=
Signature digestsha512
Dependencies
Required Nextcloud versions >=30.0.0,<35.0.0
Minimum Integer bits32
PHP>=8.1.0