Skip to content

Fixed critical issues - auth, docker setup, db indexing, error handling, sync calls#41

Merged
rkritika1508 merged 6 commits intomainfrom
feat/auth-issue-fix
Feb 12, 2026
Merged

Fixed critical issues - auth, docker setup, db indexing, error handling, sync calls#41
rkritika1508 merged 6 commits intomainfrom
feat/auth-issue-fix

Conversation

@rkritika1508
Copy link
Copy Markdown
Collaborator

@rkritika1508 rkritika1508 commented Feb 11, 2026

Summary

Target issue is #43, #44, #45, #46, #47.

Explain the motivation for making this change. What existing problem does the pull request solve?

  1. Auth uses a plain string comparison against a static token with no security hardening. Token is compared in plaintext. If logs or memory dumps expose the comparison, the token is leaked.
  2. Docker compose was failing immediately.
  3. Neither request_log nor validator_log tables have any indexes beyond the primary key. As data grows, queries filtering by request_id, status, or inserted_at will degrade.
  4. Many route handlers are defined as async def but all CRUD operations use synchronous Session from SQLModel/SQLAlchemy. This blocks the asyncio event loop during every database call.
  5. The catch-all exception handler sends str(exc) directly to the API client. In production, this can expose file paths, database errors, stack trace fragments, and other sensitive internal details.

Here are the fixes I have made -

  1. Used hashing and string time comparison to fix the authorization issue.
  2. Fixed docker setup and tested it thoroughly.
  3. Added indexing to request_log and validator_log.
  4. Update all route handlers to synchronous calls.
  5. Updated exception handling to generic exceptions to prevent sharing sensitive information with users.

Checklist

Before submitting a pull request, please ensure that you mark these task.

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

Notes

Please add here if any other information is required for the reviewer.

Summary by CodeRabbit

  • Security

    • Token handling hardened: token hashing and constant-time comparison; auth token must be a SHA-256 hex digest.
  • Performance

    • New database indexes added to improve query performance for logs.
  • Bug Fixes

    • Fixed backend port mapping and healthcheck URL for correct deployment.
  • Improvements

    • Safer error messages in production.
    • Updated guardrails-ai to >=0.8.0.
    • Smaller runtime images by excluding test artifacts and clearer prestart logging.
  • Scripts

    • Hub installer made resilient when API key is absent; manifest-driven installs.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Removed tests from the runtime Docker image, converted several FastAPI handlers and related tests from async to sync, added SHA‑256 hashing and constant‑time auth token comparison, introduced Alembic revision adding log indexes, added safe error-message handling, bumped guardrails-ai, and adjusted CI/install/docker scripts and docs.

Changes

Cohort / File(s) Summary
Docker & Infra
backend/Dockerfile, docker-compose.yml, backend/scripts/install_guardrails_from_hub.sh, backend/scripts/prestart.sh, .github/workflows/continuous_integration.yml
Removed COPY ./tests from image build; changed backend port mapping and healthcheck to 8000; made hub installer manifest-driven and tolerant when API key absent; added prestart logging; CI now injects hashed auth token and passes GUARDRAILS_HUB_API_KEY.
DB Migrations & Models
backend/app/alembic/versions/004_added_log_indexes.py, backend/app/alembic/env.py, backend/app/models/__init__.py
Added Alembic revision 004 to create/drop indexes on request_log and validator_log; imported app.models in Alembic env; re-exported ValidatorConfig from models package.
API Routes & Tests (sync conversion)
backend/app/api/routes/guardrails.py, backend/app/api/routes/utils.py, backend/app/tests/test_validate_with_guard.py
Converted several route handlers and health-check from async to sync; updated _validate_with_guard signature/finalization and corresponding tests to call sync functions (removed awaits).
Auth & Error Handling
backend/app/api/deps.py, backend/app/core/exception_handlers.py, backend/app/core/config.py, .env.example, .env.test.example, backend/README.md
Added _hash_token (SHA‑256) and constant-time compare for bearer token verification; added _safe_error_message(exc) and used it for 500 responses; added AUTH_TOKEN hash validation in Settings and updated docs/examples.
Dependencies & Packaging
backend/pyproject.toml
Bumped guardrails-ai dependency from >=0.7.2 to >=0.8.0.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • AkhileshNegi
  • nishika26
  • dennyabrain

