Skip to content
Draft
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
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
source =
audio_library
chapters
credential_registry
diarize
job_store
mcp_driver
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
python -m pip install --disable-pip-version-check --no-index --no-deps --no-build-isolation -e .

- name: Compile check
run: python -m py_compile media_shrinker.py config_file.py presets.py saas_web.py mcp_driver.py job_store.py
run: python -m py_compile media_shrinker.py config_file.py presets.py saas_web.py mcp_driver.py job_store.py credential_registry.py

- name: Run tests
run: python -m unittest discover -s tests -v
Expand Down
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
**Learning:** To enhance security in FastAPI applications, missing HTTP response headers could leak referrers or give access to APIs (e.g. geolocation) without explicit intent.
**Prevention:** Implement an `@app.middleware('http')` function to globally inject defense-in-depth security headers such as `Content-Security-Policy`, `X-Frame-Options`, `Strict-Transport-Security`, `X-Content-Type-Options`, `X-XSS-Protection`, `Referrer-Policy` (e.g., `strict-origin-when-cross-origin`), and `Permissions-Policy` (e.g., `geolocation=(), microphone=(), camera=()`).

## 2026-08-16 - [Sentinel: Request-time API key environment reads]
**Vulnerability:** API keys compared from `os.environ` on every request, with first-match `hmac.compare_digest` on raw strings and fail-open public binds.
**Learning:** Environment transport is not a verifier store. Hostile Unicode or overlong `X-API-Key` values must stay on a bounded 401 path. Listing APIs that return plaintext recreate the secret.
**Prevention:** Bootstrap keys once into `credential_registry` (SHA-256 digests, two-word `api_credentials` table). Compare every usable digest without short-circuit. Never echo secrets in errors, repr, or public listings. Fail-closed on `0.0.0.0` unless keys exist.

## 2026-07-10 - [Sentinel: Media Source Path Traversal]
**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.
Expand Down
7 changes: 3 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ repo.
job store, and open PRs adding API-key auth and usage metering), so it *will*
read runtime secrets/config (API keys, DB creds, endpoints). When you add them,
source them from the KV, not `os.getenv`.
- **Known deviation to migrate:** the in-flight API-key auth work reads keys from
a `CODEC_CARVER_API_KEYS` environment variable — that is exactly the anti-pattern
above. Move it to read from the credential registry (env may still be the
bootstrap transport that *populates* the KV, never the runtime source).
- **API keys:** `credential_registry.py` is the request-time source. Env
(`CODEC_CARVER_API_KEYS`) is bootstrap transport into `api_credentials`
only. See [`docs/doctoring/api-credential-registry.md`](docs/doctoring/api-credential-registry.md).

### Code exploration
- There is no `.codegraph/` index in this repo today, so use normal search
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]
### Added
- API 키는 SQLite `api_credentials` 레지스트리에 검증 재료만 저장하고, `CODEC_CARVER_API_KEYS`는 기동 시 수송만 합니다. 공개 바인드는 키가 없으면 실패합니다. 근거는 [`docs/doctoring/api-credential-registry.md`](docs/doctoring/api-credential-registry.md)에 있습니다.
- 다중 파일 업로드 선택 시 즉각적인 파일 개수 피드백 및 제한 초과 경고 메시지 추가
- 일괄 업로드 폼에 대상 바이트 프리셋 버튼과 총 파일 크기 미리보기를 추가하여 사용성을 개선했습니다.
- 클라이언트 측 폼 검증 시 하드코딩된 '5 GiB' 텍스트를 동적으로 변환되도록 수정하고 일괄 업로드 폼에 최대 크기(MAX_UPLOAD_BYTES) 검증 피드백을 추가했습니다.
Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ python3 -m unittest tests.test_media_shrinker -v
python3 -m unittest tests.test_job_store.TestCreateAndGet.test_create_get_roundtrip

