diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 9c9d083b..626011bd 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -1,3 +1,7 @@ +## 2024-05-15 - Prevent 500 errors with HMAC non-ASCII inputs +**Vulnerability:** Unhandled TypeError (and 500 Server Error) when `hmac.compare_digest` processes non-ASCII strings. +**Learning:** Python`s `hmac.compare_digest` does not support comparing strings with non-ASCII characters directly. +**Prevention:** Always encode both strings to `utf-8` bytes before comparison to prevent unhandled TypeError exceptions. ## 2026-07-25 - [Cross-platform upload basename normalization] **Behavior:** Upload metadata now interprets both forward slashes and backslashes as path separators before extracting a basename. **Learning:** On POSIX systems, `pathlib.Path(filename).name` retains backslashes because they are ordinary characters there. That caused inconsistent manifest and converter filenames for Windows-style client paths. The upload itself is still written inside a trusted temporary workspace, and batch archive entry names are generated outputs; this change does not establish a filesystem traversal or archive-entry escape. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9313538b..90457a54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## [Unreleased] +- **보안(Security)**: `hmac.compare_digest` 함수에 non-ASCII 문자가 입력될 경우 발생하는 500 에러를 수정했습니다. ### Added - 다중 파일 업로드 선택 시 즉각적인 파일 개수 피드백 및 제한 초과 경고 메시지 추가 - 일괄 업로드 폼에 대상 바이트 프리셋 버튼과 총 파일 크기 미리보기를 추가하여 사용성을 개선했습니다. diff --git a/saas_web.py b/saas_web.py index 63265e94..4c123b85 100644 --- a/saas_web.py +++ b/saas_web.py @@ -114,7 +114,8 @@ async def require_api_key(request: Request, call_next): if configured_keys and not (request.method == "GET" and request.url.path == "/"): provided_key = request.headers.get("x-api-key", "") if not any( - hmac.compare_digest(provided_key, key) for key in configured_keys + hmac.compare_digest(provided_key.encode("utf-8"), key.encode("utf-8")) + for key in configured_keys ): return JSONResponse( status_code=401, diff --git a/tests/test_saas_web.py b/tests/test_saas_web.py index 3b57e033..d2d81196 100644 --- a/tests/test_saas_web.py +++ b/tests/test_saas_web.py @@ -741,6 +741,18 @@ def test_job_api_requires_key_when_configured(self): self.assertEqual(response.json(), {"error": "Invalid or missing API key"}) self.assertEqual(allowed.status_code, 404) + def test_non_ascii_keys_handled_safely(self): + with patch.dict(os.environ, {"CODEC_CARVER_API_KEYS": "secret-key"}): + # TestClient enforces ASCII strings, but we can pass bytes to mimic a raw HTTP request + # with non-ASCII content that hits the backend + response = client.post( + "/shrink", + files={"file": ("input.wav", io.BytesIO(b"dummy wav data"), "audio/wav")}, + data={"target_bytes": 0}, + headers={b"X-API-Key": "你好".encode("utf-8")}, + ) + self.assertEqual(response.status_code, 401) + def test_multiple_comma_separated_keys_all_valid(self): with patch.dict( os.environ, {"CODEC_CARVER_API_KEYS": "key-one,key-two,key-three"}