A paused upload request looks like a stopped upload channel. But does changing the request's state also invalidate the authorization already embedded in a saved page?
I tested this with Buckets by Serverless Creed, comparing browser behavior with decoded POST conditions, controlled requests sent directly to S3, and independently verified objects. The results separated three boundaries: page validation, S3 authorization, and application request state.
The upload path and tested configuration
Buckets generates an HTML recipient page containing signed POST fields. The browser submits a multipart request directly to S3, which evaluates the signature, policy conditions and applicable AWS permissions. A saved page carries issued authorization; uploading through it does not require the recipient to sign into AWS.
The experiment used Buckets 2.7.54 on Windows, a local HTML page, and a disposable private bucket in us-east-1 on October 11–12, 2026.
| Setting | Tested value |
|---|---|
| Request | Vendor Upload Test |
| Bucket | buckets-intern-aditi-dropzone-20261011 |
| Object prefix | incoming/vendor/ |
| File-size display | 1 MB |
| File-count limit | 1 |
| Allowed type | text/plain |
| Expiry selection | 1 day |
Bucket creation settings selected Block All Public Access and SSE-S3. Exact OS/browser versions and the signing credentials' lifetime were not recorded.
For reproduction, use the Buckets downloads page, an approved private sandbox, Python 3 and authorized AWS Console/CLI access. The sandbox budget alarm was confirmed before testing; direct POST replay was conducted under reported owner approval.
CORS allowed POST, GET and HEAD, with wildcard origins and headers. Successful browser uploads showed HTTP 200 preflight and HTTP 204 upload responses. CORS controls browser cross-origin access; it does not grant S3 write permission. A non-browser replay client is not subject to browser CORS enforcement. AWS CORS documentation
Establishing a valid baseline
Deterministic files made the size boundary reproducible:
from pathlib import Path
folder = Path("blog03-fixtures")
folder.mkdir(exist_ok=True)
files = {
"valid.txt": b"Aditi upload fixture\n", # 21 bytes
"second.txt": b"Second file\n", # 12 bytes
"wrong.html": b"<p>Harmless test</p>", # 20 bytes
"too-large.txt": b"x" * 1_048_577, # one byte over 1 MiB
}
for name, data in files.items():
(folder / name).write_bytes(data)
The page accepted valid.txt with HTTP 204. AWS Console independently showed the 21-byte object at incoming/vendor/abb7e962f9cc45c9-valid.txt.
The generated token changed the final key, so verification used the actual stored key rather than assuming it matched the filename. Downloading the object and comparing SHA-256 with PowerShell's Get-FileHash produced the same digest as the source:
860DB5A8887A82E09C1C812D0CBBAE23CADDD7D2A39575100A2998387647A758
That established byte integrity as well as upload completion.

Figure 1. Valid 21-byte upload: HTTP 204 with a successful preflight response.
What the browser rejected
Selecting wrong.html produced:
File type 'text/html' not allowed
The cleared, recording Network panel captured no request. This was a page-side rejection, not an S3 policy response. The page already contained a successful upload, so this was not an isolated fresh-session MIME test.

Figure 2. Browser rejection of text/html without a captured upload request.
The 1,048,577-byte fixture produced:
File exceeds 1 MB limit (1 MB)
Both values were rounded to “1 MB,” obscuring a one-byte difference. The screenshot established the displayed rejection, but did not retain Network evidence for that attempt.
A subsequent 21-byte recovery.txt uploaded from the same page without a refresh. HTTP 204 and an independently listed S3 object confirmed recovery after rejected input.

