DEV Community

Aditi Dange
Aditi Dange

Posted on

Testing S3 Drop Zone Upload Limits: What Happens When You Pause a Request?

 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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

That established byte integrity as well as upload completion.

Recipient page showing valid.txt uploaded with 21 bytes beside Network responses of 204 and 200.
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
Enter fullscreen mode Exit fullscreen mode

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.

wrong.html rejected as text/html beside an empty recording Network panel.
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)
Enter fullscreen mode Exit fullscreen mode

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.

Oversized-file error retained while recovery.txt shows Uploaded and Network records HTTP 204.
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"]
  ]
}
Enter fullscreen mode Exit fullscreen mode

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.

AWS CLI output showing prefix and MIME rejection with 403 AccessDenied, size rejection with 400 EntityTooLarge, and five accepted objects.
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.

Same page showing valid.txt uploaded and second.txt rejected with File limit reached.
Figure 5. The existing page rejected a second valid file.

Reopened page showing second.txt uploaded while still displaying the one-file limit.
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.

Buckets request marked Paused with save and host controls disabled.
Figure 7. Pausing disabled the request’s save and host controls.

Previously saved page showing after-pause.txt uploaded with 23 bytes and HTTP 204 while the request was paused.
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.

AWS CLI bucket-list query filtered for the deleted test bucket returning an empty JSON array.
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)