Synthetic Industry

Troubleshooting guide · updated 2026-10-11

Direct uploads with signed storage links: what the link allows, how long it lasts and what it does not check

A signed upload link is a bearer token tied to the credentials that made it; design the key, lifetime, after-upload checks and the cleanup of uploads nobody confirms around that, and note where S3-compatible stores differ.

What the link is

A presigned URL lets someone without storage credentials perform one operation, such as uploading a named object, until it expires. AWS documents that the link carries the permissions of whoever created it and that, in effect, it is a bearer token: anyone who holds it can use it. If the object key in the link already exists, the upload replaces it. These three facts decide the design: who may be issued a link, which key it names, and how short its life is.

  • Treat the link as a secret for the duration of its life.
  • Never issue a link for a key the client chose.
  • Do not log full links; they contain the signature.

How long it really lasts

The link expires at the time you set or when the credentials that signed it expire or are revoked, whichever is sooner. A link made from a role session ends when the session ends even if you asked for longer. SDK-created links can be set up to seven days, which is far longer than an upload needs. S3 checks the expiry when the request starts, so a download already underway can finish after expiry, but a restart after expiry fails. A bucket policy can also refuse requests whose signature is older than a set age.

  • Pick minutes, not days, for an upload link.
  • Rotating or deleting the signing key invalidates outstanding links.
  • Test an expired link on staging with a short lifetime.

Fix the key and the content type on the server

Your server should decide where the object goes, for example in a pending area in a folder named for the signed-in user, and what content type it may have. AWS lists a content-type mismatch between the signed link and the upload request as a cause of a signature error, so signing the type means a different type is refused by the store itself. The server never accepts a path from the browser; that is how one user overwrites another's file. It should also record each link it issues, so that the browser later confirms an upload by its identifier rather than by a key.

  • One folder per user inside a pending area, chosen server-side.
  • A random object name inside it.
  • Confirm by upload identifier, looked up for the signed-in user, never by a key the browser sends.
  • Check on completion that the object exists and has the expected declared type and size, then move it to a confirmed area.

What the link does not check

The AWS pages read for this guide do not describe a way for a presigned PUT link to cap file size. Other mechanisms, such as POST uploads with a policy, may enforce a size range on some stores, but that depends on the provider and was not confirmed here (R2, for one, documents no POST support for presigned links). Where a store and SDK let the signed request include the exact content length, the server can take the declared size, check it against the limit and sign it, so the store itself rejects a body of a different length. That is not documented on the AWS pages read, so test it on your store and SDK before relying on it: some SDKs do not sign the header by default. The portable design is to treat the upload as unverified: when the browser says it has finished, the server asks the store for the object's size and declared type, records the file only if both are acceptable, and deletes it otherwise. The type it checks is the one declared in the upload; a file labelled as an image can be anything, and malware or content scanning is a separate step this does not provide.

  • Decide the size limit before building, then test one file over it.
  • Do not mark a file available until the completion check passes.
  • Remove failing objects when the completion check finds them.
  • Until the check runs, the store may hold an oversize object.

Uploads that are never confirmed

The completion check only sees uploads the browser reports. A signed-in user can send a file and never report it, and the object stays in the bucket, costs money and appears in no record. Close that gap with a pending area. Upload links name a key in a pending area; on a successful completion check the server moves the object to a confirmed area, and on a failed one deletes it. A scheduled sweep deletes pending objects older than an agreed age, which must be longer than the link lifetime so a link that is still valid cannot re-create an object afterwards, and an expiry rule on the pending prefix is a backstop. Store expiry rules work in whole days: Amazon S3 works out the expiry as creation time plus the days, rounded up to the next midnight UTC, removes the object asynchronously and may do so some time after that date, and Cloudflare R2 typically removes objects within 24 hours of the expiration time. So the sweep does the quick cleanup and the rule catches what it misses. Also have the server count how many unconfirmed uploads a user has open, and refuse another link at an agreed number.

  • Test it: upload, never confirm, run the sweep with a short age on staging, and check the listing.
  • Leave confirmed files and young pending objects alone; test that too.
  • Read the rule back from the bucket after your administrator applies it.
  • Abort rules for multipart uploads are a different lifecycle action and are not needed for single presigned uploads.

S3-compatible stores differ

Cloudflare's R2 documents that it supports presigned GET, HEAD, PUT and DELETE links but not POST, allows lifetimes from one second to seven days, works only against its S3 API hostname rather than a custom domain, and needs CORS rules before a browser can use a link. Signing the content type there also makes a different type fail. If your store is described as S3-compatible, check each of these before choosing a mechanism. Keep the bucket private, and on AWS turn on Block Public Access, which overrides policies and ACLs that would otherwise open it.

How the paid outcome is accepted

The signed-upload outcome is accepted when an expired link is refused, a wrong content type is refused, an oversize or wrong-type file is never recorded and leaves no object once the completion step has run, another user cannot obtain a link for the first user's key or confirm their upload, anonymous access is denied, an upload that is never confirmed is removed by the sweep after the agreed age while confirmed files stay, and the expiry rule on the pending prefix is read back as written. The fixed £395 test price is untested and payment follows your sign-off.

Sources and limits

  • AWS: download and upload objects with presigned URLs Checked 2026-10-11.
    • A presigned URL carries the permissions of the principal that created it and works as a bearer token for whoever holds it.
    • It expires at its configured time or when the signing credentials expire or are revoked, whichever comes first.
    • Uploading to the URL replaces an existing object with the same key; a bucket policy can deny requests whose signature is older than a set age.
  • AWS: uploading objects with presigned URLs Checked 2026-10-11.
    • A content-type mismatch between the signed URL and the upload is a listed cause of SignatureDoesNotMatch.
  • AWS: Block Public Access Checked 2026-10-11.
    • Block Public Access settings override bucket policies and ACLs that would allow public access.
  • Cloudflare R2: presigned URLs Checked 2026-10-11.
    • R2 supports GET, HEAD, PUT and DELETE presigned URLs but not POST, with expiry from one second to seven days, on the S3 API hostname only, and needs CORS rules for browser use.
  • AWS: lifecycle configuration elements Checked 2026-10-11.
    • A lifecycle rule can filter by key prefix and expire objects a number of days after creation; the time is the creation time plus the days, rounded up to the next midnight UTC.
    • Aborting incomplete multipart uploads is a separate lifecycle action from expiring objects.
  • AWS: expiring objects Checked 2026-10-11.
    • Expired objects in a non-versioned bucket are removed asynchronously, and there may be a delay between the expiration date and removal.
  • Cloudflare R2: object lifecycles Checked 2026-10-11.
    • R2 lifecycle rules can delete objects after a number of days, scoped to a prefix; objects are typically removed within 24 hours of the expiration time.