Skip to content

fix(server): harden security, fix review issues from PR #189 - #190

Merged
YaoYinYing merged 4 commits into
mainfrom
fix/pr-189-review-fixes
Jul 15, 2026
Merged

fix(server): harden security, fix review issues from PR #189#190
YaoYinYing merged 4 commits into
mainfrom
fix/pr-189-review-fixes

Conversation

@YaoYinYing

@YaoYinYing YaoYinYing commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Summary

Addresses review discussions from CodeRabbit, Codex, and DeepSource on PR #189.

Security

  • Host header spoofing: _public_base_url() now always uses SERVER_BASE_URL, never the request Host header
  • Account enumeration: resend-verification endpoint returns generic response for all non-actionable cases
  • Role demotion: clearing admin role now also clears is_admin flag

Correctness

  • Cancelled tasks treated as terminal in DB update guard
  • Admin digest checks _send_email() return value, unmarks users on failure
  • Exception handlers guard _pack_failed_results_archive with _task_is_deleted()
  • Celery apply_async failure marks task as failed + 503
  • Batch-enable sets email_verified = True
  • Legacy NULL columns backfilled in _ensure_columns()
  • SQLAlchemy metadata includes all columns

Robustness

  • Rate limiter prunes expired IP entries
  • normalize_email() guards against non-string input
  • _inject_admin_password targets user DB not task DB
  • Server test env decoupled from root conftest (no PyMOL dependency)

Infrastructure

  • CI: actions/setup-python pinned SHA → @v6
  • RESULT_RETENTION_DAYS env var (default 30)
  • Codecov coverage for server subproject (flags: server)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added configurable retention for task result files (default 30 days; set to 0 to disable auto-cleanup).
  • Bug Fixes
    • Prevented late updates from modifying tasks that are cancelled or deleted.
    • Hardened verification and admin email/digest behavior, including privacy-safe resend responses and improved role/admin handling.
    • Improved failed-task handling and rate limiting stability (including memory pruning).
  • Documentation
    • Updated the server changelog and .env example.
  • Tests & CI
    • Enhanced test coverage reporting and Codecov uploads; improved test setup/skip behavior when Docker isn’t available.

- Host header spoofing: _public_base_url() uses SERVER_BASE_URL exclusively
- Role demotion now clears is_admin flag
- Cancelled tasks treated as terminal (prevent late-write resurrection)
- Admin digest checks _send_email() return value, unmarks on failure
- Exception handlers guard _pack_failed_results_archive with _task_is_deleted()
- Celery apply_async failure now marks task failed + 503 response
- Resend-verification returns generic response (prevents account enumeration)
- Batch-enable sets email_verified=True
- _ensure_columns() backfills deleted, registration_status, user_status, role
- Rate limiter prunes expired IP entries periodically
- normalize_email() guards against non-string input
- _inject_admin_password targets user DB not task DB
- Missing columns added to SQLAlchemy _users_table metadata
- Server test env decoupled from root tests/conftest.py
- CI: actions/setup-python pinned SHA → @v6
- Add RESULT_RETENTION_DAYS env var (default 30)
- Add Codecov coverage tracking for server subproject (flags: server)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@YaoYinYing, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e2cc8cf2-c85e-4b95-81e8-61c9442e80f4

📥 Commits

Reviewing files that changed from the base of the PR and between 695cb92 and 853b6e3.

📒 Files selected for processing (2)
  • codecov.yml
  • server/pssm_gremlin_server/auth.py
📝 Walkthrough

Walkthrough

The PR hardens authentication, task lifecycle handling, rate limiting, and Celery submission. It adds result-retention configuration, improves Docker-aware test setup, and enables server coverage reporting in CI.

Changes

GREMLIN server hardening