Figure 3. After rejecting the oversized file, the same page accepted a valid 21-byte retry without a refresh.
What the signed policy actually constrained
Decoding the saved policy exposed these conditions. This is a sanitized excerpt, not the complete signed policy:
{
"expiration": "2026-10-12T17:32:08.054Z",
"conditions": [
["starts-with", "$key", "incoming/vendor/"],
["content-length-range", 0, 1048576],
["starts-with", "$Content-Type", "text/plain"]
]
}
The displayed “1 MB” corresponded to 1 MiB: 1,048,576 bytes. The key condition restricted the submitted object key to incoming/vendor/.
The MIME condition was a prefix comparison on the submitted Content-Type field, not strict equality or file-content inspection. Rejecting text/html does not prove that S3 recognizes the bytes as plain text. AWS POST policy documentation
To test S3 independently of the page, a local Python client submitted the saved signed fields as multipart POST requests. A valid baseline succeeded first; each negative then changed one tested boundary while retaining the policy and signature.
| Direct POST test | Changed input | HTTP | S3 result |
|---|---|---|---|
| Baseline | Valid prefix, 21 bytes, text/plain
|
204 | Object created |
| Prefix | Key under outside-vendor/
|
403 |
AccessDenied; failed key-prefix condition |
| Size | 1,048,577-byte file | 400 | EntityTooLarge |
| Submitted MIME | Form field Content-Type=text/html
|
403 |
AccessDenied; failed MIME-prefix condition |
The size response stated: “Your proposed upload exceeds the maximum allowed size.” The MIME replay preserved the plain-text bytes and file part's text/plain header, isolating the changed form field.
Correct multipart construction matters: preserve signed fields, avoid duplicate key or Content-Type fields, generate a matching boundary header, and place the file part last. AWS POST Object documentation
A whole-bucket AWS CLI listing showed five successful objects: four browser uploads and the replay baseline. None of the failed replay keys appeared. Checking only the allowed prefix would have missed an incorrectly accepted outside-prefix object.

Figure 4. S3 rejected prefix, size and submitted MIME violations; independent listing showed only the five successful uploads.
Why one file was not a global quota
After the first upload, selecting second.txt in the same page returned “File limit reached,” with no captured Network request.
Reopening the saved HTML in a new tab allowed that same file to upload with HTTP 204. AWS showed both objects, although the page still displayed a one-file limit.

Figure 5. The existing page rejected a second valid file.

Figure 6. Reopening the saved HTML reset the page counter and allowed the second file.
For this saved-page flow, the counter operated within the page instance. It did not enforce a shared quota across uses of the issued authorization. A global limit requires an authoritative shared admission mechanism; a browser counter alone cannot provide it.
What Pause stopped—and what remained usable
Pausing the request changed its card to “Paused” and disabled the save and host controls. That established the UI behavior; no backend refusal response was captured from those disabled controls.
The previously saved HTML still uploaded after-pause.txt successfully. HTTP 204 and the independently verified 23-byte object incoming/vendor/63ce607d042713f2-after-pause.txt established that issued authorization remained usable during pause.

Figure 7. Pausing disabled the request’s save and host controls.

Figure 8. Previously saved HTML successfully uploaded a file while the request was paused.
After the Drop Zone record was deleted, the controlled POST baseline also succeeded with the saved fields. In this run, pausing or deleting the application record did not invalidate the already-issued S3 authorization.
This distinction matters operationally: stopping new page generation and revoking existing upload authority are separate controls. Hosted-page removal was not tested, so these results apply to saved HTML and direct replay.
Expiry, verification and cleanup
The policy expiration was October 12, 2026, 23:02:08.054 IST. It was inspected, not tested with a fresh POST after that time. Credential validity and AWS permissions can end signed access earlier; the policy timestamp alone does not guarantee a full day of usability. AWS credential-expiration guidance
| Boundary | Evidence | Supported conclusion |
|---|---|---|
| Prefix | Direct 403; failed key absent | S3 enforced the signed prefix |
| Size | Page error; direct 400 | One byte above the bound rejected |
| MIME | Page rejection; direct 403 | Submitted MIME prefix enforced |
| File count | Same-page rejection; reopened-page success | Page-instance scope observed |
| Pause | Disabled controls; saved-page upload succeeded | Existing authorization remained usable |
| Expiry | Decoded timestamp only | Elapsed expiry untested |
Exact-bound acceptance and file-content validation were not tested.
Cleanup removed the request and all five uploaded objects, then deleted the disposable bucket. An independent AWS CLI bucket-list query filtered for its exact name returned [], corroborating deletion.

Figure 9. Independent AWS verification returned an empty list for the deleted test bucket.
Saved HTML and extracted signed fields are authorization material. Keep them private and remove local signed copies after evidence collection; publish only sanitized conditions and responses.
Conclusion
The useful question is not whether an upload page displays a limit, but where that limit is enforced.
In this run, S3 enforced the signed key prefix, file-size range and submitted MIME prefix. The page's file counter reset on reopening. Pausing disabled new generation controls, yet previously saved authorization still worked.
For engineers, the practical lesson is to verify each boundary at its own layer: inspect exact policy conditions, observe browser requests, test approved direct submissions and check actual S3 state. Treat request management, issued authorization and object cleanup as separate operations.
Top comments (0)