Spaces:
Runtime error
Runtime error
fix: secure sessions behind HTTPS proxies
Browse files- .env.example +1 -0
- README.md +1 -0
- data/benchmark_runs/latest.json +1 -1
- simpliscribe/config.py +5 -0
- simpliscribe/main.py +2 -2
.env.example
CHANGED
|
@@ -7,6 +7,7 @@ ADMIN_EMAIL=admin@localhost
|
|
| 7 |
ADMIN_PASSWORD=
|
| 8 |
RETENTION_DAYS=30
|
| 9 |
SESSION_MAX_AGE_SECONDS=28800
|
|
|
|
| 10 |
HUGGINGFACEHUB_API_TOKEN=
|
| 11 |
HF_CHAT_MODEL=Qwen/Qwen2.5-7B-Instruct
|
| 12 |
INFERENCE_PROVIDER=fallback
|
|
|
|
| 7 |
ADMIN_PASSWORD=
|
| 8 |
RETENTION_DAYS=30
|
| 9 |
SESSION_MAX_AGE_SECONDS=28800
|
| 10 |
+
SESSION_HTTPS_ONLY=false
|
| 11 |
HUGGINGFACEHUB_API_TOKEN=
|
| 12 |
HF_CHAT_MODEL=Qwen/Qwen2.5-7B-Instruct
|
| 13 |
INFERENCE_PROVIDER=fallback
|
README.md
CHANGED
|
@@ -194,6 +194,7 @@ ADMIN_EMAIL=reviewer@example.com
|
|
| 194 |
ADMIN_PASSWORD=<strong-secret-manager-value>
|
| 195 |
RETENTION_DAYS=30
|
| 196 |
SESSION_MAX_AGE_SECONDS=28800
|
|
|
|
| 197 |
```
|
| 198 |
|
| 199 |
Production behavior includes authenticated, owner-scoped analyses; signed HTTP-only session cookies; CSRF validation; explicit upload consent; automatic retention expiry; redacted audit events; protected history, details, reports, and review APIs; analysis rate/concurrency limits; CSP and HSTS headers; separate liveness/readiness endpoints; and a non-root container health check.
|
|
|
|
| 194 |
ADMIN_PASSWORD=<strong-secret-manager-value>
|
| 195 |
RETENTION_DAYS=30
|
| 196 |
SESSION_MAX_AGE_SECONDS=28800
|
| 197 |
+
SESSION_HTTPS_ONLY=true
|
| 198 |
```
|
| 199 |
|
| 200 |
Production behavior includes authenticated, owner-scoped analyses; signed HTTP-only session cookies; CSRF validation; explicit upload consent; automatic retention expiry; redacted audit events; protected history, details, reports, and review APIs; analysis rate/concurrency limits; CSP and HSTS headers; separate liveness/readiness endpoints; and a non-root container health check.
|
data/benchmark_runs/latest.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
| 6 |
"provenance": "Repository-authored synthetic OCR text for regression testing; not clinician-adjudicated.",
|
| 7 |
"created_at": "2026-07-11"
|
| 8 |
},
|
| 9 |
-
"generated_at": "2026-07-12T04:
|
| 10 |
"case_count": 8,
|
| 11 |
"success_count": 8,
|
| 12 |
"failure_count": 0,
|
|
|
|
| 6 |
"provenance": "Repository-authored synthetic OCR text for regression testing; not clinician-adjudicated.",
|
| 7 |
"created_at": "2026-07-11"
|
| 8 |
},
|
| 9 |
+
"generated_at": "2026-07-12T04:36:50.992895+00:00",
|
| 10 |
"case_count": 8,
|
| 11 |
"success_count": 8,
|
| 12 |
"failure_count": 0,
|
simpliscribe/config.py
CHANGED
|
@@ -24,6 +24,7 @@ class Settings:
|
|
| 24 |
auth_required: bool = os.environ.get("AUTH_REQUIRED", "").strip().lower() in {"1", "true", "yes", "on"}
|
| 25 |
retention_days: int = int(os.environ.get("RETENTION_DAYS", "30"))
|
| 26 |
session_max_age_seconds: int = int(os.environ.get("SESSION_MAX_AGE_SECONDS", "28800"))
|
|
|
|
| 27 |
india_medicine_dataset: Path = BASE_DIR / "A_Z_medicines_dataset_of_India.csv"
|
| 28 |
medicine_database_dataset: Path = BASE_DIR / "all_medicine databased.csv"
|
| 29 |
max_upload_mb: int = int(os.environ.get("MAX_UPLOAD_MB", "10"))
|
|
@@ -56,6 +57,10 @@ class Settings:
|
|
| 56 |
def authentication_enabled(self) -> bool:
|
| 57 |
return self.production or self.auth_required
|
| 58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
def validate_runtime(self) -> None:
|
| 60 |
errors: list[str] = []
|
| 61 |
if self.production:
|
|
|
|
| 24 |
auth_required: bool = os.environ.get("AUTH_REQUIRED", "").strip().lower() in {"1", "true", "yes", "on"}
|
| 25 |
retention_days: int = int(os.environ.get("RETENTION_DAYS", "30"))
|
| 26 |
session_max_age_seconds: int = int(os.environ.get("SESSION_MAX_AGE_SECONDS", "28800"))
|
| 27 |
+
session_https_only: bool = os.environ.get("SESSION_HTTPS_ONLY", "").strip().lower() in {"1", "true", "yes", "on"}
|
| 28 |
india_medicine_dataset: Path = BASE_DIR / "A_Z_medicines_dataset_of_India.csv"
|
| 29 |
medicine_database_dataset: Path = BASE_DIR / "all_medicine databased.csv"
|
| 30 |
max_upload_mb: int = int(os.environ.get("MAX_UPLOAD_MB", "10"))
|
|
|
|
| 57 |
def authentication_enabled(self) -> bool:
|
| 58 |
return self.production or self.auth_required
|
| 59 |
|
| 60 |
+
@property
|
| 61 |
+
def secure_transport(self) -> bool:
|
| 62 |
+
return self.production or self.session_https_only
|
| 63 |
+
|
| 64 |
def validate_runtime(self) -> None:
|
| 65 |
errors: list[str] = []
|
| 66 |
if self.production:
|
simpliscribe/main.py
CHANGED
|
@@ -40,7 +40,7 @@ async def protect_health_data_responses(request: Request, call_next):
|
|
| 40 |
response.headers.setdefault("X-Frame-Options", "DENY")
|
| 41 |
response.headers.setdefault("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
|
| 42 |
response.headers.setdefault("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' data: blob:; connect-src 'self'")
|
| 43 |
-
if settings.
|
| 44 |
response.headers.setdefault("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
| 45 |
return response
|
| 46 |
|
|
@@ -52,7 +52,7 @@ app.add_middleware(
|
|
| 52 |
secret_key=settings.session_secret,
|
| 53 |
max_age=settings.session_max_age_seconds,
|
| 54 |
same_site="lax",
|
| 55 |
-
https_only=settings.
|
| 56 |
)
|
| 57 |
|
| 58 |
|
|
|
|
| 40 |
response.headers.setdefault("X-Frame-Options", "DENY")
|
| 41 |
response.headers.setdefault("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
|
| 42 |
response.headers.setdefault("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' data: blob:; connect-src 'self'")
|
| 43 |
+
if settings.secure_transport:
|
| 44 |
response.headers.setdefault("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
| 45 |
return response
|
| 46 |
|
|
|
|
| 52 |
secret_key=settings.session_secret,
|
| 53 |
max_age=settings.session_max_age_seconds,
|
| 54 |
same_site="lax",
|
| 55 |
+
https_only=settings.secure_transport,
|
| 56 |
)
|
| 57 |
|
| 58 |
|