# Compile check (CI runs this on all four modules)
python -m py_compile media_shrinker.py saas_web.py mcp_driver.py job_store.py
python -m py_compile media_shrinker.py saas_web.py mcp_driver.py job_store.py credential_registry.py

# CLI (omit --execute for a dry run that only lists candidates)
codec-carver /path/to/recordings --execute --output-dir under_2gb
Expand All @@ -50,6 +50,7 @@ Four flat top-level modules (declared as `py-modules` in `pyproject.toml`; there
- **`saas_web.py`** — single-file FastAPI upload UI (the `[web]` extra; what the Docker image serves). Streams one upload into a temp workspace, calls `media_shrinker.convert_file`, and returns the first generated output as a download. Middleware enforces a 5 GiB upload cap and security headers. Processing is synchronous per request.
- **`mcp_driver.py`** — FastMCP server (the `[mcp]` extra) exposing a single `shrink_media` tool that wraps `convert_file`.
- **`job_store.py`** — stdlib-only SQLite (WAL) durable job store intended for async/worker job tracking. It is tested but not yet wired into `saas_web.py`. Callers pass `now` explicitly; the store never calls `datetime.now()` itself.
- **`credential_registry.py`** — stdlib-only SQLite verifier store for API keys. Request-time auth reads digests only; `CODEC_CARVER_API_KEYS` is startup transport. Callers pass `now` explicitly.

Supporting directories: `fuzz/` holds Atheris harnesses plus seed corpora for the three untrusted-input parsing surfaces (`parse_silencedetect_intervals`, `_parse_probe_payload`, `build_segments`); the same invariants run as Hypothesis property tests in `tests/test_fuzz_properties.py` so they execute in the normal suite. `docs/papers/` holds the fuzzing survey the harness design references.

Expand All @@ -61,7 +62,7 @@ Supporting directories: `fuzz/` holds Atheris harnesses plus seed corpora for th
## Key conventions

- **Never endanger sources.** The scan's selected sources are protected from deletion/overwrite (`protected_sources` / `_ensure_not_protected_source_path`). Generated names keep the full original filename plus a new suffix (`clip.wav.flac`, `meeting.wav.part0001.flac`) so same-stem inputs cannot collide. Keep `--output-dir` a generated-only directory.
- **Stdlib-only core.** `media_shrinker.py` and `job_store.py` must not grow third-party imports; FastAPI/MCP dependencies belong to the optional `web`/`mcp` extras. Tests guard optional imports with `skipUnless` so the suite passes without extras installed.
- **Stdlib-only core.** `media_shrinker.py`, `job_store.py`, and `credential_registry.py` must not grow third-party imports; FastAPI/MCP dependencies belong to the optional `web`/`mcp` extras. Tests guard optional imports with `skipUnless` so the suite passes without extras installed.
- **Docstring coverage is 100%.** `interrogate` is configured with `fail-under = 100` (excluding `scripts`, `tests`, `fuzz`) — every module and function, including private helpers, needs a docstring. `.coveragerc` likewise sets `fail_under = 100` over `media_shrinker`, `saas_web`, and `mcp_driver`.
- **Security posture.** ffmpeg/ffprobe are always invoked with `-nostdin` and `-protocol_whitelist file,crypto,data` (SSRF/LFI hardening); uploaded filenames are sanitized to a safe basename; temp files use `tempfile` APIs, not predictable names; copied permissions are masked to drop setuid/setgid/sticky bits. `.jules/sentinel.md` logs past vulnerabilities and their prevention rules — check it before touching subprocess invocation, temp-file, or metadata-copy code. `.jules/bolt.md` records performance lessons (pre-resolve paths once, prune walks, avoid repeated `stat`).
- **Fuzzing-first for parsers.** Anything that parses ffmpeg/ffprobe output is an untrusted-input surface: parsers must never raise unexpected exception types on arbitrary input (raise `MediaShrinkerError` for invalid payloads). If you change one, update the matching harness in `fuzz/` and its Hypothesis mirror in `tests/test_fuzz_properties.py`.
Expand Down
Loading
Loading