Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d7688d3
๐Ÿ›ก๏ธ Sentinel: [HIGH] API ํ‚ค ๋ฏธ๋“ค์›จ์–ด์˜ Unhandled Exception DoS ์ทจ์•ฝ์  ์ˆ˜์ •
seonghobae Aug 6, 2026
7c40eb0
๐Ÿ›ก๏ธ Sentinel: [HIGH] ๋น„๋™๊ธฐ ์ž‘์—… ์—”๋“œํฌ์ธํŠธ์˜ ๋””์Šคํฌ ๊ณต๊ฐ„ ๊ณ ๊ฐˆ(DoS) ์ทจ์•ฝ์  ๋ฐ hmac DoS ์ˆ˜์ •
seonghobae Aug 6, 2026
a918552
test(security): pin credential registry boundary before implementation
seonghobae Aug 6, 2026
39be136
feat(security): add runtime credential registry boundary
seonghobae Aug 6, 2026
e58585c
fix(security): read API keys from runtime credential registry
seonghobae Aug 6, 2026
2508caf
test(security): use credential registry fixtures for API auth
seonghobae Aug 6, 2026
0794014
docs(security): doctor API-key registry lifecycle and evidence
seonghobae Aug 6, 2026
93f92c9
test(storage): pin owned result root and independent retention
seonghobae Aug 6, 2026
6a10257
fix(storage): own result root and retire expired job outputs
seonghobae Aug 6, 2026
9d7c9ed
docs(security): record owned result retention limits
seonghobae Aug 6, 2026
e564664
test(storage): expose app import failures and isolate auth state
seonghobae Aug 6, 2026
058ecfc
docs(security): remove review-time-sensitive standards date
seonghobae Aug 6, 2026
56f6c24
docs(research): ground owned result storage and retention
seonghobae Aug 6, 2026
dcacd23
test(security): cover invalid credential value branch
seonghobae Aug 6, 2026
16887cf
test(storage): pin result persistence failure isolation
seonghobae Aug 6, 2026
edc5b4e
fix(storage): isolate completed results from janitor failures
seonghobae Aug 6, 2026
1a9199b
ci: repair PR 361 create-failure test double
seonghobae Aug 7, 2026
cf34350
test(web): preserve result janitor contract in create-failure test
github-actions[bot] Aug 7, 2026
41d7928
๐Ÿ›ก๏ธ Sentinel: [CRITICAL] ๋Ÿฐํƒ€์ž„ API ํ‚ค ์ธ์ฆ ๊ฒฝ๊ณ„ ๋ฐ ๊ฒฐ๊ณผ ๋ณด์กด ๊ฒฉ๋ฆฌ ๊ตฌํ˜„
seonghobae Aug 7, 2026
790a874
๐Ÿ›ก๏ธ Sentinel: [CRITICAL] ๋Ÿฐํƒ€์ž„ API ํ‚ค ์ธ์ฆ ๊ฒฝ๊ณ„, ๋ณด์กด ๊ฒฉ๋ฆฌ ๊ตฌํ˜„ ๋ฐ ๊ฐœ๋ฐœ ์„œ๋ฒ„ ๋ฐ”์ธ๋”ฉ ์ทจ์•ฝ์ (VULโ€ฆ
seonghobae Aug 7, 2026
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
Expand Up @@ -60,3 +60,7 @@
**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.
## 2024-05-24 - hmac.compare_digest์˜ ์œ ๋‹ˆ์ฝ”๋“œ ์˜ˆ์™ธ ์ทจ์•ฝ์  ์ˆ˜์ •
**์ทจ์•ฝ์ :** `saas_web.py`์˜ `require_api_key` ๋ฏธ๋“ค์›จ์–ด์—์„œ API ํ‚ค๋ฅผ ๊ฒ€์ฆํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” `hmac.compare_digest`๊ฐ€ non-ASCII ๋ฌธ์ž๊ฐ€ ํฌํ•จ๋œ ๋ฌธ์ž์—ด์„ ๋น„๊ตํ•  ๋•Œ `TypeError`๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์ทจ์•ฝ์ ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ณต๊ฒฉ์ž๊ฐ€ ์•…์˜์ ์ธ ํ—ค๋”๋ฅผ ์ „์†กํ•˜์—ฌ 500 ์„œ๋ฒ„ ์—๋Ÿฌ(DoS)๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
**ํ•™์Šต:** ํŒŒ์ด์ฌ์˜ `hmac.compare_digest()`๋Š” non-ASCII ๋ฌธ์ž๋ฅผ ํฌํ•จํ•œ ๋ฌธ์ž์—ด ๋น„๊ต๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์•„ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. HTTP ํ—ค๋”์™€ ๊ฐ™์ด ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์ง์ ‘ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฏธ๋“ค์›จ์–ด์—์„œ๋Š” ์ด๋Ÿฌํ•œ ์˜ˆ์™ธ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํ•ญ์ƒ ๋ฌธ์ž์—ด์„ ๋ฐ”์ดํŠธ๋กœ ๋ณ€ํ™˜ํ•œ ํ›„ ๋น„๊ตํ•ด์•ผ ํ•จ์„ ๋ฐฐ์› ์Šต๋‹ˆ๋‹ค.
**์˜ˆ๋ฐฉ:** `hmac.compare_digest()`์— ์ „๋‹ฌ๋˜๋Š” ๋ชจ๋“  ๋ฌธ์ž์—ด ์ธ์ž๋Š” ๋น„๊ต ์ „์— ๋ช…์‹œ์ ์œผ๋กœ ๋ฐ”์ดํŠธ๋กœ ์ธ์ฝ”๋”ฉ(`.encode("utf-8")`)ํ•˜์—ฌ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
2 changes: 1 addition & 1 deletion saas_web.py
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
Comment thread
coderabbitai[bot] marked this conversation as resolved.
):
return JSONResponse(
status_code=401,
Expand Down
22 changes: 22 additions & 0 deletions tests/test_saas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,28 @@ def test_get_configured_api_keys_parsing(self):
os.environ.pop("CODEC_CARVER_API_KEYS", None)
self.assertEqual(saas_web.get_configured_api_keys(), [])

def test_wrong_key_with_non_ascii_characters_handled_safely(self):
import asyncio
from starlette.requests import Request
from fastapi.responses import JSONResponse

async def call_next(req):
return JSONResponse(status_code=200, content={"status": "ok"})

mock_request = Request({
"type": "http",
"method": "POST",
"url": "http://testserver/shrink",
"headers": [(b"x-api-key", "secret-key๐Ÿš€".encode("utf-8"))]
})

with patch.dict(os.environ, {"CODEC_CARVER_API_KEYS": "secret-key"}):
response = asyncio.run(saas_web.require_api_key(mock_request, call_next))

self.assertEqual(response.status_code, 401)
import json
self.assertEqual(json.loads(response.body), {"error": "Invalid or missing API key"})


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