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 v2 (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 real fixes and tests
shell: bash
run: |
set -euo pipefail
python - <<'PY'
from pathlib import Path
# Medical OCR: remove identity mappings whose trailing spaces were
# previously consumed by 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 in s:
s = s.replace(old, new, 1)
p.write_text(s, encoding='utf-8')
# Admin stub authentication.
p = Path('app/routers/admin.py')
s = p.read_text(encoding='utf-8')
if 'from app.core.security import get_current_user' not in s:
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():', 'async def system_status(current_user: User = Depends(get_current_user)):', 1)
p.write_text(s, encoding='utf-8')
# Full-stack compose is explicitly alternate; never overwrite the
# root CI/HF canonical compose.
old = Path('deploy/docker-compose.prod.yml')
new = Path('deploy/docker-compose.full.yml')
if old.exists() and not new.exists():
old.rename(new)
p = Path('deploy/deploy.sh')
s = p.read_text(encoding='utf-8')
if 'COMPOSE_FILE="${DEPLOY_DIR}/docker-compose.full.yml"' not in s:
marker = 'DEPLOY_DIR="$(dirname "$0")"\n'
s = s.replace(marker, marker + 'COMPOSE_FILE="${DEPLOY_DIR}/docker-compose.full.yml"\n', 1)
old_block = '''if [[ -f "${DEPLOY_DIR}/docker-compose.prod.yml" ]]; then\n cp "${DEPLOY_DIR}/docker-compose.prod.yml" "${PROJECT_DIR}/docker-compose.prod.yml"\nfi\n'''
s = s.replace(old_block, '')
if 'if [[ ! -f "$COMPOSE_FILE" ]]; then' not in s:
needle = 'if [[ -f "${DEPLOY_DIR}/Dockerfile" ]]; then\n cp "${DEPLOY_DIR}/Dockerfile" "${PROJECT_DIR}/Dockerfile"\nfi\n'
s = s.replace(needle, needle + 'if [[ ! -f "$COMPOSE_FILE" ]]; then\n error "Full-stack compose file not found: $COMPOSE_FILE"\nfi\n', 1)
s = s.replace('docker compose -f docker-compose.prod.yml', 'docker compose --project-directory "$PROJECT_DIR" -f "$COMPOSE_FILE"')
p.write_text(s, encoding='utf-8')
# Behavioral tests against the actual app_core implementation.
t = Path('tests/security/test_medical_behavior.py')
t.parent.mkdir(parents=True, exist_ok=True)
t.write_text('''import sys\nfrom pathlib import Path\n\nROOT = Path(__file__).resolve().parents[2]\nsys.path.insert(0, str(ROOT / "hf-space"))\nimport app_core\n\ndef test_tramadol_dose_separator_preserved():\n out, _ = app_core._auto_correct_ocr("ترامادول 0.5 mg")\n assert out == "ترامادول 0.5 mg"\n\ndef test_medical_negation_and_dose_preserved():\n text = "لا يعطى ترامادول 0.5 mg"\n out, _ = app_core._auto_correct_ocr(text)\n assert out == text\n\ndef test_decimals_preserved():\n for text in ["0.5", "1.25", "0.75", "٠٫٥", "١٫٢٥"]:\n out, _ = app_core._auto_correct_ocr(text)\n assert out == text\n\ndef test_negations_preserved():\n for text in ["لا", "ليس", "لم", "لن", "غير", "بدون", "لا يوجد سكري"]:\n out, _ = app_core._auto_correct_ocr(text)\n assert out == text\n\ndef test_intended_ocr_correction_remains():\n out, _ = app_core._auto_correct_ocr("باراسيتبمول 500 mg")\n assert out == "باراسيتامول 500 mg"\n''', encoding='utf-8')
PY
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
# Static verification of the exact requested changes.
test -f deploy/docker-compose.full.yml
test -f docker-compose.prod.yml
! grep -Fq 'cp "${DEPLOY_DIR}/docker-compose.prod.yml" "${PROJECT_DIR}/docker-compose.prod.yml"' deploy/deploy.sh
grep -Fq 'docker-compose.full.yml' deploy/deploy.sh
grep -Fq 'Depends(get_current_user)' app/routers/admin.py
! grep -Fq '"ترامادول ": "ترامادول"' hf-space/app_core.py
! grep -Fq '"ديكلوفيناك ": "ديكلوفيناك"' hf-space/app_core.py
! grep -Fq '"اوميبرازول ": "اوميبرازول"' hf-space/app_core.py
git diff --check
git diff --stat
- name: Remove temporary runners and commit verified changes
shell: bash
run: |
set -euo pipefail
rm -f .github/workflows/_apply_security_fixes.yml .github/workflows/_apply_security_fixes_v2.yml
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add -A
git status --short
git diff --cached --check
git commit -m 'fix(security): harden medical OCR, admin auth, and deployment topology'
git push origin HEAD:${{ github.head_ref }}