Job integrate-s3-presigned-uploads-for-user-files · revised 11 October 2026
Let users upload files straight to private S3-compatible storage with expiring links
Signed-in users upload one kind of file straight to a private bucket through an expiring link. Files are recorded only after a size and type check; failed or unconfirmed uploads are removed.
You might be seeing
- Uploads fail or slow down the app for everyone when a user sends a large file
- Files are reachable by anyone who holds the address
- The upload feature accepts any type or size, or lets a user choose where the file lands
No passwords, keys, card details or admin invites needed to start.
What usually happened
A signed storage link is a bearer token: whoever holds it can perform the signed action until it expires, and it lasts only as long as the credentials that signed it. Issued carelessly, it lets users choose any file name, overwrite others' files, upload types or sizes you did not intend, or open a bucket to the world. Even a careful design that checks each upload when the browser says it has finished leaves a gap: a signed-in user can send a file and never report it, and the object then stays in the bucket, costs money and appears in no record. Made carefully, the server controls who may upload where while the bytes go straight to storage, and anything never confirmed is cleaned up.
Who it’s for: A founder or product owner whose app accepts files, such as profile photos or supporting documents, through the app server or a public bucket, and who wants them stored privately without the server carrying every byte.
Usually starts when: Large uploads time out or load the server, files sit in a bucket anyone with a link can open, or a first attempt at direct upload let users overwrite each other's files.
The result: One agreed upload feature issues a link for a key the server chooses in a pending area of the bucket, in a folder named for the user, with a short lifetime and a fixed content type. A file is recorded and moved to the confirmed area only after the server confirms the stored object's size and its declared content type. An oversize or wrong-type object is not recorded and is removed; an upload that is never confirmed is removed after an agreed time; and expired links, changed content types, other users' keys and anonymous access to the bucket are refused. Type means the content type declared when the link was signed, not an inspection of the file's contents.
Check whether this job fits
Answer from what you know about your uploads and storage. It needs no keys, bucket names or files.
Checks you can run yourself
Check whether your bucket is public
In your storage console, open the bucket's access settings and note whether public access is blocked. Do not share the bucket name or any file.
Look for: Whether the bucket blocks public access and whether a policy grants access to everyone. Either answer is useful in the enquiry.
What you get
- A pull request with the endpoints, the completion check, the cleanup sweep, the download link and tests
- Written bucket and credential settings for your administrator, with the reason for each, including the expiry rule on the pending prefix
- A table of the refusal and cleanup cases tested and the response or result of each
- Undo steps
Included
- One upload feature for one file purpose, for one S3-compatible store chosen at agreement: Amazon S3 or a store that implements its signed-link behaviour
- A server endpoint that checks the signed-in user, chooses the object key in a pending area of the bucket, in a folder named for that user, records the pending upload, signs a short-lived upload link with a fixed content type and returns it; a user may hold only an agreed number of unconfirmed uploads at once
- A completion step, called with the upload's identifier (never a key), in which the server checks the stored object's existence, size and declared content type before the app records the file and moves it to the confirmed area, and removes an object that fails the check
- Cleanup of uploads that are never confirmed: a scheduled sweep that deletes pending objects older than an agreed age (always longer than the link lifetime, so a link that is still valid cannot re-create an object after the sweep), and an expiry rule on the pending prefix as a backstop, written for your administrator to apply
- Where the chosen store and SDK allow the signed request to include the exact content length, the server takes the declared size, checks it against the limit and signs it so the store itself rejects a body of a different size; whether this works is tested on the chosen store at agreement, because the AWS pages we read do not document it
- A signed download link for the file's owner only
- Bucket settings written for your administrator: private by default, cross-origin rules for your site only, an expiry rule on the pending prefix, and a least-privilege key limited to one prefix
Not included
- Malware or content scanning, inspection of what a file actually contains (a file labelled as an image is checked only by its declared type), image resizing and thumbnails
- Resumable or multipart uploads of very large files
- Moving existing files into the new store
- Content delivery network set-up or public file hosting
- Setting up the cloud account, billing or the bucket itself, which your administrator does from our written settings
- Any promise about the contents users upload
How we know it’s done
Agreed with you before work starts. Each check produces evidence you keep.
A signed upload link used after the agreed staging lifetime has passed is refused by the store, and a link used within it accepts the file.
Evidence: Both responses with timestamps and the object listing before and after.
Uploading with a content type different from the one the link was signed for is refused, and an upload with the right type completes and is recorded by the app.
Evidence: Both responses and the app's file record.
A file over the agreed size limit and a file of a disallowed declared type end up not recorded as available in the app, and no object remains for them in the bucket once the completion step has run. If signing the content length works on the chosen store, a body of a different length from the declared size is also refused by the store itself.
Evidence: App records and a bucket listing after each attempt, and, where signed length is used, the store's refusal response.
A second test user cannot obtain a link for the first user's key, cannot confirm or download the first user's upload, and an anonymous request for any object in the bucket is denied.
Evidence: The refused responses for each attempt and the bucket's public-access settings as shown by your administrator.
An upload that is never confirmed is deleted by the sweep once the agreed pending age has passed (shortened on staging, for example to ten minutes), while a confirmed file and a pending object younger than that age are left in place.
Evidence: Bucket listings of the pending and confirmed areas before and after the sweep, with object ages, and the sweep's log line.
The bucket carries the expiry rule on the pending prefix as written in the settings document, and a user who already holds the agreed number of unconfirmed uploads is refused another link.
Evidence: The rule as read back from the bucket by your administrator (prefix, number of days, enabled), and the refused response with the pending count.
Sign-off. You read the refusal and cleanup table, the responses and the bucket settings, then sign off in writing and merge. Payment follows sign-off; your team deploys and holds the production keys.
If it fails. If the agreed checks do not pass you do not pay for this fixed scope. If the cause is the store's signing support, regulated file contents or a public-file need, we explain the evidence and stop. Wider work needs a new written agreement.
When it fits, and when we stop
It fits when
- The app has signed-in users and a server where the link can be issued
- You have, or will create, a bucket on a provider that implements the signed-link behaviour, and a person who can create a restricted key
- The upload purpose, accepted types and size limit are written down before work starts
- A staging bucket and site address can be used for tests
- You agree how long an unconfirmed upload may stay before it is removed, and how many unconfirmed uploads one user may have open at once
- Your maintainer can review, merge and deploy the change
We stop and tell you if
- The files contain regulated or special-category personal data and your own review has not approved the storage location
- Your store does not support the signed-link behaviour the design needs, and no alternative mechanism is agreed
- Files must be publicly viewable from the bucket, which is a different design
- Users are not signed in, so the server cannot decide who may upload
What could go wrong
Before merge, closing the pull request changes nothing. After merge, reverting the commit removes the upload route; files already stored remain in your bucket under your control, and your administrator can revoke the key to invalidate any outstanding links.
Scroll the table sideways to read it all.
| Risk | How we handle it |
|---|---|
| A user overwrites another user's file. | The server chooses the object key under the user's own prefix and never accepts a client-supplied path; a test tries the other user's key. |
| A signed link outlives its purpose or is shared. | A short lifetime, a fixed content type, a completion check, and the signing credentials' own expiry, all tested. |
| An oversize or wrong-type file is accepted because the store did not stop it. | The completion step checks size and declared type and removes a failing object before the app records it, and the cases are tested whatever the store enforces. Until the check runs, the store may hold the object. |
| A signed-in user uploads a file and never confirms it, so it stays in the bucket, costs money and is recorded nowhere. A user could repeat this many times. | Uploads land in a pending area, the server records each issued link and refuses more than an agreed number of open ones per user, a sweep deletes pending objects older than the agreed age, and an expiry rule on the pending prefix removes anything the sweep missed. A test uploads and never confirms. |
| A file labelled with an allowed type is something else, because only the declared content type is checked. | The page and handover say that type means the declared content type. Malware scanning and content inspection are excluded and can be quoted separately. |
A reviewer separate from the builder checks the diff, that the key is chosen by the server, that the bucket stays private, that the staging key is limited to one prefix and that no key is logged. Your authorised maintainer merges and your administrator applies the production settings.
How we deliver
We arrange the work and independent review, then show you the result against the agreed checks. You keep authority over your systems.
- Agree the provider, the file purpose, accepted types, size limit, link lifetime, key prefix rule, the age at which unconfirmed uploads are removed and the per-user limit on open uploads
- Read how uploads and file records work today and where a user's identity is available
- Write the refusal-case table first, with expected responses
- Build the signing endpoint, completion check, cleanup sweep and owner download link on a branch, with tests against a staging bucket, and test whether the chosen store enforces a signed content length
- Give your administrator the bucket and key settings, including the pending-prefix expiry rule, then run the agreed cases on staging with two test users, including an upload that is never confirmed, and capture the evidence
- Have an independent reviewer check the diff and settings, then hand over with undo steps
An enquiry books nothing and charges nothing. Scope, access route and checks are agreed in writing first.
Need to keep it working?
If storage credentials and cost matter, ask about the monthly service that checks credentials, quotas and provider changes.
Ongoing work is separately scoped and quoted: no monitoring, response-time guarantee or automatic subscription is included in this job.
Explore an ongoing engineering lane, or mention the responsibility you need in your enquiry.
What you can check
This is a new service. We have not delivered this job for a client yet.
Other ways to get this done
- AWS documents how presigned URLs are made and limited; your developer can follow it to build the same flow. docs.aws.amazon.com
- If files must pass through your server for scanning or processing anyway, direct upload may not be the right design.
Questions
Why not let the browser choose the file name?
A client-chosen key lets a user overwrite someone else's file. The server chooses the key under the user's own prefix.
Does the store reject oversize files itself?
That depends on the provider and signing method. The job does not rely on it: a completion step checks the stored size and the declared type and removes a failing file before the app records it. Until then the store may hold the object, which is why uploads that are never confirmed are swept away after an agreed time. Where the store and SDK let the signed request include the exact length, the job uses that too, after testing it.
What happens to a file someone uploads but never confirms?
It sits in a pending area, the server limits how many a user can have open, a scheduled sweep deletes it after the agreed age, and an expiry rule on the pending prefix is a backstop. Store expiry rules work in whole days, so the sweep does the quick cleanup.
Will this work with my S3-compatible provider?
Possibly. Providers differ, for example in which signed-link methods they support, so we check the provider named in your enquiry before quoting.
Do you scan uploads for malware, or check what is inside?
No. The check is on the declared content type and the stored size, not the contents, so a file labelled as an image could be something else. Scanning and content inspection are separate jobs and the app makes no promise about what users upload.
Send an enquiry
Send us
- The app's language and framework, and what is uploaded and why
- The storage provider you use or prefer, and the largest file and accepted types
- Whether uploads currently pass through your server, and any public-bucket setting you know of
- Do not send access keys, bucket names with real data, user files or code in the first enquiry
Later, once you agree
- Read access to the code through a company-controlled repository or export, and a staging environment you control
- A staging bucket and a restricted staging key created by your administrator from our written settings
- Two test users, and sample files of the agreed types, one too large and one of the wrong type, plus the agreed pending age and per-user limit
- The staging and production site addresses to allow in the bucket's cross-origin rules
You own the storage account, the bucket, the keys and the files. We work on a branch through a company-controlled identity and use a restricted staging key limited to one prefix. Production keys never leave your secret store.
Email fallback: open your mail app
If website submission is unavailable, review and send the fallback email yourself. An email fallback is not a website receipt. Or write to hello@syntheticindustry.ai with “integrate-s3-presigned-uploads-for-user-files” as the subject.