Skip to main content
Technology & EngineeringSecurity Practices61 lines

Private Image Upload Acceptance Test

Verify an existing private image upload path with real byte-level and authorization tests, from bounded ingestion and normalization through authenticated preview and an approved media revision. Use for image upload regressions or acceptance review, not arbitrary malware scanning or live social publishing.

Quick Summary12 lines
Produce evidence that the application accepts the intended images, stores the transformed bytes, and keeps the approved image tied to its owner and exact revision. An upload response alone does not establish these properties.

## Key Points

- [OWASP File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html)
- [sharp constructor and decoder limits](https://sharp.pixelplumbing.com/api-constructor/)
- [sharp output metadata and processing timeout](https://sharp.pixelplumbing.com/api-output/)
- [GCS object generations](https://docs.cloud.google.com/storage/docs/metadata)
- [GCS request preconditions](https://docs.cloud.google.com/storage/docs/request-preconditions)
- [GCS signed URL capabilities](https://docs.cloud.google.com/storage/docs/access-control/signed-urls)
skilldb get security-practices-skills/private-image-upload-acceptance-testFull skill: 61 lines
Paste into your CLAUDE.md or agent config

Private Image Upload Acceptance Test

Produce evidence that the application accepts the intended images, stores the transformed bytes, and keeps the approved image tied to its owner and exact revision. An upload response alone does not establish these properties.

Establish the test boundary

Read the application's supported formats, byte/pixel/dimension limits, metadata policy, ownership model and delivery interface. Record the actual decoder version. Treat these as product requirements: one application's JPEG-only policy, size cap or signed-URL lifetime is not a universal standard.

Use an isolated local fixture or an explicitly authorized test environment, synthetic images and two test identities. Keep external publishing disabled. Replacing objects, creating upload records and testing another identity belong inside that authorized scope; do not turn a production read-only review into mutation tests. A setup or observer failure is unavailable evidence, not an acceptance pass.

Follow the bytes

Create a small valid image with a recognizable pixel pattern, known dimensions, an orientation tag and harmless metadata. Record the raw upload hash. Upload it through the real HTTP path, then retrieve its authenticated preview and inspect that returned byte sequence with a decoder.

Check the actual output format, displayed dimensions after orientation, expected metadata removal, output byte cap and a hash of the normalized bytes. Compare these with the saved record and approved reference where the test interface permits it. Do not compare only a normalizer's return value while the storage path may still save the original input. Hashes of source and normalized output are different identities; neither proves authorization.

A useful negative control deliberately computes valid normalized metadata but stores the original bytes. The same acceptance checks must detect the mismatch or failed preview. Report the observed request, response and byte facts rather than accepting a test case's name as its result.

Exercise bounded rejection

Use small fixtures to test an invalid payload with an allowed MIME header and plausible image prefix, an unsupported format, and a file whose declared dimensions exceed a deliberately small test pixel budget. A matching filename, MIME type or signature is not sufficient evidence that pixels decode correctly.

Send a bounded stream exceeding the application cap without relying on Content-Length. Observe rejection and verify no usable asset was stored. Exercise a stalled stream only when the environment provides a bounded timeout test. Byte, decoder-pixel and request-time limits cover different resources. A decoder processing timeout may exclude time queued for a worker; do not call it an end-to-end deadline.

Decode and re-encode to an allowed output format, but do not claim this removes every malicious payload. Keep the decoder current and resource-limited; a fixture does not assess all parser vulnerabilities. Avoid giant allocations or real decompression bombs just to test a configured limit.

Verify owner and revision continuity

Use the exact returned asset ID as the owner, a second identity and an unauthenticated caller. The latter two must not obtain private bytes or attach the owner's asset merely by knowing its identifier. Check response data as well as status. Keep actual account credentials out of evidence.

When the application has a separate privileged delivery resolver, verify that ordinary callers cannot use it. Only use a fake signer or an explicitly authorized test capability; do not publish externally. Upload/preview responses should expose no unintended storage bearer capability. A signed URL grants access to its holder even when the underlying bucket is private, so redact it from reports and logs.

Freeze the approved asset reference before a controlled storage replacement. A correct path either continues to the exact approved version or rejects the changed/missing version. It must not silently deliver the latest bytes under the old approval. Compare the bytes reached by the test delivery capability with the approved normalized hash, rather than merely trusting the resolver's claimed hash.

For GCS, object generation identifies a version; the same name can refer to new bytes. Selecting a specific generation differs from a precondition against the current object. ifGenerationMatch=0 prevents overwriting a live object during creation, but does not verify future delivery. Other stores need their own documented immutable-version or conditional-read mechanism.

Test corrupted stored bytes or a forged approved hash separately from a newer valid version. Changed bytes must not be signed as if they matched the old review. A storage/signing error remains an explicit failure or unavailable outcome; no public-storage fallback should make the test pass.

Record what was proved

Save input/output hashes, decoder version, product limits, request identities represented by harmless labels, HTTP status, observed metadata and the exact failed invariant. Distinguish valid acceptance, expected rejection, detected regression and unavailable evidence. Count attempted tests separately from successful uploads or deliveries.

Run at least one positive path and one deliberately faulty control. Keep fixture implementation and expected answers separate from a forward evaluator when practical. A passing fixture establishes these exercised boundaries only; it does not prove production IAM, provider retrieval, exhaustive upload security, general agent improvement or real adoption.

References and rights

Primary documentation checked 2026-10-27:

Original SkillDB-authored task procedure under LicenseRef-SkillDB-Content. References verify behavior; no source prose or example code is copied. The public adapter's MIT license does not grant MIT rights to catalog skill bodies.

Install this skill directly: skilldb add security-practices-skills

Get CLI access →