Poem

🐰 I hopped through Docker, leaving tests behind,

Tokens now hashed so secrets stay kind.
Indexes bloom where logs once sprawled,
Sync routes hum, no awaits to call.
A carrot cheer — code nicely refined! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title lists multiple categories of fixes but does not prioritize or highlight the primary change; it reads as a general summary of many unrelated fixes rather than focusing on the main objective. Consider revising the title to emphasize the most critical fix (e.g., 'Fix authorization token comparison to use secure hashing' or 'Refactor async route handlers to sync for SQLAlchemy compatibility'), or use a more specific focus on the primary issue being addressed.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/auth-issue-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
backend/app/core/exception_handlers.py (1)

74-79: ⚠️ Potential issue | 🟠 Major

Consider logging the actual exception before returning the safe message.

In production, _safe_error_message hides the real error from the response (correctly), but the actual exception should still be logged server-side for debugging. Without logging here, 500 errors in production become very difficult to diagnose.

Proposed fix
+import logging
+
+logger = logging.getLogger(__name__)
+
 ...
 
     `@app.exception_handler`(Exception)
     async def generic_error_handler(request: Request, exc: Exception):
+        logger.exception("Unhandled exception on %s %s", request.method, request.url)
         return JSONResponse(
             status_code=HTTP_500_INTERNAL_SERVER_ERROR,
             content=APIResponse.failure_response(_safe_error_message(exc)).model_dump(),
         )
docker-compose.yml (1)

70-77: ⚠️ Potential issue | 🔴 Critical

Critical: Port mismatch — uvicorn listens on port 80 but compose expects port 8000.

The port mapping ("8000:8000") and healthcheck (localhost:8000) both target container port 8000, but the command on Line 77 starts uvicorn with --port 80. Nothing will be listening on container port 8000, so the service will be unreachable from the host and the healthcheck will always fail.

Either update the command to --port 8000 or revert the port mapping and healthcheck to use port 80.

🐛 Proposed fix — align uvicorn port with compose config
     command: >
-      uv run uvicorn app.main:app --host 0.0.0.0 --port 80 --reload
+      uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
backend/app/api/routes/guardrails.py (1)

160-164: ⚠️ Potential issue | 🟡 Minor

result.error may be None, producing the string "None" as the error message.

If result.validated_output is None and result.error is also None, str(result.error) yields the literal string "None", which would be persisted in the request log and returned to the caller. Consider a fallback:

Proposed fix
         # Case 2: validation failed without a fix
         return _finalize(
             status=RequestStatus.ERROR,
-            error_message=str(result.error),
+            error_message=str(result.error) if result.error is not None else "Validation failed",
         )
backend/app/tests/test_validate_with_guard.py (1)

62-78: ⚠️ Potential issue | 🟡 Minor

Test assertion on Line 78 is implicitly coupled to settings.ENVIRONMENT.

_safe_error_message returns "An unexpected error occurred." when ENVIRONMENT == "production", so response.error == "Invalid config" will fail in that case. This is fine if tests always run in a non-production environment, but the coupling is implicit. Consider either:

  • Patching settings.ENVIRONMENT explicitly in the test, or
  • Asserting with in or a broader check.
Proposed fix — make the environment explicit
 def test_validate_with_guard_exception():
