Skip to content

Security fixes: medical OCR separator, admin auth, deployment topology #1

Security fixes: medical OCR separator, admin auth, deployment topology

Security fixes: medical OCR separator, admin auth, deployment topology #1

name: Apply security fixes (temporary)
on:
pull_request:
branches: [main]
permissions:
contents: write
jobs:
apply:
if: github.event.pull_request.head.ref == 'security-fixes-3451e6e' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 1
- name: Apply fixes to real files
shell: bash
run: |
set -euo pipefail
python - <<'PY'
from pathlib import Path
# 1) Medical OCR: remove identity mappings whose keys contain a trailing
# separator. The previous mappings consumed the separator via str.replace.
p = Path('hf-space/app_core.py')
s = p.read_text(encoding='utf-8')
replacements = {
' "ديكلوفيناك ": "ديكلوفيناك", "اوجمينتين": "اوجمنتين",': ' "اوجمينتين": "اوجمنتين",',
' "اوميبرازول ": "اوميبرازول", "سيليبريكس ": "سيليبريكس",': ' "اوميبرازول": "اوميبرازول", "سيليبريكس": "سيليبريكس",',
' "ترامادول ": "ترامادول", "كاتافلام ": "كاتافلام",': ' "كاتافلام": "كاتافلام",',
' "نوفافين ": "نوفافين", "فلاميكس ": "فلاميكس",': ' "فلاميكس": "فلاميكس",',
' "بنادول ": "بنادول", "ادفيل ": "ادفيل",': ' "بنادول": "بنادول", "ادفيل": "ادفيل",',
}
for old, new in replacements.items():
if old not in s:
raise SystemExit(f'Missing expected app_core pattern: {old!r}')
s = s.replace(old, new, 1)
p.write_text(s, encoding='utf-8')
# 2) Authenticate the admin system-status stub using the repository's
# existing dependency.
p = Path('app/routers/admin.py')
s = p.read_text(encoding='utf-8')
s = s.replace('from fastapi import APIRouter\n', 'from fastapi import APIRouter, Depends\nfrom app.core.security import get_current_user\nfrom app.db.models.auth import User\n', 1)
s = s.replace('async def system_status():\n', 'async def system_status(current_user: User = Depends(get_current_user)):\n', 1)
p.write_text(s, encoding='utf-8')
# 3) Separate the alternate full-stack deployment from the CI/HF
# canonical compose file without changing its project-root semantics.
old = Path('deploy/docker-compose.prod.yml')
new = Path('deploy/docker-compose.full.yml')
if not old.exists():
raise SystemExit('deploy/docker-compose.prod.yml is missing')
if new.exists():
raise SystemExit('deploy/docker-compose.full.yml already exists')
old.rename(new)
p = Path('deploy/deploy.sh')
s = p.read_text(encoding='utf-8')
expected = '''DEPLOY_DIR="$(dirname "$0")"\nif [[ -f "${DEPLOY_DIR}/Dockerfile" ]]; then\n cp "${DEPLOY_DIR}/Dockerfile" "${PROJECT_DIR}/Dockerfile"\nfi\nif [[ -f "${DEPLOY_DIR}/docker-compose.prod.yml" ]]; then\n cp "${DEPLOY_DIR}/docker-compose.prod.yml" "${PROJECT_DIR}/docker-compose.prod.yml"\nfi\n'''
replacement = '''DEPLOY_DIR="$(dirname "$0")"\nCOMPOSE_FILE="${DEPLOY_DIR}/docker-compose.full.yml"\nif [[ -f "${DEPLOY_DIR}/Dockerfile" ]]; then\n cp "${DEPLOY_DIR}/Dockerfile" "${PROJECT_DIR}/Dockerfile"\nfi\nif [[ ! -f "$COMPOSE_FILE" ]]; then\n error "Full-stack compose file not found: $COMPOSE_FILE"\nfi\n'''
if expected not in s:
raise SystemExit('Expected deploy.sh compose-copy block not found')
s = s.replace(expected, replacement, 1)
s = s.replace('docker compose -f docker-compose.prod.yml', 'docker compose --project-directory "$PROJECT_DIR" -f "$COMPOSE_FILE"')
s = s.replace('docker-compose.prod.yml', 'deploy/docker-compose.full.yml')
# The previous replacement changes the COMPOSE_FILE path if applied to
# its literal value; restore the actual variable definition.
s = s.replace('COMPOSE_FILE="${DEPLOY_DIR}/deploy/docker-compose.full.yml"', 'COMPOSE_FILE="${DEPLOY_DIR}/docker-compose.full.yml"')
p.write_text(s, encoding='utf-8')
PY
- name: Add behavioral medical regression tests
shell: bash
run: |
set -euo pipefail
mkdir -p tests/security
cat > tests/security/test_medical_behavior.py <<'PY'
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / 'hf-space'))
import app_core
def test_tramadol_dose_separator_preserved():
out, _ = app_core._auto_correct_ocr('ترامادول 0.5 mg')
assert out == 'ترامادول 0.5 mg'
def test_medical_negation_and_dose_preserved():
text = 'لا يعطى ترامادول 0.5 mg'
out, _ = app_core._auto_correct_ocr(text)
assert out == text
def test_decimals_preserved():
for text in ['0.5', '1.25', '0.75', '٠٫٥', '١٫٢٥']:
out, _ = app_core._auto_correct_ocr(text)
assert out == text
def test_negations_preserved():
for text in ['لا', 'ليس', 'لم', 'لن', 'غير', 'بدون', 'لا يوجد سكري']:
out, _ = app_core._auto_correct_ocr(text)
assert out == text
def test_intended_ocr_correction_remains():
out, _ = app_core._auto_correct_ocr('باراسيتبمول 500 mg')
assert out == 'باراسيتامول 500 mg'
PY
- name: Run targeted behavioral tests
shell: bash
run: |
set -euo pipefail
python -m pip install --disable-pip-version-check pytest numpy opencv-python-headless 'gradio<6' >/dev/null
pytest -q tests/security/test_medical_behavior.py
- name: Verify deployment references and admin source
shell: bash
run: |
set -euo pipefail
test -f deploy/docker-compose.full.yml
test -f docker-compose.prod.yml
! grep -q 'cp .*docker-compose.prod.yml.*docker-compose.prod.yml' deploy/deploy.sh
grep -q 'docker-compose.full.yml' deploy/deploy.sh
grep -q 'Depends(get_current_user)' app/routers/admin.py
grep -q '"ترامادول": "ترامادول"' hf-space/app_core.py
! grep -q '"ترامادول ": "ترامادول"' hf-space/app_core.py
- name: Commit verified fixes back to PR branch
shell: bash
run: |
set -euo pipefail
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git status --short
git diff --check
git diff --stat
git add hf-space/app_core.py app/routers/admin.py deploy/deploy.sh deploy/docker-compose.full.yml tests/security/test_medical_behavior.py
git rm --cached deploy/docker-compose.prod.yml 2>/dev/null || true
git add -A deploy/docker-compose.prod.yml deploy/docker-compose.full.yml
if git diff --cached --quiet; then
echo 'No changes to commit.'
exit 0
fi
git commit -m 'fix(security): harden medical OCR and production topology'
git push origin HEAD:${{ github.head_ref }}