Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
b57eb24
fix(server): address deepsource audit findings
YaoYinYing Jul 16, 2026
8d7d1f5
fix(deepsource): exclude server tests from audit noise
YaoYinYing Jul 16, 2026
69ad82b
docs(deepsource): plan active issue reduction
YaoYinYing Jul 16, 2026
39e911e
docs(deepsource): drop reduction plan
YaoYinYing Jul 16, 2026
58ccb65
ci(deepsource): upload coverage reports
YaoYinYing Jul 16, 2026
883ba78
Revert "ci(deepsource): upload coverage reports" [ci skip]
YaoYinYing Jul 16, 2026
ea08200
fix(security): use cryptographic password randomness
YaoYinYing Jul 16, 2026
73abc91
fix(security): require verified download hashes
YaoYinYing Jul 16, 2026
f0b6016
fix(security): avoid logging editor auth tokens
YaoYinYing Jul 16, 2026
051cb86
fix(deepsource): clean up utility findings
YaoYinYing Jul 16, 2026
7790f41
fix(security): verify ESM model downloads
YaoYinYing Jul 16, 2026
802bdbe
fix(dev): keep UI validation active under optimization
YaoYinYing Jul 16, 2026
1ea1350
fix(dev): parse UI XML with defusedxml
YaoYinYing Jul 16, 2026
f653f90
fix(deepsource): log silent fallback errors
YaoYinYing Jul 16, 2026
8d56a16
fix(deepsource): use secure random for Vina object names
YaoYinYing Jul 16, 2026
6ebdf2e
fix(server): use secure jitter for digest scheduling
YaoYinYing Jul 16, 2026
20b7792
fix(dev): parse UI typing XML with Qt stream reader
YaoYinYing Jul 16, 2026
45c8a22
fix(security): clean remaining production audit findings
YaoYinYing Jul 16, 2026
5b61c1f
fix(deepsource): clear higher-risk flake8 findings
YaoYinYing Jul 16, 2026
4135cad
fix(deepsource): add warning stacklevels
YaoYinYing Jul 16, 2026
7bcbb14
fix(deepsource): expand overload stubs
YaoYinYing Jul 16, 2026
2049401
fix(deepsource): wrap long source lines
YaoYinYing Jul 17, 2026
831340f
fix(deepsource): preserve expanded overload stubs
YaoYinYing Jul 17, 2026
52551c8
fix(deepsource): wrap runtime doc lines
YaoYinYing Jul 17, 2026
9f63f34
fix(deepsource): wrap utility long lines
YaoYinYing Jul 17, 2026
c01e01b
fix(deepsource): wrap tools doc lines
YaoYinYing Jul 17, 2026
cb52d7a
fix(deepsource): wrap shortcut command strings
YaoYinYing Jul 17, 2026
879bdfe
docs(tests): document mini UniRef fixtures
YaoYinYing Jul 17, 2026
755e0b0
docs(server): separate setup guide from audit checks
YaoYinYing Jul 17, 2026
31476e3
docs(server): record security test status
YaoYinYing Jul 17, 2026
ec81815
fix(deepsource): wrap sidechain and diffusion lines
YaoYinYing Jul 17, 2026
04255e6
fix(deepsource): wrap rosetta citation lines
YaoYinYing Jul 17, 2026
f044164
fix(deepsource): wrap design citation lines
YaoYinYing Jul 17, 2026
6cb3db4
fix(config): remove local workspace paths
YaoYinYing Jul 17, 2026
d04ec83
fix(deepsource): close file handles deterministically
YaoYinYing Jul 17, 2026
aecdce8
fix(deepsource): route tracebacks through logger
YaoYinYing Jul 17, 2026
af708b2
fix(shortcuts): clean up conformer preview processes
YaoYinYing Jul 17, 2026
f02ccc3
fix(security): harden archive member validation
YaoYinYing Jul 17, 2026
0bd32df
docs(dev): drop resolved monaco token item
YaoYinYing Jul 17, 2026
146c06a
test(deepsource): lock resolved security audit checks
YaoYinYing Jul 17, 2026
6920cfe
fix(config): remove workstation-specific paths
YaoYinYing Jul 17, 2026
e53fc5e
fix(security): harden generated ssl certificates
YaoYinYing Jul 17, 2026
4d2f3f4
fix(rfdiffusion): defer matplotlib backend selection
YaoYinYing Jul 17, 2026
1c6221c
docs(downloads): require registry hashes
YaoYinYing Jul 17, 2026
d2a108c
fix(workers): propagate worker thread errors
YaoYinYing Jul 17, 2026
29daae4
fix(rfdiffusion): require approval before dgl install
YaoYinYing Jul 17, 2026
9ba7d44
fix(config): avoid bootstrap reset prompts in headless runs
YaoYinYing Jul 17, 2026
28bd448
fix(plugin): keep working directory scoped
YaoYinYing Jul 17, 2026
d128ed5
test(config): isolate bootstrap user dirs
YaoYinYing Jul 17, 2026
bc4fd9e
fix(server): remove cluster path defaults
YaoYinYing Jul 17, 2026
078f9f5
fix(config): preserve existing environment variables
YaoYinYing Jul 17, 2026
8a0b76c
fix(workers): avoid global pymol interrupt on abort
YaoYinYing Jul 17, 2026
8bd1947
fix(package-manager): cache github tag lookups
YaoYinYing Jul 17, 2026
69c0d18
fix(file-dialog): extract archives into temp dirs
YaoYinYing Jul 17, 2026
5a49e68
fix(audit): address archive and tag cache regressions
YaoYinYing Jul 17, 2026
752453b
fix(package-manager): cache-first startup, HMAC-verified self-upgrade
YaoYinYing Jul 19, 2026
78e1f51
fix(package-manager): use retrying self-bootstrap assets
YaoYinYing Jul 19, 2026
850152c
fix(package-manager): verify bootstrap assets before writing
YaoYinYing Jul 19, 2026
2544f88
test(package-manager): cover HMAC bootstrap verification
YaoYinYing Jul 19, 2026
8492338
lint
YaoYinYing Jul 19, 2026
717e132
fix(tests): repair conftest config isolation and ValueDialog font guard
YaoYinYing Jul 19, 2026
0f3882a
test(ci): fix PR test regressions
YaoYinYing Jul 22, 2026
7993d51
fix(dev-tools): support Qt6 UI typing parser
YaoYinYing Jul 22, 2026
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
24 changes: 15 additions & 9 deletions server/pssm_gremlin_server/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from typing import Any