-    with patch(
+    with patch("app.api.routes.guardrails.settings") as mock_settings, patch(
         "app.api.routes.guardrails.build_guard",
         side_effect=Exception("Invalid config"),
     ):
+        mock_settings.ENVIRONMENT = "development"
         response = _validate_with_guard(
🤖 Fix all issues with AI agents
In `@backend/app/api/deps.py`:
- Around line 39-43: The AUTH_TOKEN is expected to be a SHA-256 hex digest but
there is no startup validation or documentation and deps.py checks it
per-request (provided_hash = _hash_token(...); expected_hash =
settings.AUTH_TOKEN) raising RuntimeError at runtime; fix by adding a
startup-time validator in your configuration initialization (where settings is
constructed) that asserts settings.AUTH_TOKEN matches a 64-character hex regex
(e.g. r'^[0-9a-f]{64}$') and raises a clear error if not, update .env.example
and docs to state AUTH_TOKEN must be a SHA-256 hex digest, and remove/replace
the per-request RuntimeError in deps.py so deps uses the validated
settings.AUTH_TOKEN and only performs secrets.compare_digest(_hash_token(...),
settings.AUTH_TOKEN) without re-checking configuration on every request.
🧹 Nitpick comments (8)
backend/app/alembic/env.py (1)

10-11: Redundant but harmless explicit import.

Line 8's from app.models import SQLModel already executes app.models.__init__, which imports all models. The additional import app.models on line 10 is redundant. Consider adding a comment like # noqa: F401 — ensure all models are registered to clarify intent, or remove it since line 8 already serves this purpose.

backend/app/core/exception_handlers.py (1)

52-55: Good addition for error message safety.

The production guard and fallback are solid. One concern: the string comparison settings.ENVIRONMENT == "production" is brittle — ensure the config value is normalized (e.g., lowercased) to avoid silent mismatches like "Production" or "PRODUCTION".

backend/app/api/deps.py (1)

23-24: Unsalted SHA-256 hash provides limited additional security over plaintext comparison.

The real improvement here is secrets.compare_digest for constant-time comparison (line 45), which mitigates timing attacks. The SHA-256 hash without a salt doesn't add meaningful security — if an attacker can read settings.AUTH_TOKEN from the environment, having it as a hash vs. plaintext doesn't help since the hash is deterministic and unsalted.

If the goal is to avoid storing the raw token in config, consider using a proper key derivation function (e.g., bcrypt which is already a dependency) or at minimum document the threat model this addresses.

backend/app/alembic/versions/004_added_log_indexes.py (2)

8-8: Unused imports from Alembic template boilerplate.

Sequence and Union are imported but never used in this migration. The static analysis tool also flags that Sequence should be imported from collections.abc (ruff UP035). Since neither is used, simply remove the import.

Proposed fix
-from typing import Sequence, Union

20-28: Index additions look appropriate.

The indexes on request_id, status, inserted_at, outcome, and name align well with typical log query and filtering patterns.

One consideration: if request_id is frequently queried alongside inserted_at (e.g., for time-bounded lookups per request), a composite index might be more efficient than two separate single-column indexes. But single-column indexes are a solid starting point.

backend/pyproject.toml (1)

25-25: Consider adding an upper bound for guardrails-ai.

While guardrails-ai currently pins to v0.8.0 (the latest available), leaving it open-ended could pull in future major versions with breaking changes once the package reaches 1.0.0. Many other dependencies in this file include upper bounds for stability (e.g., fastapi<1.0.0, tenacity<9.0.0).

docker-compose.yml (1)

41-41: Clarify the purpose of entrypoint: [].

This overrides the image's default ENTRYPOINT with an empty list, which means the command (bash scripts/prestart.sh) runs directly without any entrypoint wrapper. This is fine if the Dockerfile sets an entrypoint that would conflict, but it's worth a brief inline comment explaining why it's needed for future maintainers.

backend/app/tests/test_validate_with_guard.py (1)

11-13: Module-level shared MagicMock instances accumulate state across tests.

mock_request_log_crud and mock_validator_log_crud are created once and reused across all test functions. Call counts, call args, and return values persist between tests, which can cause order-dependent failures. Consider using pytest fixtures or calling reset_mock() in each test.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@backend/app/core/config.py`:
- Around line 85-89: CI is failing because startup validation in
_enforce_non_default_secrets (which calls _validate_auth_token_hash) rejects
AUTH_TOKEN set to the placeholder "<ADD-KEY>"; update the CI environment (or
.env.test used by CI) to provide a valid 64-character hex SHA-256 digest for
AUTH_TOKEN (generate with e.g. echo -n "your-secret-token" | sha256sum and paste
the resulting hex string into AUTH_TOKEN) so _validate_auth_token_hash passes
during tests.
🧹 Nitpick comments (1)
backend/app/core/config.py (1)

79-83: Redundant anchors in re.fullmatch pattern.

re.fullmatch already matches the entire string, so ^ and $ are unnecessary. Harmless but misleading — it suggests the author may have intended re.match or re.search.

♻️ Suggested fix
     def _validate_auth_token_hash(self) -> None:
-        if not re.fullmatch(r"^[0-9a-f]{64}$", self.AUTH_TOKEN):
+        if not re.fullmatch(r"[0-9a-f]{64}", self.AUTH_TOKEN):
             raise ValueError(
                 "AUTH_TOKEN must be a SHA-256 hex digest (64 lowercase hex characters)."
             )

@rkritika1508 rkritika1508 self-assigned this Feb 11, 2026
@rkritika1508 rkritika1508 added bug Something isn't working ready-for-review labels Feb 11, 2026
@rkritika1508
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 11, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
docker-compose.yml (1)

70-77: ⚠️ Potential issue | 🔴 Critical

Critical: Port mismatch — container will be unreachable and healthcheck will always fail.

The command on line 77 starts uvicorn on --port 80, but the port mapping (8000:8000) and healthcheck (localhost:8000) both reference port 8000 inside the container. Nothing listens on container port 8000, so the healthcheck will perpetually fail and the backend will be unreachable from the host.

Change the uvicorn port to match:

🐛 Proposed fix
     command: >
-      uv run uvicorn app.main:app --host 0.0.0.0 --port 80 --reload
+      uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
backend/app/api/routes/guardrails.py (1)

51-78: ⚠️ Potential issue | 🟡 Minor

Inconsistent response format: success path returns raw dict, error path returns APIResponse.

On line 78, the success path returns {"validators": validators} (a plain dict), while the error path on lines 71–76 returns APIResponse.failure_response(...). This means the response schema differs between success and failure for the same endpoint, which is confusing for API consumers.

🐛 Proposed fix
-    return {"validators": validators}
+    return APIResponse.success_response(data={"validators": validators})
backend/app/tests/test_validate_with_guard.py (1)

62-78: ⚠️ Potential issue | 🟡 Minor

Test assertion on line 78 is coupled to non-production ENVIRONMENT setting.

_safe_error_message returns "An unexpected error occurred." when settings.ENVIRONMENT == "production", but this test asserts response.error == "Invalid config". The test will silently break if ENVIRONMENT is ever set to "production" in the test env. Consider explicitly patching settings.ENVIRONMENT to a non-production value, or mocking _safe_error_message.

🧹 Nitpick comments (8)
backend/app/alembic/versions/004_added_log_indexes.py (1)

8-8: Unused imports (Alembic template boilerplate).

Sequence and Union are never referenced in this file. Safe to remove.

🧹 Proposed cleanup
-from typing import Sequence, Union
-
 from alembic import op
backend/app/alembic/env.py (1)

10-11: Redundant import, but acceptable as an explicit signal.

from app.models import SQLModel on line 8 already executes app.models.__init__, which imports all models. The bare import app.models on line 10 is a no-op at runtime. It does serve as a readable hint that all models must be loaded for autogenerate, which is a common Alembic convention.

backend/app/core/config.py (1)

79-83: re.fullmatch already anchors the match — ^ and $ are redundant.

re.fullmatch matches the entire string by definition. The ^ and $ anchors in the pattern do nothing here.

Suggested fix
     def _validate_auth_token_hash(self) -> None:
-        if not re.fullmatch(r"^[0-9a-f]{64}$", self.AUTH_TOKEN):
+        if not re.fullmatch(r"[0-9a-f]{64}", self.AUTH_TOKEN):
             raise ValueError(
                 "AUTH_TOKEN must be a SHA-256 hex digest (64 lowercase hex characters)."
             )
backend/README.md (1)

207-217: Clarify the digest generation command output.

shasum -a 256 (and sha256sum on Linux) outputs the hash followed by a filename/dash. Developers unfamiliar with this may paste the entire output including the trailing - into .env. Consider showing how to extract just the hash, or noting the expected output format.

Suggested improvement
-echo -n "your-plain-text-token" | shasum -a 256
+echo -n "your-plain-text-token" | sha256sum | awk '{print $1}'
backend/app/core/exception_handlers.py (1)

52-55: Private naming convention on a cross-module function.

_safe_error_message is prefixed with _ (Python convention for module-private), but it's imported and used in backend/app/api/routes/guardrails.py. Consider renaming to safe_error_message (no underscore) to signal it's part of the module's public API.

docker-compose.yml (1)

41-41: What does entrypoint: [] achieve here?

Setting entrypoint: [] overrides any ENTRYPOINT from the Dockerfile with an empty list, so only command runs. This is fine if the Dockerfile has an entrypoint that conflicts with prestart.sh, but it's worth a brief inline comment explaining the intent for future maintainers.

backend/scripts/install_guardrails_from_hub.sh (1)

25-28: Quote command substitutions to prevent word splitting (SC2046).

While the current outputs are single-token strings, unquoted $(...) is a shellcheck warning and a latent risk if the expressions ever change.

♻️ Proposed fix
   guardrails configure \
     --token "$GUARDRAILS_HUB_API_KEY" \
-    $( [[ "$ENABLE_METRICS" == "true" ]] && echo "--enable-metrics" || echo "--disable-metrics" ) \
-    $( [[ "$ENABLE_REMOTE_INFERENCING" == "true" ]] && echo "--enable-remote-inferencing" || echo "--disable-remote-inferencing" )
+    "$( [[ "$ENABLE_METRICS" == "true" ]] && echo "--enable-metrics" || echo "--disable-metrics" )" \
+    "$( [[ "$ENABLE_REMOTE_INFERENCING" == "true" ]] && echo "--enable-remote-inferencing" || echo "--disable-remote-inferencing" )"
backend/app/tests/test_validate_with_guard.py (1)

11-13: Shared MagicMock instances across tests may leak call state.

mock_request_log_crud and mock_validator_log_crud are module-level singletons. Call history (e.g., .update.call_args, .create.call_count) accumulates across tests. This doesn't break the current assertions, but it makes the tests fragile if you later add assertions on mock interactions.

Consider using pytest fixtures with autouse reset, or instantiate fresh mocks per test.

♻️ Proposed fix using fixtures
-mock_request_log_crud = MagicMock()
-mock_validator_log_crud = MagicMock()
-mock_request_log_id = uuid4()
+@pytest.fixture(autouse=True)
+def reset_mocks():
+    mock_request_log_crud.reset_mock()
+    mock_validator_log_crud.reset_mock()

Or alternatively, create fresh mocks per test via fixtures.

@rkritika1508 rkritika1508 merged commit 57b5bda into main Feb 12, 2026
2 checks passed
@rkritika1508 rkritika1508 deleted the feat/auth-issue-fix branch February 12, 2026 04:46
rkritika1508 added a commit that referenced this pull request Feb 12, 2026
rkritika1508 added a commit that referenced this pull request Feb 12, 2026
@coderabbitai coderabbitai bot mentioned this pull request Apr 10, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ready-for-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exception handling improvements Fix async route handlers Add database indexing in tables Docker setup fix Improve token authentication

3 participants