bayan-api / archive /phase_reports /audits /security-audit.md
youssefreda9's picture
Cleanup pass 2: Fix .gitignore corruption, archive docs/audit + phase10 helpers, fix PROJECT_DESCRIPTION.md
6e716e3
|
Raw
History Blame Contribute Delete
4.06 kB
# BAYAN — Security Audit Report
**Date:** 2026-06-18
---
## Attack Surface
| Component | Exposure | Risk Level |
|-----------|----------|------------|
| Flask API (7860) | Public via HF Spaces | Medium |
| Supabase PostgreSQL | Behind RLS + Auth | Low |
| Google OAuth | Delegated to Google | Low |
| Model Inference | CPU-bound, no GPU | Low |
| localStorage | Client-only | Low |
---
## Authentication & Authorization
### Supabase RLS (Row-Level Security)
| Test | Result | Evidence |
|------|--------|----------|
| Unauthenticated REST access | ✅ BLOCKED | HTTP 401 |
| Invalid JWT token | ✅ BLOCKED | HTTP 401 |
| Cross-user document access | ✅ BLOCKED | RLS `user_id = auth.uid()` |
| Guest session isolation | ✅ PASS | Guest users have unique UUIDs |
### Google OAuth
| Test | Result |
|------|--------|
| OAuth flow uses Supabase SDK | ✅ |
| Tokens stored in Supabase session | ✅ |
| No custom token handling | ✅ (delegated) |
---
## API Input Validation
| Test | Endpoint | Result | Details |
|------|----------|--------|---------|
| Empty text | All | ✅ Returns 200/400 gracefully | No crash |
| Missing `text` field | All | ✅ Returns 400 | `"Text is required"` |
| Very long text (10K+) | /api/analyze | ⚠️ Timeout | Pipeline processes all 3 models |
| Non-JSON payload | All | ✅ Returns 400 | `"Request must be JSON"` |
| SQL injection attempt | /api/analyze | ✅ SAFE | NLP model treats it as text |
| XSS `<script>` tag | /api/analyze | ✅ SAFE | Tags preserved as text (editor uses textContent) |
### Max Text Length Protection
```python
MAX_ANALYZE_LENGTH = 5000 # Frontend
MAX_TEXT_LENGTH = 5000 # Backend (app.py)
```
Both frontend and backend enforce a 5000-character limit.
---
## CORS Configuration
```python
CORS(app, resources={r"/api/*": {"origins": "*"}})
```
| Aspect | Status |
|--------|--------|
| API routes protected | ✅ `/api/*` only |
| Static files unaffected | ✅ |
| Wildcard origin | ⚠️ Allows any origin |
> [!NOTE]
> Wildcard CORS (`*`) is acceptable because the API is public and requires no authentication. All sensitive operations go through Supabase SDK directly (client → Supabase, not client → Flask → Supabase).
---
## Data Protection
| Data | Storage | Protection |
|------|---------|------------|
| User profile | Supabase `profiles` | RLS + JWT |
| Documents | Supabase `documents` | RLS + JWT |
| Summaries | Supabase `summaries` | RLS + JWT |
| Settings | Supabase `settings` | RLS + JWT |
| Editor drafts | localStorage | Client-only |
| Dismissed words | localStorage | Client-only |
---
## Secrets Management
| Secret | Location | Status |
|--------|----------|--------|
| `HF_API_TOKEN` | HF Spaces Secrets | ✅ |
| `SUPABASE_URL` | HF Spaces Secrets | ✅ |
| `SUPABASE_ANON_KEY` | HF Spaces Secrets | ✅ |
| Google OAuth Client ID | Frontend JS (public) | ✅ (public key by design) |
> [!IMPORTANT]
> No private keys are exposed in frontend code. The Supabase anon key is a public key designed for client-side use, protected by RLS policies.
---
## Vulnerability Assessment
| Category | Risk | Status |
|----------|------|--------|
| SQL Injection | None | Supabase ORM + parameterized |
| XSS | Low | Editor uses `textContent` not `innerHTML` for user text |
| CSRF | N/A | No session cookies (stateless API) |
| Path Traversal | None | No file uploads |
| DDoS | Medium | No rate limiting (relies on HF proxy) |
| Model Poisoning | None | Models are read-only |
---
## Recommendations
1. **Rate Limiting:** Add Flask-Limiter for `/api/analyze` (e.g., 10 req/min per IP)
2. **Input Size:** Current 5000-char limit is good; consider per-endpoint limits
3. **CORS:** Could restrict to specific domains when going to production
4. **Monitoring:** Add request logging with IP for abuse detection
---
## Security Score: 85/100 🟡
The system is secure for its current deployment model (public NLP API + Supabase-protected data). The main gap is lack of rate limiting, which is mitigated by HF Spaces' own proxy layer.