Skip to content

Security implementations - #87

Merged
ahmet-cskn merged 19 commits into
mainfrom
security-implementations
Jul 14, 2026
Merged

ahmet-cskn merged 19 commits into
mainfrom
security-implementations

Conversation

@ahmet-cskn

@ahmet-cskn ahmet-cskn commented Jul 9, 2026 •

Copy link
Copy Markdown
Collaborator

Here is the summary of changes:

  • Service-level jwt verification is implemented for both the checklist service and the genai service
  • Pretty much re-wrote the genai service to use the models and endpoints generated by openapi
  • Added testing to the genai service
  • The public and private jwt keys at infra/.env did not belong to each other, i generated new ones and replaced them

Summary by CodeRabbit

  • New Features

    • Checklist and checklist-item operations now use JWT authentication and enforce ownership access.
    • Conversation and chat endpoints now require JWT authentication, with clearer unauthorized and forbidden responses.
    • Health checks are available through the service’s API path.
  • Bug Fixes

    • Improved authorization handling prevents access to other users’ checklists and conversations.
    • Updated API responses and validation provide more consistent formats and error handling.
  • Documentation

    • OpenAPI definitions now reflect authentication requirements, response formats, and input constraints.

Comment thread services/genai-service/main.py
Comment thread api/genai-service.yaml Outdated
Comment thread api/genai-service.yaml
Comment thread api/genai-service.yaml
Comment thread infra/.env
@alexander-wudy

Copy link
Copy Markdown
Collaborator

Btw, maybe you also take a look at #86 and integrate the comments of Werner I marked with 👍 as well here

Comment thread api/checklist-service.yaml
Comment thread api/checklist-service.yaml
Comment thread api/checklist-service.yaml Outdated
@alexander-wudy

Copy link
Copy Markdown
Collaborator

lgtm

@w-richter

Copy link
Copy Markdown
Collaborator

lgtm now too

@coderabbitai

coderabbitai Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR adds JWT authentication to checklist and GenAI APIs, introduces generated OpenAPI response models and routing, enforces checklist ownership, updates GenAI deployment paths, and adds PostgreSQL-backed endpoint and contract tests.

Changes

Checklist authentication and response contracts

Layer / File(s) Summary
Checklist API contracts
api/checklist-service.yaml
Checklist operations now require JWT authentication, document authorization failures, and use dedicated request and response schemas.
Ownership checks and DTO mapping
services/checklist-service/...
Checklist services validate ownership, throw access exceptions, and map entities to generated response DTOs through MapStruct.
Authenticated controller wiring
services/checklist-service/src/main/java/.../ChecklistController.java
The controller extracts user identity from validated requests and passes it through checklist operations.
Checklist build and controller tests
services/checklist-service/pom.xml, services/checklist-service/src/test/...
MapStruct compilation is configured, and controller tests use authenticated requests and updated service contracts.

GenAI generated API and runtime authentication

