Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2026-08-30 - [Sentinel: Unhandled ValueError in API Key Middleware]
**Vulnerability:** Unhandled TypeError when processing non-ASCII API keys in `X-API-Key` header.
**Learning:** Python's `hmac.compare_digest` raises a `TypeError` when comparing non-ASCII string arguments. If user input like an API key is passed directly to `compare_digest` without ensuring it is ASCII or encoding it to bytes, an attacker can crash the request process with a 500 server error by providing Unicode characters.
**Prevention:** Always encode user-provided secrets and configured secrets to bytes (e.g., `.encode('utf-8')`) before passing them to `hmac.compare_digest`.
## 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.
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
- 순수 영숫자 토큰은 정규식 호출을 건너뛰되 다국어·문장부호 토큰화 결과는 기존 의미와 동일하게 유지합니다. 근거, 한계, APA 7 참고문헌은 [`docs/doctoring/token-fast-path-equivalence.md`](docs/doctoring/token-fast-path-equivalence.md)에 기록했습니다.

### Fixed
- 비 ASCII 문자가 포함된 `X-API-Key` 헤더를 처리할 때 `hmac.compare_digest`에서 발생하는 `TypeError`를 해결하기 위해, 비교 전 두 값을 UTF-8로 인코딩하도록 수정했습니다.
- 단일·일괄 대상 크기 입력을 비웠을 때 이전 custom validity와 `aria-invalid` 상태를 즉시 초기화해 현재 필수 입력 상태를 정확히 전달합니다.
- 업로드 파일명의 경로 구분자를 정규화하여 POSIX에서도 Windows 형식의 클라이언트 경로가 일관된 basename으로 기록되도록 수정했습니다.
2 changes: 1 addition & 1 deletion saas_web.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Credential registry migration remains incomplete

get_configured_api_keys still reads runtime secrets directly from the environment. Repository policy requires one-time environment bootstrap into a credential registry, then registry-only request handling.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Non-ASCII configured keys always fail

When a configured key contains non-ASCII characters, encode("utf-8") re-encodes Starlette's Latin-1 header text rather than the received bytes. Matching clients always receive 401.

Suggested change
hmac.compare_digest(provided_key.encode("utf-8"), key.encode("utf-8")) for key in configured_keys
hmac.compare_digest(provided_key.encode("latin-1"), key.encode("utf-8")) for key in configured_keys
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

):
return JSONResponse(
status_code=401,
Expand Down
21 changes: 21 additions & 0 deletions tests/test_saas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,27 @@ def test_video_content_type_accepted_by_validator(self):
)
)

def test_unicode_api_key_handled_safely(self):
import asyncio
from fastapi import Request
from fastapi.responses import JSONResponse

class MockCallNext:
async def __call__(self, request):
return JSONResponse({"status": "ok"})

scope = {
"type": "http",
"method": "POST",
"url": "/shrink",
"headers": [(b"x-api-key", "안녕".encode("utf-8"))],
}

request = Request(scope)
with patch("saas_web.get_configured_api_keys", return_value=["secret1"]):
res = asyncio.run(saas_web.require_api_key(request, MockCallNext()))
self.assertEqual(res.status_code, 401)


if __name__ == "__main__":
unittest.main()
Loading