S3 Signature Requirements

On 23 August 2026 we applied a security update to DanubeData Object Storage. It tightened how request signatures are checked, and it can cause uploads to fail while downloads keep working.

A temporary compatibility setting is in place, so uploads work normally until 30 August 2026. If one of your clients is affected, correct it before that date.

The rule

Our S3 endpoint enforces the standard AWS Signature Version 4 rule. Three groups of headers must be covered by your request signature whenever they are present:

  • the Host header
  • every x-amz-* header
  • the Content-Type header

Most SDKs already do this and need no change. Two common cases do not, and both are covered below.

Why the update was applied

The update fixes CVE-2026-54330, rated Important, along with three other vulnerabilities disclosed on 19 August 2026. Before the fix, a request could carry headers the signature did not cover, which meant anyone holding a presigned upload URL could attach extra headers and obtain more read and write access than whoever issued that URL intended. Because that directly exposes customer data, we applied the update within days of disclosure rather than waiting for a quieter maintenance window.

Independent references: Red Hat and CERT-FR advisory CERTFR-2026-AVI-1057.

How to recognise an affected request

A rejected request returns:

  • HTTP status 403
  • Error code AccessDenied
  • An empty <Message> element

The request is refused before any permission check, which is why the symptom is confusing:

  • uploads fail, but downloads and listings still succeed
  • access keys marked Full are still rejected
  • the bucket policy is correct and unchanged

If you see that combination, this rule is the cause. It is not a permissions problem, and rotating your keys will not help.

A request that fails the signature check itself returns SignatureDoesNotMatch instead. That is a different problem, usually a wrong secret key, a clock difference of more than 15 minutes, or a mismatch between the header you signed and the header you sent.

Case 1 — presigned upload URLs

If your presigned URL signs only host but the uploading client sends a Content-Type header, the upload is rejected. Browsers always set Content-Type when uploading a file, so this affects most browser upload flows.

You can see what a URL covers by looking at its X-Amz-SignedHeaders parameter.

Preferred fix: sign the content type

Pass the content type when generating the URL, then send exactly that header. The signed URL will then show X-Amz-SignedHeaders=content-type;host.

AWS SDK for JavaScript v3

JavaScript
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'

const url = await getSignedUrl(
  s3,
  new PutObjectCommand({ Bucket: bucket, Key: key, ContentType: file.type }),
  { expiresIn: 600 },
)

await fetch(url, {
  method: 'PUT',
  headers: { 'Content-Type': file.type },
  body: file,
})

boto3 (Python)

Python
url = s3.generate_presigned_url(
    'put_object',
    Params={'Bucket': bucket, 'Key': key, 'ContentType': 'image/jpeg'},
    ExpiresIn=600,
)

The header you send must match the one you signed exactly. If they differ you get SignatureDoesNotMatch.

This is the recommended option because the stored object keeps its correct content type, which matters when the bucket serves files publicly.

Alternative fix: send no content type

Do not send a Content-Type header at all.

JavaScript
await fetch(url, { method: 'PUT', body: await file.arrayBuffer() })
Bash
curl -T photo.jpg "<presigned-url>"

This works immediately, but objects are then stored as binary/octet-stream.

Quick check

Take a fresh presigned URL and run:

Bash
curl -T photo.jpg "<presigned-url>"

A 200 proves your keys and bucket are fine and that no key rotation is needed.

Case 2 — the AWS SDK for PHP

aws-sdk-php never covers Content-Type in its signature, so any upload it makes that carries that header is rejected. The SDK cannot be configured to sign it without a custom signer class.

If you control the code, the options are:

  • upload without setting ContentType, or
  • subclass the SDK's SignatureV4 to remove content-type from its header blacklist, or
  • upload through your own backend instead of a presigned URL

If you would like the signer subclass we use ourselves, open a ticket and we will share it.

This also affects appliances that bundle the SDK. The most common is Synology Hyper Backup — see Synology Hyper Backup for a complete walkthrough.

Signature Version 2

Signature Version 2 covers Content-Type as part of the signature by design, so it is unaffected by this change and remains supported on our endpoints. It is the recommended workaround for appliances whose S3 client cannot be updated.

Signature Version 4 is still preferred wherever your client signs correctly, as it is the stronger scheme.

Clients confirmed working

No change is needed for the AWS SDK for JavaScript, boto3 and other Python clients, the AWS SDK for Go, the AWS SDK for .NET, rclone, MinIO clients over HTTPS, or s3cmd.

Endpoints

EndpointNotes
https://s3.danubedata.roPrimary endpoint
https://new-s3.danubedata.roAlso available

Both endpoints accept path-style (https://s3.danubedata.ro/your-bucket) and virtual-hosted-style (https://your-bucket.s3.danubedata.ro) addressing, and both Signature Version 4 and Signature Version 2.

Need help?

Open a ticket from your dashboard. We can look at your bucket's request logs and tell you whether anything you run will break on 30 August 2026, and help you adjust a client or a backup appliance before then.