From 0511b19256d9344e7fd3b4dbdcc379ed36198c82 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 6 Sep 2026 17:40:40 +0000 Subject: [PATCH] =?UTF-8?q?=EC=9E=98=EB=AA=BB=EB=90=9C=20=EB=93=9C?= =?UTF-8?q?=EB=9E=98=EA=B7=B8=20=EC=95=A4=20=EB=93=9C=EB=A1=AD=20=EC=A0=9C?= =?UTF-8?q?=EC=B6=9C=EC=9D=84=20=EB=B0=A9=EC=A7=80=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=B8=A1=20=ED=8C=8C=EC=9D=BC=20=EC=9C=A0=ED=98=95?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/palette.md | 4 ++++ saas_web.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/.jules/palette.md b/.jules/palette.md index 2dcd639e..cbdaebc8 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -81,3 +81,7 @@ ## 2024-08-04 - 숫자 입력 필드 빈 문자열 상태 초기화 처리 **학습:** 숫자 입력 필드에서 빈 문자열('')을 입력할 때 브라우저는 이전의 유효하지 않은 상태를 암시적으로 유지하므로, 사용자 정의 검증을 명시적으로 초기화하지 않으면 네이티브 HTML5 유효성 검사가 정상 작동하지 않을 수 있음을 확인했습니다. **실행:** 인라인 검증 스크립트 작성 시 빈 문자열 상태를 별도로 확인하여 this.setCustomValidity('') 및 this.removeAttribute('aria-invalid')를 명시적으로 호출하는 로직을 추가해야 합니다. + +## 2026-09-06 - Drag-and-drop file type client-side validation +**Learning:** The native `accept` attribute on file inputs often fails to prevent invalid file drops. Without explicit client-side validation, dragging unsupported files allows form submission to proceed, resulting in a jarring server error page instead of an accessible inline warning. +**Action:** Always implement explicit client-side `file.type` checking for file inputs (especially those acting as drop zones) and show inline validation errors via `setCustomValidity` and `aria-invalid`. diff --git a/saas_web.py b/saas_web.py index 63265e94..7d29da71 100644 --- a/saas_web.py +++ b/saas_web.py @@ -229,6 +229,13 @@ async def add_security_headers(request: Request, call_next): return; } const text = formatBinaryBytes(file.size); + if (!file.type.startsWith('audio/') && !file.type.startsWith('video/')) { + input.setCustomValidity('Unsupported content type; select an audio or video file.'); + input.setAttribute('aria-invalid', 'true'); + preview.innerText = 'Selected file type unsupported: ' + file.name; + preview.style.color = '#dc3545'; + return; + } if (file.size > MAX_UPLOAD_BYTES) { const limitText = formatBinaryBytes(MAX_UPLOAD_BYTES); input.setCustomValidity('File exceeds ' + limitText + ' limit.'); @@ -330,6 +337,13 @@ async def add_security_headers(request: Request, call_next): let totalSize = 0; for (let i = 0; i < files.length; i++) { + if (!files[i].type.startsWith('audio/') && !files[i].type.startsWith('video/')) { + input.setCustomValidity('Unsupported content type; select audio or video files.'); + input.setAttribute('aria-invalid', 'true'); + preview.innerText = 'Selected file type unsupported: ' + files[i].name; + preview.style.color = '#dc3545'; + return; + } totalSize += files[i].size; }