Layer / File(s) Summary
GenAI API contract updates
api/genai-service.yaml
Conversation and chat operations add JWT requirements and unauthorized responses, while schemas gain validation and nullability updates.
JWT middleware and generated route implementation
services/genai-service/main.py
JWT keys are fetched and cached, authentication context is propagated, downstream calls forward authorization, and generated API methods implement conversations and chat.
Generated client packaging and health routing
services/genai-service/Dockerfile, infra/docker-compose.yml, infra/iac/.../deployment.yaml
The image generates Python API code and health checks use the /api/genai/api/v1/health path.
GenAI integration and contract tests
services/genai-service/tests/*, .github/workflows/ci.yml
PostgreSQL-backed pytest fixtures, endpoint tests, Schemathesis validation, and a CI test job are added.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 is too generic and does not clearly describe the main changes to JWT auth, GenAI endpoint rewrites, and CI/tests. Use a specific title such as "Add JWT auth and OpenAPI-generated GenAI service endpoints".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security-implementations

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.

@ahmet-cskn
ahmet-cskn merged commit f6b69c5 into main Jul 14, 2026
9 of 10 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (4)
services/checklist-service/src/main/java/de/tum/devopss26/checklistservice/service/ChecklistServiceImpl.java (3)

115-117: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Use Objects.equals for safe comparison.

Using Objects.equals safely compares the IDs and prevents a potential NullPointerException in the unlikely event that entity.getUserId() is null.

💡 Proposed refactor
-        if (!entity.getUserId().equals(userId)) {
+        if (!java.util.Objects.equals(entity.getUserId(), userId)) {
             throw new IllegalChecklistAccessException(userId, entity.getUserId(), id);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/checklist-service/src/main/java/de/tum/devopss26/checklistservice/service/ChecklistServiceImpl.java`
around lines 115 - 117, Update the user ID comparison in the access check around
entity.getUserId() to use Objects.equals rather than invoking equals on the
entity value. Preserve the existing IllegalChecklistAccessException behavior
when the IDs differ, including null-versus-non-null values.

88-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract checklist item retrieval and validation into a helper.

This logic to fetch a checklist item and validate its association with the parent checklist is duplicated here and in deleteChecklistItem. Consider extracting it into a private helper method (e.g., getChecklistItemEntity(checklistId, itemId)) to keep the code DRY.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/checklist-service/src/main/java/de/tum/devopss26/checklistservice/service/ChecklistServiceImpl.java`
around lines 88 - 92, Extract the duplicated checklist-item lookup and
checklist-association validation from the current method and deleteChecklistItem
into a private helper such as getChecklistItemEntity(checklistId, itemId).
Preserve the existing ChecklistItemNotFoundException and
ChecklistItemNotInChecklistException behavior, then replace both call sites with
the helper.

121-140: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider delegating entity mapping to MapStruct.

Since MapStruct is already configured via ChecklistMapper, you can define mapping methods for ChecklistEntity -> IdentifiedChecklist and ChecklistItemEntity -> IdentifiedChecklistItem directly in the mapper interface. This will automatically generate the conversion code, standardize the mapping approach across the service, and allow you to remove these manual toDto methods entirely.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/checklist-service/src/main/java/de/tum/devopss26/checklistservice/service/ChecklistServiceImpl.java`
around lines 121 - 140, Move the ChecklistEntity and ChecklistItemEntity
conversion logic from the private toDto methods in ChecklistServiceImpl into
mapping methods on the existing ChecklistMapper interface. Configure any
necessary field or timestamp mappings there, update service callers to use
ChecklistMapper, and remove the manual toDto methods while preserving all
currently mapped fields and UTC timestamp behavior.
services/checklist-service/src/test/java/de/tum/devopss26/checklistservice/controller/ChecklistControllerTest.java (1)

121-129: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add coverage for cross-user access returning 403.

The negative tests cover 401 and 404, but never exercise IllegalChecklistAccessException. Add checklist and nested-item cases proving that another user's resource returns 403 and satisfies the OpenAPI contract.

Also applies to: 198-233, 274-354

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/checklist-service/src/test/java/de/tum/devopss26/checklistservice/controller/ChecklistControllerTest.java`
around lines 121 - 129, Add tests alongside ChecklistControllerTest cases such
as getChecklistById_NOT_FOUND and the additional checklist/nested-item
negative-test sections to cover IllegalChecklistAccessException for resources
owned by another user. Mock the service methods with that exception, perform the
corresponding authenticated checklist and nested-item requests, and assert HTTP
403 plus openApi().isValid("checklist-service.yaml").
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 46-71: Harden the python-test job by setting its job-level
GITHUB_TOKEN permissions to read-only and configuring the actions/checkout@v4
step with credential persistence disabled. Apply these changes specifically to
the python-test job without altering unrelated workflow jobs.

In `@api/checklist-service.yaml`:
- Around line 266-272: Mark the inherited server-managed createdAt and items
properties as readOnly in the shared checklist schema used by
CreateChecklistRequest and UpdateChecklistRequest, including the corresponding
schema section around the additional referenced lines. Ensure generated clients
cannot treat these fields as writable while preserving them in response models.

In `@infra/docker-compose.yml`:
- Line 182: Update the health check for the GenAI service in the docker-compose
test command to use Python’s urllib instead of curl, unless curl is explicitly
installed in the runtime image; preserve the existing health endpoint and
failure behavior.

In `@services/genai-service/Dockerfile`:
- Around line 13-23: Update the runtime stage of the Dockerfile to create a
dedicated non-root appuser, grant it ownership or access to /app as needed, and
switch to USER appuser before CMD. Keep the existing uvicorn startup command
unchanged.
- Around line 8-10: Pin the OpenAPI generator used by the Docker build: update
the generator invocation in the RUN command to use an explicit package version,
or copy and apply the repository’s openapitools.json before generation. Ensure
clean builds resolve the same fixed generator version instead of selecting it
dynamically.

In `@services/genai-service/main.py`:
- Around line 453-489: Refactor the conversation/message flow in the async
session block so conversation creation or lookup and the initial USER message
are committed before calling _fetch_user_data, _rag_retrieve, and _build_chain.
Perform those external calls after the session is released, then open a new
short-lived _sessions transaction to add the AGENT message and commit it using
the persisted conversation.id.
- Around line 120-140: Update _fetch_public_key_locked and
_get_or_fetch_public_key to record the time of failed fetch attempts and enforce
a cooldown before retrying. When a fetch fails, preserve _public_key as None,
update _last_fetch_time, and have subsequent callers return the existing
unavailable result without making another request until the cooldown expires, so
the authentication flow can respond with 503.

In `@services/genai-service/tests/conftest.py`:
- Around line 46-60: Update the database setup fixture around db_url and
schema_engine to require an explicitly configured, test-only database URL or
name before calling main.Base.metadata.drop_all. Validate it against an explicit
allowlist and fail fast when the configured value is absent or not allowlisted;
do not preserve or reuse injected production database values through defaults.

---

Nitpick comments:
In
`@services/checklist-service/src/main/java/de/tum/devopss26/checklistservice/service/ChecklistServiceImpl.java`:
- Around line 115-117: Update the user ID comparison in the access check around
entity.getUserId() to use Objects.equals rather than invoking equals on the
entity value. Preserve the existing IllegalChecklistAccessException behavior
when the IDs differ, including null-versus-non-null values.
- Around line 88-92: Extract the duplicated checklist-item lookup and
checklist-association validation from the current method and deleteChecklistItem
into a private helper such as getChecklistItemEntity(checklistId, itemId).
Preserve the existing ChecklistItemNotFoundException and
ChecklistItemNotInChecklistException behavior, then replace both call sites with
the helper.
- Around line 121-140: Move the ChecklistEntity and ChecklistItemEntity
conversion logic from the private toDto methods in ChecklistServiceImpl into
mapping methods on the existing ChecklistMapper interface. Configure any
necessary field or timestamp mappings there, update service callers to use
ChecklistMapper, and remove the manual toDto methods while preserving all
currently mapped fields and UTC timestamp behavior.

In
`@services/checklist-service/src/test/java/de/tum/devopss26/checklistservice/controller/ChecklistControllerTest.java`:
- Around line 121-129: Add tests alongside ChecklistControllerTest cases such as
getChecklistById_NOT_FOUND and the additional checklist/nested-item
negative-test sections to cover IllegalChecklistAccessException for resources
owned by another user. Mock the service methods with that exception, perform the
corresponding authenticated checklist and nested-item requests, and assert HTTP
403 plus openApi().isValid("checklist-service.yaml").
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 20833d23-c30d-45af-9d2d-1f1325a21fa8

📥 Commits

Reviewing files that changed from the base of the PR and between eb7a55e and 09aae7d.

⛔ Files ignored due to path filters (4)
  • services/genai-service/__pycache__/main.cpython-311.pyc is excluded by !**/*.pyc
  • services/genai-service/tests/__pycache__/conftest.cpython-311-pytest-8.3.3.pyc is excluded by !**/*.pyc
  • services/genai-service/tests/__pycache__/test_endpoints.cpython-311-pytest-8.3.3.pyc is excluded by !**/*.pyc
  • services/genai-service/tests/__pycache__/test_openapi_contract.cpython-311-pytest-8.3.3.pyc is excluded by !**/*.pyc
📒 Files selected for processing (20)
  • .github/workflows/ci.yml
  • .gitignore
  • api/checklist-service.yaml
  • api/genai-service.yaml
  • infra/docker-compose.yml
  • infra/iac/aet/templates/genai-service/deployment.yaml
  • services/checklist-service/pom.xml
  • services/checklist-service/src/main/java/de/tum/devopss26/checklistservice/controller/ChecklistController.java
  • services/checklist-service/src/main/java/de/tum/devopss26/checklistservice/exception/IllegalChecklistAccessException.java
  • services/checklist-service/src/main/java/de/tum/devopss26/checklistservice/mapper/ChecklistMapper.java
  • services/checklist-service/src/main/java/de/tum/devopss26/checklistservice/service/ChecklistService.java
  • services/checklist-service/src/main/java/de/tum/devopss26/checklistservice/service/ChecklistServiceImpl.java
  • services/checklist-service/src/test/java/de/tum/devopss26/checklistservice/controller/ChecklistControllerTest.java
  • services/genai-service/Dockerfile
  • services/genai-service/main.py
  • services/genai-service/pytest.ini
  • services/genai-service/requirements-dev.txt
  • services/genai-service/tests/conftest.py
  • services/genai-service/tests/test_endpoints.py
  • services/genai-service/tests/test_openapi_contract.py

Comment thread .github/workflows/ci.yml
Comment on lines +46 to +71
python-test:
name: Test Python (GenAI Service)
needs: openapi-lint
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: genai_service_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
SERVICES_POSTGRES_USER: postgres
SERVICES_POSTGRES_PASSWORD: postgres
SERVICES_POSTGRES_URL: localhost
SERVICES_POSTGRES_PORT_INT: 5432
steps:
- name: Checkout Code
uses: actions/checkout@v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Restrict the test job's GitHub token.

This job executes third-party installers and repository tests while inheriting default token permissions and retaining checkout credentials. Limit the token to read-only access and disable credential persistence.

Proposed hardening
   python-test:
     name: Test Python (GenAI Service)
     needs: openapi-lint
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
...
       - name: Checkout Code
         uses: actions/checkout@v4
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
python-test:
name: Test Python (GenAI Service)
needs: openapi-lint
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: genai_service_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
SERVICES_POSTGRES_USER: postgres
SERVICES_POSTGRES_PASSWORD: postgres
SERVICES_POSTGRES_URL: localhost
SERVICES_POSTGRES_PORT_INT: 5432
steps:
- name: Checkout Code
uses: actions/checkout@v4
python-test:
name: Test Python (GenAI Service)
needs: openapi-lint
runs-on: ubuntu-latest
permissions:
contents: read
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: genai_service_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
SERVICES_POSTGRES_USER: postgres
SERVICES_POSTGRES_PASSWORD: postgres
SERVICES_POSTGRES_URL: localhost
SERVICES_POSTGRES_PORT_INT: 5432
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 70-71: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[warning] 46-100: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 46 - 71, Harden the python-test job by
setting its job-level GITHUB_TOKEN permissions to read-only and configuring the
actions/checkout@v4 step with credential persistence disabled. Apply these
changes specifically to the python-test job without altering unrelated workflow
jobs.

Source: Linters/SAST tools

Comment on lines 266 to +272
createdAt:
type: string
format: date-time
items:
type: array
items:
$ref: '#/components/schemas/ChecklistItem'
$ref: '#/components/schemas/IdentifiedChecklistItem'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Mark server-managed checklist fields as read-only.

CreateChecklistRequest and UpdateChecklistRequest inherit createdAt and items, but the controller discards both. Generated clients can therefore submit data that is silently ignored. Use a write-only base schema or mark these properties readOnly.

Proposed contract fix
         createdAt:
           type: string
           format: date-time
+          readOnly: true
         items:
           type: array
+          readOnly: true
           items:
             $ref: '`#/components/schemas/IdentifiedChecklistItem`'

Also applies to: 311-337

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/checklist-service.yaml` around lines 266 - 272, Mark the inherited
server-managed createdAt and items properties as readOnly in the shared
checklist schema used by CreateChecklistRequest and UpdateChecklistRequest,
including the corresponding schema section around the additional referenced
lines. Ensure generated clients cannot treat these fields as writable while
preserving them in response models.

Comment thread infra/docker-compose.yml
condition: service_started
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:${GENAI_SERVICE_APP_PORT}/health || exit 1"]
test: ["CMD-SHELL", "curl -f http://localhost:${GENAI_SERVICE_APP_PORT}/api/genai/api/v1/health || exit 1"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use a health-check executable that exists in the runtime image.
This health check depends on curl, but the runtime image doesn’t install it, so a healthy Uvicorn process can still be marked unhealthy. Use Python’s urllib instead, or add curl to the image.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@infra/docker-compose.yml` at line 182, Update the health check for the GenAI
service in the docker-compose test command to use Python’s urllib instead of
curl, unless curl is explicitly installed in the runtime image; preserve the
existing health endpoint and failure behavior.

Comment on lines +8 to +10
RUN npx --yes @openapitools/openapi-generator-cli generate \
-i api/genai-service.yaml -g python-fastapi \
-o services/genai-service/generated --skip-validate-spec

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf 'Dockerfile:\n'
sed -n '1,40p' services/genai-service/Dockerfile

printf '\nRelevant package/lock files:\n'
git ls-files | rg '(^|/)(package\.json|package-lock\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|yarn.lock|bun.lockb)$'

printf '\nSearch for openapi-generator-cli references:\n'
rg -n "`@openapitools/openapi-generator-cli`|openapi-generator-cli" -S .

Repository: AET-DevOps26/team-devopss26

Length of output: 1669


🏁 Script executed:

set -euo pipefail

printf 'openapitools.json:\n'
if [ -f openapitools.json ]; then
  cat -n openapitools.json
else
  echo '(missing)'
fi

printf '\npackage.json:\n'
if [ -f package.json ]; then
  cat -n package.json
fi

printf '\napi/scripts/gen-all.sh:\n'
if [ -f api/scripts/gen-all.sh ]; then
  cat -n api/scripts/gen-all.sh
fi

printf '\nSearch for generatorVersion/version pins:\n'
rg -n '"generatorVersion"|"version"' openapitools.json package.json api/scripts/gen-all.sh services/genai-service/Dockerfile -S

Repository: AET-DevOps26/team-devopss26

Length of output: 2883


Pin the generator in this Docker stage. services/genai-service/Dockerfile doesn’t copy the repo-level openapitools.json, so npx @openapitools/openapi-generator-cli generate still resolves tooling dynamically on clean builds. Copy the config into the image or pin the package version here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/genai-service/Dockerfile` around lines 8 - 10, Pin the OpenAPI
generator used by the Docker build: update the generator invocation in the RUN
command to use an explicit package version, or copy and apply the repository’s
openapitools.json before generation. Ensure clean builds resolve the same fixed
generator version instead of selecting it dynamically.

Comment on lines 13 to +23
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
COPY services/genai-service/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY main.py .
COPY services/genai-service/main.py .
COPY --from=codegen /app/services/genai-service/generated/src ./generated/src

CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${GENAI_SERVICE_APP_PORT:-8006} --root-path /api/genai"]
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${GENAI_SERVICE_APP_PORT:-8006}"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Drop root privileges in the runtime stage.

Create a dedicated application user and add USER appuser before CMD; this service does not need root privileges.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/genai-service/Dockerfile` around lines 13 - 23, Update the runtime
stage of the Dockerfile to create a dedicated non-root appuser, grant it
ownership or access to /app as needed, and switch to USER appuser before CMD.
Keep the existing uvicorn startup command unchanged.

Source: Linters/SAST tools

Comment on lines +120 to +140
async def _fetch_public_key_locked():
global _public_key, _last_fetch_time
try:
async with httpx.AsyncClient(timeout=5.0) as client:
resp = await client.get(f"{_USER_SERVICE_URL}{_PUBLIC_KEY_PATH}")
resp.raise_for_status()
base64_key = resp.json()["publicKey"]
_public_key = load_der_public_key(base64.b64decode(base64_key))
_last_fetch_time = time.monotonic()
except Exception:
_public_key = None
return _public_key


async def _get_or_fetch_public_key():
if _public_key is not None:
return _public_key
async with _public_key_lock:
if _public_key is not None:
return _public_key
return await _fetch_public_key_locked()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Throttle failed public-key fetches.

On failure, _last_fetch_time remains unchanged, so every authenticated request serially retries the five-second user-service call. Record failed attempts and apply a cooldown/backoff, returning 503 until another fetch is allowed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/genai-service/main.py` around lines 120 - 140, Update
_fetch_public_key_locked and _get_or_fetch_public_key to record the time of
failed fetch attempts and enforce a cooldown before retrying. When a fetch
fails, preserve _public_key as None, update _last_fetch_time, and have
subsequent callers return the existing unavailable result without making another
request until the cooldown expires, so the authentication flow can respond with
503.

Comment on lines +453 to +489
async with _sessions() as db:
if chat_request.conversation_id:
result = await db.execute(
select(ChatConversation).where(ChatConversation.id == chat_request.conversation_id)
)
conversation = result.scalar_one_or_none()
if not conversation:
raise HTTPException(status_code=404, detail="Conversation not found")
if conversation.user_id != user_id:
raise HTTPException(status_code=403, detail="Access denied")
else:
conversation = ChatConversation(user_id=user_id, title=chat_request.message[:100])
db.add(conversation)
await db.flush()

db.add(ChatMessage(
conversation_id=conversation.id, role="USER",
content=chat_request.message, timestamp=_now_ms(),
))

context = ""
try:
chunks = await _fetch_user_data(user_id)
context = await _rag_retrieve(chat_request.message, chunks)
except Exception:
pass

response_text = await _build_chain(chat_request.model).ainvoke({
"message": chat_request.message,
"context": context or "No relevant data found in the user's notes, calendar, or checklists.",
})

db.add(ChatMessage(
conversation_id=conversation.id, role="AGENT",
content=response_text, timestamp=_now_ms(),
))
await db.commit()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Release the database transaction before external calls.

The select/flush starts a transaction that remains open across downstream HTTP, RAG, and LLM calls. Their latency can exhaust the connection pool under load. Persist the initial state, release the session, perform external work, then use a short transaction to store the agent response.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/genai-service/main.py` around lines 453 - 489, Refactor the
conversation/message flow in the async session block so conversation creation or
lookup and the initial USER message are committed before calling
_fetch_user_data, _rag_retrieve, and _build_chain. Perform those external calls
after the session is released, then open a new short-lived _sessions transaction
to add the AGENT message and commit it using the persisted conversation.id.

Comment on lines +46 to +60
db_url = (
f"postgresql+asyncpg://{os.environ['SERVICES_POSTGRES_USER']}:"
f"{os.environ['SERVICES_POSTGRES_PASSWORD']}@"
f"{os.environ['SERVICES_POSTGRES_URL']}:"
f"{os.environ['SERVICES_POSTGRES_PORT_INT']}/genai_service_db"
)
# Schema setup happens on its own throwaway engine/connection so that
# main._engine's connection pool isn't bound to *this* event loop: some
# callers (schemathesis's call_asgi) run requests on a different loop than
# this fixture's, and asyncpg connections can't cross loops.
schema_engine = create_async_engine(db_url)
async with schema_engine.begin() as conn:
await conn.run_sync(main.Base.metadata.drop_all)
await conn.run_sync(main.Base.metadata.create_all)
await schema_engine.dispose()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Require a dedicated test database before dropping tables.

This fixture targets genai_service_db using generic service credentials and immediately calls drop_all. Because setdefault preserves injected production values, an accidental test run in a deployed environment can erase production data. Require a test-only URL/database name and fail unless it is explicitly allowlisted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/genai-service/tests/conftest.py` around lines 46 - 60, Update the
database setup fixture around db_url and schema_engine to require an explicitly
configured, test-only database URL or name before calling
main.Base.metadata.drop_all. Validate it against an explicit allowlist and fail
fast when the configured value is absent or not allowlisted; do not preserve or
reuse injected production database values through defaults.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants