TC-EDGE-003: Upload Sanitization & Storage
ð New on stage â 2026-06-25 â 2026-07-01. This TC covers the canonical upload-hardening wave: filename sanitization on every upload (
sanitizeUploadFilename, #444/G16), bare-S3-key storage + read-time presign for logos/avatars/proofs (#451/#348/#406/#407), and the external-avatar-URL redirect guard (#410) that stops the image-redirect service from mangling a member's external avatar link. See the Test Plan and the Jun 25 release / Jul 1 release notes.
Module: Edge Cases â Upload Sanitization & Storage
Primary Test User: admin@rcme.membervu.com / Admin123!; testmember@rcme.membervu.com / Member123! for avatar/proof upload; treasurer@demo.membervu.com / Treasurer123! for payment-proof review
Priority: P1 (path-traversal / header-injection hardening on a public upload surface)
Objectives
- Upload a file whose name is a path-traversal / control-char / formula-injection payload and confirm the platform neutralizes it â never 500s, never stores the raw hostile string.
- Confirm avatars/logos/payment proofs are persisted as a bare S3 key (not a full or presigned URL) and resolve to a working image for an anonymous/authorized viewer via a fresh presigned URL on every read.
- Confirm an org logo saved before this hardening (a legacy full-URL value) still renders after the deploy â no backfill migration was run.
- Confirm a member's external avatar URL (e.g. an outside image host) is passed through untouched, not rewritten into a broken redirect-service link (#410).
Scenario summary
| ID | Scenario | Key data assertion |
|---|---|---|
| EDGE3-01 | Path-traversal filename (../../etc/passwd) | Upload succeeds (no 500); the Upload.originalFilename audit row is sanitized to passwd.bin â no / or .. survives. |
| EDGE3-02 | Control-char / whitespace filename | Upload succeeds; sanitized name is ASCII-only, no tabs/newlines/control bytes, extension preserved. |
| EDGE3-03 | Formula-injection-style filename (=cmd|'/c calc'!A0.png) | Upload succeeds; sanitized name has no leading = and no |/'/! â e.g. c_calc_A0.png. |
| EDGE3-04 | Avatar/logo/proof stored as bare key | DB column (Member.avatarUrl / OrgProfile.logoUrl / Payment.proofUrl) holds a bare storage key like avatars/<tenant>/<member>/avatar-âŠjpg â not a full https://⊠URL. |
| EDGE3-05 | Presigned read for anonymous/authorized viewer | The image loads after a hard refresh; the actual <img>/network-request URL carries a fresh presigned query string (or redirect-service path) each time â not a stale expired signature. |
| EDGE3-06 | Legacy full-URL logo still renders (no backfill) | An OrgProfile.logoUrl left as a pre-hardening full URL still resolves via presignStoredAsset's URL-parsing fallback â renders without needing a data migration. |
| EDGE3-07 | External avatar URL passed through untouched (#410) | A member with avatarUrl set to an external host (e.g. https://robohash.org/âŠ) renders that exact URL â it is not rewritten into an /images/members/⊠or /secure-images/members/⊠redirect path. |
Not-in-scope
There is no admin UI page listing raw
Upload audit rows â the sanitized originalFilename is backend-only. Verify it via the served Content-Disposition filename (a generated key, e.g. proof-<timestamp>-<random>.ext â NOT the sanitized display name) plus the absence of any 500/crash, not a direct UI read of the audit row.
â¡ïž Full click-by-click steps: TC-EDGE-003 (Detailed).