Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,8 @@
**Vulnerability:** Path traversal in `media_shrinker.py` via unresolved `..` segments or symlink escapes before deriving conversion output paths.
**Learning:** `Path.relative_to()` is only a lexical containment check unless both the source and root have first been resolved into canonical absolute paths. Relative paths and symlinks can otherwise bypass root-boundary assumptions.
**Prevention:** Resolve both source and root once, reject sources outside the resolved root with a sanitized `MediaShrinkerError`, and derive `rel_source` from the resolved paths before planning outputs.

## 2026-07-20 - [Sentinel: Unhandled Exception DoS in HMAC Comparison]
**Vulnerability:** Uncontrolled Resource Consumption (DoS) via Unhandled `TypeError` Exception (CWE-400 / CWE-754) when validating API keys.
**Learning:** Python's `hmac.compare_digest` function throws a `TypeError: comparing strings with non-ASCII characters is not supported` if either string argument contains non-ASCII characters. Because the `X-API-Key` HTTP header is user-controlled, an attacker can send a request with a non-ASCII key (e.g., `X-API-Key: ö`), causing the FastAPI application to crash with a 500 Internal Server Error, bypassing the intended 401 Unauthorized response and potentially leading to a Denial of Service.
**Prevention:** Always encode user-controlled strings to bytes (e.g., `.encode("utf-8")`) before passing them to `hmac.compare_digest` to ensure safe, constant-time comparison regardless of the input character set.
4 changes: 2 additions & 2 deletions saas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ async def require_api_key(request: Request, call_next):

configured_keys = get_configured_api_keys()
if configured_keys and not (request.method == "GET" and request.url.path == "/"):
provided_key = request.headers.get("x-api-key", "")
provided_key = request.headers.get("x-api-key", "").encode("utf-8")

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-Latin-1 API keys always fail

For keys containing characters outside Latin-1, provided_key.encode("utf-8") changes the header bytes. Every valid request receives 401.

Suggested change
provided_key = request.headers.get("x-api-key", "").encode("utf-8")
provided_key = request.headers.get("x-api-key", "").encode("latin-1")
Devin Review

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

if not any(
hmac.compare_digest(provided_key, key) for key in configured_keys
hmac.compare_digest(provided_key, key.encode("utf-8")) for key in configured_keys
):
return JSONResponse(
status_code=401,
Expand Down
28 changes: 28 additions & 0 deletions tests/test_saas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,34 @@
client = TestClient(app)


@unittest.skipUnless(
_HAS_FASTAPI, "fastapi not installed (optional integration dependency)"
)
class TestApiKeyAuthDoSMitigation(unittest.IsolatedAsyncioTestCase):
async def test_non_ascii_header_does_not_crash(self):
from fastapi import Request
import saas_web

# Setup a mock request with a non-ASCII API key header
scope = {
"type": "http",
"method": "POST",
"path": "/shrink",
"headers": [(b"x-api-key", "wrong-key-ö".encode("utf-8"))],
}
request = Request(scope)

async def mock_call_next(request):
return "SUCCESS"

with patch.dict(os.environ, {"CODEC_CARVER_API_KEYS": "secret-key"}):
response = await saas_web.require_api_key(request, mock_call_next)
Comment on lines +51 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Runtime secret source remains noncompliant

The test reinforces runtime use of CODEC_CARVER_API_KEYS. AGENTS.md requires this authentication path to migrate to a credential registry.

Devin Review

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


self.assertEqual(response.status_code, 401)
body = json.loads(response.body.decode('utf-8'))
self.assertEqual(body["error"], "Invalid or missing API key")
Comment on lines +39 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Test bypasses HTTP decoding

The direct require_api_key call only proves malformed input returns 401. It never tests a valid non-ASCII key through the HTTP client.

Devin Review

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



@unittest.skipUnless(
_HAS_FASTAPI, "fastapi not installed (optional integration dependency)"
)
Expand Down
Loading