Skip to content

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

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

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

name: Apply security fixes v3 (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 and verify real fixes
shell: bash
run: |
set -euo pipefail
python - <<'PY'
from pathlib import Path
# Medical OCR: remove identity mappings with trailing separators.
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'Expected app_core pattern not found: {old!r}')
s = s.replace(old, new, 1)
p.write_text(s, encoding='utf-8')
# Admin system-status 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)
if 'current_user: User = Depends(get_current_user)' not in s:
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')
# Separate alternate full-stack compose; do not overwrite canonical root 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')
marker = 'DEPLOY_DIR="$(dirname "$0")"\n'
if 'COMPOSE_FILE="${DEPLOY_DIR}/docker-compose.full.yml"' not in s:
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')
# Real behavioral tests importing the production module.
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
# Verify intended modifications and absence of the dangerous patterns.
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 runner and commit only intended files
shell: bash
run: |
set -euo pipefail
rm -f .github/workflows/_apply_security_fixes_v3.yml
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
# Explicitly stage ONLY the intended changes. Do not use git add -A.
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 add -u -- deploy/docker-compose.prod.yml .github/workflows/_apply_security_fixes_v3.yml
git status --short
git diff --cached --check
git diff --cached --stat
git diff --cached --name-only
# Hard guard: only the five intended production/test paths may remain staged.
test "$(git diff --cached --name-only | wc -l)" -eq 5
git diff --cached --name-only | grep -Fx 'hf-space/app_core.py'
git diff --cached --name-only | grep -Fx 'app/routers/admin.py'
git diff --cached --name-only | grep -Fx 'deploy/deploy.sh'
git diff --cached --name-only | grep -Fx 'deploy/docker-compose.full.yml'
git diff --cached --name-only | grep -Fx 'tests/security/test_medical_behavior.py'
git commit -m 'fix(security): harden medical OCR, admin auth, and deployment topology'
git push origin HEAD:${{ github.head_ref }}