Layer / File(s) Summary
Authentication data and email handling
server/pssm_gremlin_server/auth.py, server/pssm_gremlin_server/schemas.py
User schema backfills legacy fields, email links use SERVER_BASE_URL, admin digest success is tracked, and invalid non-string emails raise ValueError.
Authentication and admin route behavior
server/pssm_gremlin_server/routes.py
Verification resend responses are generalized, role changes update is_admin directly, and batch enabling verifies email addresses.
Terminal task and submission handling
server/pssm_gremlin_server/db.py, server/pssm_gremlin_server/pssm_gremlin.py, server/pssm_gremlin_server/routes.py
Cancelled tasks are terminal, terminal tasks avoid result and failed-artifact finalization, and Celery submission failures mark tasks failed and return HTTP 503.
Rate limiting and runtime configuration
server/pssm_gremlin_server/ratelimit.py, server/.env.example
Expired per-IP rate-limit state is periodically pruned using monotonic time, and result retention settings are documented.
Test environment and coverage workflow
server/tests/*, server/pyproject.toml, .github/workflows/server-test.yml, .gitignore, CHANGELOG.md
Docker checks and test paths are localized, coverage tooling and Codecov upload are added, generated metadata is ignored, and fixes are recorded in the changelog.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is directly related to the PR’s main goals: security hardening and addressing review issues from the prior PR.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pr-189-review-fixes

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.

@deepsource-io

deepsource-io Bot commented Jul 15, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 1061426...853b6e3 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Python Jul 15, 2026 1:57p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

@codacy-production

codacy-production Bot commented Jul 15, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 22 complexity · 2 duplication

Metric Results
Complexity 22
Duplication 2

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23ffdcc4f9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/pssm_gremlin_server/auth.py Outdated
)
# Backfill legacy rows that predate the column — ensures no NULLs
# slip through for columns added after initial schema creation.
if "deleted" not in existing:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Backfill NULLs even when columns already exist

On databases that already ran the previous migration, these columns are already present in existing but legacy rows can still contain NULL, so this new backfill is skipped exactly where it is needed. Those rows remain broken after upgrade: for example list_users() filters deleted == False, excluding NULL rows, and UserResponse expects non-null registration_status/user_status/role, so admin user listing can still hide users or fail validation.

Useful? React with 👍 / 👎.

Comment on lines +900 to +901
if not _task_is_deleted(md5sum):
_pack_failed_results_archive(task, error_message)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat cancelled tasks as terminal before repacking failures

When a user cancels a running task, cancel_task() deletes the artifacts and records status='cancelled', but a terminated Docker run commonly reaches this exception path afterward. Since this new guard only checks deleted statuses, the worker still calls _pack_failed_results_archive() for a cancelled task, recreating the result directory/zip that cancellation just removed while the DB guard prevents the status from changing away from cancelled.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
server/pssm_gremlin_server/ratelimit.py (1)

34-48: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Use time.monotonic() instead of time.time() for rate limiting.

System clock adjustments (e.g., via NTP syncs) can cause time.time() to jump backwards or forwards. A backwards jump could unexpectedly reset user rate limits, while also causing now - _last_cleanup to become negative and inadvertently stall the pruning step for an extended period.

Using time.monotonic() is a robust best practice for in-memory interval tracking and rate limiters since it strictly advances and is unaffected by system clock updates.

♻️ Proposed refactor
-    _last_cleanup: float = 0.0
+    _last_cleanup: float = time.monotonic()
 
     def _prune_expired(now: float, cutoff: float) -> None:
         """Drop per-IP entries whose most recent timestamp is expired."""
         empty = [ip for ip, ts in state.items() if not ts or ts[-1] <= cutoff]
         for ip in empty:
             del state[ip]
 
     def decorator(f: Callable) -> Callable:
         `@wraps`(f)
         def decorated(*args: Any, **kwargs: Any) -> Any:
             nonlocal _last_cleanup
             ip = request.remote_addr or "unknown"
-            now = time.time()
+            now = time.monotonic()
             cutoff = now - window_seconds
🤖 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 `@server/pssm_gremlin_server/ratelimit.py` around lines 34 - 48, Replace the
time.time() call in the rate-limiting decorator with time.monotonic(), ensuring
now, cutoff, timestamp comparisons, and _last_cleanup interval checks all use
the same monotonic clock.
🤖 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 `@server/pssm_gremlin_server/auth.py`:
- Around line 175-192: Add a legacy-data backfill in the schema migration logic
alongside the existing checks for deleted, registration_status, user_status, and
role: when verification_resend_count is absent from existing, update users rows
where it is NULL to 0, preserving the column’s non-null default and constraint.
- Around line 920-934: Move the exception handling inside the recipients loop so
each _send_email attempt is isolated and processing continues for all admins
without resetting a prior successful any_sent value. Catch the specific expected
exception rather than a blind Exception, log the failure with appropriate
context, and preserve the existing unmark_users_notified behavior based on
whether any send succeeded.

In `@server/pssm_gremlin_server/pssm_gremlin.py`:
- Around line 900-936: The exception handlers around _task_is_deleted must also
skip _pack_failed_results_archive and failure updates for cancelled tasks; use
the existing TaskDatabase.TERMINAL_STATUSES check or a shared predicate that
recognizes all terminal statuses. Apply this consistently to each handler, and
extract the duplicated failure-handling logic into one helper only if it
preserves the current status, timing, archive, and logging behavior.

In `@server/pssm_gremlin_server/routes.py`:
- Around line 286-295: Update the apply_async exception handler in the task
submission flow to call _pack_failed_results_archive for md5sum after marking
the task failed, matching the existing binary-upload and invalid-FASTA failure
paths so clients receive a terminal archive.

In `@server/tests/conftest.py`:
- Around line 32-43: The duplicated has_docker_daemon implementations in
server/tests/conftest.py lines 32-43 and server/tests/test_server.py lines 31-43
incorrectly treat failed docker info commands as success. Add check=True to
subprocess.run and catch only (subprocess.SubprocessError, OSError) in both
sites; preferably centralize the function in shared test utilities or reuse the
conftest implementation from test_server.py.

---

Nitpick comments:
In `@server/pssm_gremlin_server/ratelimit.py`:
- Around line 34-48: Replace the time.time() call in the rate-limiting decorator
with time.monotonic(), ensuring now, cutoff, timestamp comparisons, and
_last_cleanup interval checks all use the same monotonic clock.
🪄 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

Run ID: 2ed34e6c-79f1-4957-8150-34a14fa2dfb4

📥 Commits

Reviewing files that changed from the base of the PR and between 1061426 and 23ffdcc.

📒 Files selected for processing (13)
  • .github/workflows/server-test.yml
  • .gitignore
  • CHANGELOG.md
  • server/.env.example
  • server/pssm_gremlin_server/auth.py
  • server/pssm_gremlin_server/db.py
  • server/pssm_gremlin_server/pssm_gremlin.py
  • server/pssm_gremlin_server/ratelimit.py
  • server/pssm_gremlin_server/routes.py
  • server/pssm_gremlin_server/schemas.py
  • server/pyproject.toml
  • server/tests/conftest.py
  • server/tests/test_server.py

Comment thread server/pssm_gremlin_server/auth.py Outdated
Comment thread server/pssm_gremlin_server/auth.py
Comment thread server/pssm_gremlin_server/pssm_gremlin.py Outdated
Comment thread server/pssm_gremlin_server/routes.py Outdated
Comment thread server/tests/conftest.py
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.50%. Comparing base (1061426) to head (853b6e3).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #190      +/-   ##
==========================================
- Coverage   73.56%   73.50%   -0.07%     
==========================================
  Files         122      122              
  Lines       15221    15221              
==========================================
- Hits        11198    11188      -10     
- Misses       4023     4033      +10     

see 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

YaoYinYing and others added 2 commits July 15, 2026 21:39
- CI: install pytest-order, pytest-dependency; override root addopts
- CI: run Codecov upload only on test success (not always())
- ratelimit: use time.monotonic() for clock-jump resilience
- auth: backfill verification_resend_count NULL -> 0 in _ensure_columns()
- auth: move try/except inside admin digest recipient loop
- pssm_gremlin: rename _is_deleted_status -> _is_terminal_status (includes cancelled)
- pssm_gremlin: exception handlers check terminal status not just deleted
- routes: pack failed results archive on Celery submission failure
- tests: has_docker_daemon uses check=True for accurate Docker detection

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backfills were gated on 'col not in existing', but databases that already
ran a prior migration still have NULLs in legacy rows. Backfills now run
unconditionally (WHERE col IS NULL — idempotent), except admin_notified
which must remain gated to avoid re-marking everyone on restart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
server/pssm_gremlin_server/pssm_gremlin.py (1)

881-894: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update log messages to reflect the new cancelled state.

Since the terminal status checks (_task_is_terminal and _is_terminal_status) now include cancelled tasks, consider updating these log messages from "was deleted" to "was deleted or cancelled" to prevent confusion during troubleshooting.

♻️ Proposed log message updates
         if _task_is_terminal(md5sum):
-            logging.info("Task %s was deleted during execution; skipping result packing and finalization.", md5sum)
+            logging.info("Task %s was deleted or cancelled during execution; skipping result packing and finalization.", md5sum)
             return
         final_stage = stage_state["current"] or _RUNNING_TRACE_STEPS[-1][0]
         task_store.update_task(md5sum, status="packing results", run_stage=final_stage)
         refreshed_task = task_store.get_task(md5sum) or task
         if _is_terminal_status(refreshed_task.get("status")):
-            logging.info("Task %s was deleted before archive packing; skipping artifact packaging.", md5sum)
+            logging.info("Task %s was deleted or cancelled before archive packing; skipping artifact packaging.", md5sum)
             return
         _pack_results_archive(refreshed_task)
         refreshed_task = task_store.get_task(md5sum) or refreshed_task
         if _is_terminal_status(refreshed_task.get("status")):
-            logging.info("Task %s was deleted during archive packing; skipping final status update.", md5sum)
+            logging.info("Task %s was deleted or cancelled during archive packing; skipping final status update.", md5sum)
             return
🤖 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 `@server/pssm_gremlin_server/pssm_gremlin.py` around lines 881 - 894, Update
the three terminal-task log messages in the finalization flow around
_task_is_terminal and _is_terminal_status to say “was deleted or cancelled”
instead of only “was deleted.” Preserve the existing checks, control flow, and
message context for skipping result packing, artifact packaging, and final
status updates.
🤖 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.

Nitpick comments:
In `@server/pssm_gremlin_server/pssm_gremlin.py`:
- Around line 881-894: Update the three terminal-task log messages in the
finalization flow around _task_is_terminal and _is_terminal_status to say “was
deleted or cancelled” instead of only “was deleted.” Preserve the existing
checks, control flow, and message context for skipping result packing, artifact
packaging, and final status updates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 15618718-d71f-465c-b8a0-6ad51f37baa6

📥 Commits

Reviewing files that changed from the base of the PR and between 23ffdcc and 695cb92.

📒 Files selected for processing (7)
  • .github/workflows/server-test.yml
  • server/pssm_gremlin_server/auth.py
  • server/pssm_gremlin_server/pssm_gremlin.py
  • server/pssm_gremlin_server/ratelimit.py
  • server/pssm_gremlin_server/routes.py
  • server/tests/conftest.py
  • server/tests/test_server.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • server/pssm_gremlin_server/ratelimit.py
  • .github/workflows/server-test.yml
  • server/pssm_gremlin_server/auth.py
  • server/pssm_gremlin_server/routes.py

Codecov needs flag-to-path mapping to match uploaded coverage
with PR changes. Without this, the server CI upload shows
'No Files covered by tests were changed.'

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@YaoYinYing
YaoYinYing merged commit 39f29a1 into main Jul 15, 2026
13 of 15 checks passed
@YaoYinYing
YaoYinYing deleted the fix/pr-189-review-fixes branch July 15, 2026 14:32
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.

1 participant