Repository navigation
Security fixes: medical OCR separator, admin auth, deployment topology #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }} |