fix: gate traceback disclosure behind DEBUG_TRACEBACKS flag, run container as non-root - #11
Merged
Merged
Conversation
…ainer as non-root Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012RttwcxBgsnXH2TwQGQsrp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
/rag/query's error response behindDEBUG_TRACEBACKS(default off).ghcr.io/omnibioai/omnibioai-base:latesthas a pinnable version — it doesn't (see below).Dockerfilestill hadCOPY utils/ ./utils/, bututils/was deleted in a prior cleanup PR (chore: remove zero-byte stub modules with no references (duplicated inline in rag/engine.py) #8) — this brokedocker buildoutright. Removed the dead COPY line so the image builds again.1.
/rag/querytraceback gating (api/routes/rag.py)New
_debug_tracebacks_enabled()(same on/off convention asapi/auth.py's_auth_enabled():os.getenv("DEBUG_TRACEBACKS", "").strip().lower() == "true"). Theexceptblock in/querynow only adds"trace"to the responsedetailwhen that's true.Before/after (captured via TestClient against a mocked engine raising
Exception("Query Failed")):DEBUG_TRACEBACKSunset (new default):{ "detail": { "error": "Query Failed" } }DEBUG_TRACEBACKS=true(preserves prior behavior exactly):{ "detail": { "error": "Query Failed", "trace": "Traceback (most recent call last):\n File \".../api/routes/rag.py\", line 63, in query\n result = engine.query(...)\n ..." } }/rag/stream(api/routes/rag.pyevent_stream error handler) already only ever returned{"type": "error", "message": str(e)}— no traceback, no change needed. This PR makes that the consistent behavior everywhere: neither endpoint leaks a stack trace by default now.2. Test split (
tests/test_rag_routes.py)test_query_endpoint_failure(which asserted"trace" in data["detail"]unconditionally) is now two tests:test_query_endpoint_failure_traceback_enabled— setsDEBUG_TRACEBACKS=trueviamonkeypatch.setenv, asserts"trace" in data["detail"].test_query_endpoint_failure_traceback_disabled— clears the env var viamonkeypatch.delenv, asserts"trace" not in data["detail"].Both states are explicitly tested — nothing was deleted or weakened.
3. Non-root container (
Dockerfile)Checked every path the app/nginx actually write to at runtime before touching permissions:
/app/data/faiss_index/VectorStore.save()viascripts/build_index.pyon first bootchown -R appuser:appuser /app/app/logs/,/app/cache//var/log/nginx/{access,error}.logchown -R appuser:appuser /var/log/nginx/var/lib/nginx/{body,proxy,fastcgi,scgi,uwsgi}chown -R appuser:appuser /var/lib/nginx/etc/nginx/conf.d/devhub.confJWT_SECRET)chown -R appuser:appuser /etc/nginx/conf.dAlso: stock
nginx.confsetsuser www-data;(assumes a root master process able to setuid) andpid /run/nginx.pid;(/runis root:root here) — both adjusted: theuserdirective is dropped (master already runs asappuser, so no setuid needed) and the pid file is redirected to/tmp(world-writable).USER appuser(uid/gid 10001, no home,nologinshell) is the last setup instruction beforeCMD.4. Base image pinning
Checked the GHCR registry directly (
gh api /orgs/OmniBioAI/packages/container/omnibioai-base/versions): one version exists, tagged only"latest"— no semver/release tags, no dedicated base-image repo with GitHub releases. There's nothing to pin to that wouldn't be guessing. Left asghcr.io/omnibioai/omnibioai-base:latest, unchanged — this remains a follow-up blocked on the base image publishing versioned tags. (Pinning to the current image digest was considered and rejected: that's a materially different decision — freezing to today's exact build rather than tracking intentional base-image updates — and the task asked for a pinnable version, not a digest.)Verification
pytest:
pytest -q→ 175 passed (174-test baseline + 1 net-new test from the traceback split). Both new states individually confirmed:Docker build + non-root run (fresh build, no cache reused beyond unrelated earlier layers):
Ran it (Ollama reachable via
--add-host=ollama:host-gateway, pre-built FAISS index bind-mounted read-only so startup skips a full 19-repo rebuild):Confirmed all three checks:
nginx.confrewrite anddevhub.confgeneration both succeeded./rag/queryresponds successfully from inside the running container — tested both directly against uvicorn (port 8082, HTTP 200 with a full grounded answer) and through nginx's reverse proxy (port 5173 →/rag/, also HTTP 200).docker exec <container> ps aux— uvicorn, nginx master, and all nginx workers run asappuser, not root./var/log/nginx/error.log— empty (no errors logged)./var/lib/nginx/{body,fastcgi,proxy,scgi,uwsgi}— auto-created by nginx workers on first request, all ownedappuser:appuser./etc/nginx/conf.d/devhub.conf— written successfully by the CMD script asappuser./app/logs,/app/cache— confirmed owned byappuser:appuser(no code path currently writes there during a query, so this is "ready and correctly owned" rather than "observed under load" — noted for transparency).Scope
3 files:
Dockerfile,api/routes/rag.py,tests/test_rag_routes.py.Per instructions, this PR is not to be merged automatically — same as PR #9 and #10. Results reported above for review; merge only after explicit confirmation.