import sqlalchemy as sa
from flask import current_app, g, jsonify, redirect, render_template, request, url_for
from flask import current_app, g, jsonify, redirect, request, url_for
from itsdangerous import BadSignature, SignatureExpired, URLSafeTimedSerializer
from werkzeug.security import check_password_hash, generate_password_hash

Expand Down Expand Up @@ -471,21 +471,27 @@ def load_current_user() -> dict[str, Any] | None:
payload = validate_token(token)
if payload is not None:
user = db.get_user(payload["uid"])
if user is not None and _is_account_blocked(user) is None:
if payload.get("ver", 0) == user.get("token_version", 0):
g.auth_method = "bearer"
return user
if (
user is not None
and _is_account_blocked(user) is None
and payload.get("ver", 0) == user.get("token_version", 0)
):
g.auth_method = "bearer"
return user
# 2. Cookie — browser page navigations after login (read-only; CSRF-prone)
# Guest accounts ARE permitted to use cookie-based web access.
token = request.cookies.get("auth_token")
if token:
payload = validate_token(token)
if payload is not None:
user = db.get_user(payload["uid"])
if user is not None and _is_account_blocked(user) is None:
if payload.get("ver", 0) == user.get("token_version", 0):
g.auth_method = "cookie"
return user
if (
user is not None
and _is_account_blocked(user) is None
and payload.get("ver", 0) == user.get("token_version", 0)
):
g.auth_method = "cookie"
return user

# 3. API key (programmatic access — restricted privileges)
# Guest accounts are not permitted to use API keys.
Expand Down
15 changes: 5 additions & 10 deletions server/pssm_gremlin_server/pssm_gremlin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
# Distributed under the terms of the GNU General Public License v3.0.
# SPDX-License-Identifier: GPL-3.0-only


#! /mnt/data/envs/conda_env/envs/REvoDesign/bin/python

from __future__ import annotations

import grp
Expand All @@ -26,7 +23,7 @@
from celery import Celery
from celery.result import AsyncResult
from docker import types
from flask import Flask, current_app, g, jsonify, request
from flask import Flask, g, jsonify, request
from pssm_gremlin_server.db import TaskDatabase
from sqlalchemy.exc import IntegrityError
from werkzeug.utils import secure_filename
Expand All @@ -39,7 +36,6 @@

from pssm_gremlin_server.auth import UserDatabase # noqa: E402
from pssm_gremlin_server.auth import _env_bool # noqa: E402
from pssm_gremlin_server.auth import _env_int # noqa: E402
from pssm_gremlin_server.auth import _env_str # noqa: E402
from pssm_gremlin_server.auth import send_admin_digest # noqa: E402

Expand Down Expand Up @@ -253,7 +249,7 @@ def _digest_loop() -> None:
with open(_digest_lock, "w") as _lock_fh:
fcntl.flock(_lock_fh.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
send_admin_digest()
except (BlockingIOError, OSError):
except OSError:
pass # another worker has the lock — try next cycle
except Exception:
logging.exception("Admin digest thread failed")
Expand Down Expand Up @@ -660,7 +656,7 @@ def _create_mount(mount_name: str, path: str, read_only=True) -> tuple[types.Mou
target_path = os.path.join(_ROOT_MOUNT_DIRECTORY, mount_name)

if not read_only:
logging.warning(f"{mount_name} is not read-only!")
logging.warning("%s is not read-only!", mount_name)

if os.path.isdir(path):
source_path = path
Expand All @@ -670,7 +666,7 @@ def _create_mount(mount_name: str, path: str, read_only=True) -> tuple[types.Mou
mounted_path = os.path.join(target_path, os.path.basename(path))
if not os.path.exists(source_path):
os.makedirs(source_path)
logging.info(f"Mounting {source_path} -> {target_path}")
logging.info("Mounting %s -> %s", source_path, target_path)
mount = types.Mount(
target=str(target_path),
source=str(source_path),
Expand Down Expand Up @@ -818,8 +814,7 @@ def _pack_failed_results_archive(task: dict, error: Any) -> None:
def format_times(timestamp):
if timestamp:
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
else:
return None
return None


def format_walltime(seconds: Any) -> str:
Expand Down
55 changes: 28 additions & 27 deletions server/pssm_gremlin_server/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import os
import shutil
import time
from typing import Any

from celery.result import AsyncResult
from flask import current_app, g, jsonify, redirect, render_template, request, send_from_directory, url_for
Expand Down Expand Up @@ -735,23 +736,21 @@ def auth_forgot_password():
return jsonify({"message": "If that email is registered, a reset link has been sent."}), 200


@app.route("/PSSM_GREMLIN/reset_password", methods=["GET", "POST"])
def auth_reset_password():
"""Password-reset page.
@app.route("/PSSM_GREMLIN/reset_password", methods=["GET"])
def auth_reset_password_page():
"""Render the password-reset page for a valid reset token."""
token = request.args.get("c", "").strip()
if not token:
return render_template("error.html", code=400, message="Missing reset token."), 400
user_id = validate_reset_token(token)
if user_id is None:
return render_template("error.html", code=400, message="Invalid or expired reset token."), 400
return render_template("reset-password.html", token=token), 200

``GET`` — renders the new-password form (requires ``?c=`` token).
``POST`` — sets the new password.
"""
if request.method == "GET":
token = request.args.get("c", "").strip()
if not token:
return render_template("error.html", code=400, message="Missing reset token."), 400
user_id = validate_reset_token(token)
if user_id is None:
return render_template("error.html", code=400, message="Invalid or expired reset token."), 400
return render_template("reset-password.html", token=token), 200

# POST

@app.route("/PSSM_GREMLIN/reset_password", methods=["POST"])
def auth_reset_password():
"""Set a new password using a password-reset token."""
Comment on lines +751 to +753

Copy link
Copy Markdown

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:

#!/bin/bash
# Description: Check if update_user handles token_version automatically.

rg -A 20 'def update_user\b' server/

Repository: YaoYinYing/REvoDesign

Length of output: 1663


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- auth.py outline ---'
ast-grep outline server/pssm_gremlin_server/auth.py --view expanded | sed -n '1,220p'

echo
echo '--- routes.py outline ---'
ast-grep outline server/pssm_gremlin_server/routes.py --view expanded | sed -n '1,220p'

echo
echo '--- auth.py relevant slice ---'
grep -n -A80 -B20 'def update_user\b' server/pssm_gremlin_server/auth.py | sed -n '1,180p'

echo
echo '--- routes.py relevant slice ---'
grep -n -A80 -B20 'def auth_reset_password\b' server/pssm_gremlin_server/routes.py | sed -n '1,180p'

Repository: YaoYinYing/REvoDesign

Length of output: 13781


🏁 Script executed:

#!/bin/bash
set -euo pipefail

grep -n -A60 -B20 'def generate_token\b\|def validate_token\b\|token_version' server/pssm_gremlin_server/auth.py | sed -n '1,220p'

Repository: YaoYinYing/REvoDesign

Length of output: 11088


Invalidate sessions on password reset. db.update_user(...) only updates the password hash; it does not bump token_version, so existing bearer tokens and auth cookies remain valid. Increment token_version here as well.

🤖 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/routes.py` around lines 751 - 753, Update
auth_reset_password to increment the user’s token_version when completing the
password reset, alongside the db.update_user password-hash update. Ensure the
updated version is persisted so existing bearer tokens and authentication
cookies are invalidated while the reset flow continues to issue the new password
successfully.

req = _parse_body(ResetPasswordRequest)
if isinstance(req, tuple):
return req
Expand Down Expand Up @@ -1069,27 +1068,29 @@ def auth_revoke_api_key():
return jsonify({"message": "API key revoked"}), 200


@app.route("/PSSM_GREMLIN/api/auth/admin/users", methods=["GET", "POST"])
@app.route("/PSSM_GREMLIN/api/auth/admin/users", methods=["GET"])
@login_required
def admin_users():
"""Admin-only user management.

``GET`` — list all users (safe fields only).
``POST`` — create a new user (auto-verified, immediately active).
"""
"""Admin-only user listing with safe fields only."""
if _blocked := require_admin():
return _blocked

db = _get_user_db()
users = db.list_users()
safe = [UserResponse.model_validate(u).model_dump() for u in users]
return jsonify({"users": safe}), 200

if request.method == "GET":
users = db.list_users()
safe = [UserResponse.model_validate(u).model_dump() for u in users]
return jsonify({"users": safe}), 200

# POST — state-changing, require Bearer token for CSRF protection
@app.route("/PSSM_GREMLIN/api/auth/admin/users", methods=["POST"])
@login_required
def admin_create_user():
"""Admin-only user creation for pre-approved accounts."""
if _blocked := require_admin():
return _blocked
if _blocked := require_bearer_auth():
return _blocked

db = _get_user_db()
req = _parse_body(AdminCreateUserRequest)
if isinstance(req, tuple):
return req
Expand Down
Loading