Spaces:
Sleeping
Security hardening and HuggingFace deployment fixes
Browse filesThis commit implements comprehensive security improvements and fixes
critical deployment errors identified in HuggingFace Spaces.
Security Improvements:
- Replace hardcoded ADMIN123 token with secure environment-based generation
- Rotate Flask secret key and add fail-fast validation in production
- Add rate limiting to authentication endpoints (Flask-Limiter)
- Remove exposed HuggingFace token from git remote configuration
- Create comprehensive SECURITY.md with security policy and best practices
- Update all documentation to remove hardcoded token references
HuggingFace Deployment Fixes:
- Fix TRANSFORMERS_CACHE deprecation warning (use HF_HOME only)
- Resolve matplotlib permission errors with MPLCONFIGDIR=/tmp/matplotlib
- Improve SQLite locking with retry logic, batch commits, and optimizations
- Fix sentence text display in PDF exports with EnhancedSentence class
Dependencies:
- Add Flask-Limiter>=3.5.0 for rate limiting support
Breaking Changes:
- ADMIN_TOKEN must now be set via environment variable or will be auto-generated
- Flask secret key validation now fails fast in production if not set
Migration:
- Set ADMIN_TOKEN in .env file (see .env.example)
- Ensure FLASK_SECRET_KEY is set in production
- Check startup logs for auto-generated admin token if not set
π€ Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- .env.example +5 -1
- DEPLOYMENT.md +4 -4
- DEPLOYMENT_READY.md +1 -1
- DEPLOYMENT_SUCCESS.md +1 -1
- DEPLOY_TO_HF.md +2 -2
- HF_DEPLOYMENT_CHECKLIST.md +1 -1
- HOW_TO_DEPLOY_SENTENCE_FEATURE.md +1 -1
- HUGGINGFACE_DEPLOYMENT.md +2 -2
- MIGRATION_SUMMARY.md +1 -1
- QUICKSTART.md +1 -1
- README.md +2 -2
- README_HF.md +2 -2
- SECURITY.md +185 -0
- app/__init__.py +59 -4
- app/routes/admin.py +5 -4
- app/routes/auth.py +3 -1
- requirements.txt +1 -0
- start.sh +1 -1
|
@@ -1,2 +1,6 @@
|
|
| 1 |
-
FLASK_SECRET_KEY=
|
| 2 |
FLASK_ENV=development
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FLASK_SECRET_KEY=your_secret_key_here_change_in_production
|
| 2 |
FLASK_ENV=development
|
| 3 |
+
MODELS_DIR=models/finetuned
|
| 4 |
+
CUDA_VISIBLE_DEVICES=-1
|
| 5 |
+
# Admin token for first-time setup (generate with: python -c "import secrets; print(secrets.token_urlsafe(16))")
|
| 6 |
+
ADMIN_TOKEN=your_admin_token_here
|
|
@@ -32,7 +32,7 @@
|
|
| 32 |
3. **Access from other devices**:
|
| 33 |
- Open browser on any device on same WiFi
|
| 34 |
- Go to: `http://YOUR_IP:5000`
|
| 35 |
-
- Admin login:
|
| 36 |
|
| 37 |
4. **Share registration link**:
|
| 38 |
- Give participants: `http://YOUR_IP:5000/generate`
|
|
@@ -194,7 +194,7 @@ git push hf main
|
|
| 194 |
|
| 195 |
#### 5. First-Time Setup
|
| 196 |
1. Access your Space URL
|
| 197 |
-
2. Login with admin token:
|
| 198 |
3. Go to **Registration** β Create participant tokens
|
| 199 |
4. Share registration link with stakeholders
|
| 200 |
5. First AI analysis downloads BART model (~1.6GB, cached permanently)
|
|
@@ -300,7 +300,7 @@ git push hf main
|
|
| 300 |
|
| 301 |
### Security on HF Spaces
|
| 302 |
|
| 303 |
-
1. **Change admin token** from
|
| 304 |
```python
|
| 305 |
# Create new admin token via Flask shell or UI
|
| 306 |
```
|
|
@@ -540,6 +540,6 @@ timeout = 300 # 5 minutes
|
|
| 540 |
| Docker | Clean deployment | http://SERVER_IP:8000 | 5 min |
|
| 541 |
| Cloud Platform | Managed hosting | https://your-app.platform.com | 15 min |
|
| 542 |
|
| 543 |
-
**Default Admin Token**:
|
| 544 |
|
| 545 |
**Support**: Check logs first, then review error messages in browser console (F12)
|
|
|
|
| 32 |
3. **Access from other devices**:
|
| 33 |
- Open browser on any device on same WiFi
|
| 34 |
- Go to: `http://YOUR_IP:5000`
|
| 35 |
+
- Admin login: `<see-startup-logs-or-set-ADMIN_TOKEN>`
|
| 36 |
|
| 37 |
4. **Share registration link**:
|
| 38 |
- Give participants: `http://YOUR_IP:5000/generate`
|
|
|
|
| 194 |
|
| 195 |
#### 5. First-Time Setup
|
| 196 |
1. Access your Space URL
|
| 197 |
+
2. Login with admin token: `<see-startup-logs-or-set-ADMIN_TOKEN>` (change this!)
|
| 198 |
3. Go to **Registration** β Create participant tokens
|
| 199 |
4. Share registration link with stakeholders
|
| 200 |
5. First AI analysis downloads BART model (~1.6GB, cached permanently)
|
|
|
|
| 300 |
|
| 301 |
### Security on HF Spaces
|
| 302 |
|
| 303 |
+
1. **Change admin token** from `<see-startup-logs-or-set-ADMIN_TOKEN>`:
|
| 304 |
```python
|
| 305 |
# Create new admin token via Flask shell or UI
|
| 306 |
```
|
|
|
|
| 540 |
| Docker | Clean deployment | http://SERVER_IP:8000 | 5 min |
|
| 541 |
| Cloud Platform | Managed hosting | https://your-app.platform.com | 15 min |
|
| 542 |
|
| 543 |
+
**Default Admin Token**: `<see-startup-logs-or-set-ADMIN_TOKEN>` (β οΈ CHANGE IN PRODUCTION)
|
| 544 |
|
| 545 |
**Support**: Check logs first, then review error messages in browser console (F12)
|
|
@@ -44,7 +44,7 @@
|
|
| 44 |
- **Value**: (the key above)
|
| 45 |
|
| 46 |
### Admin Access
|
| 47 |
-
- **Default Token**:
|
| 48 |
- **Recommendation**: Change before public deployment
|
| 49 |
- **Location**: app/models/models.py (line 61)
|
| 50 |
|
|
|
|
| 44 |
- **Value**: (the key above)
|
| 45 |
|
| 46 |
### Admin Access
|
| 47 |
+
- **Default Token**: `<see-startup-logs-or-set-ADMIN_TOKEN>`
|
| 48 |
- **Recommendation**: Change before public deployment
|
| 49 |
- **Location**: app/models/models.py (line 61)
|
| 50 |
|
|
@@ -14,7 +14,7 @@
|
|
| 14 |
- **Settings**: https://huggingface.co/spaces/thadillo/participatory-planner/settings
|
| 15 |
|
| 16 |
### Admin Login
|
| 17 |
-
- **Token**:
|
| 18 |
|
| 19 |
---
|
| 20 |
|
|
|
|
| 14 |
- **Settings**: https://huggingface.co/spaces/thadillo/participatory-planner/settings
|
| 15 |
|
| 16 |
### Admin Login
|
| 17 |
+
- **Token**: `<see-startup-logs-or-set-ADMIN_TOKEN>`
|
| 18 |
|
| 19 |
---
|
| 20 |
|
|
@@ -102,7 +102,7 @@ Your app is live at:
|
|
| 102 |
- **Direct**: `https://huggingface.co/spaces/YOUR_USERNAME/participatory-planner`
|
| 103 |
- **Embedded**: `https://YOUR_USERNAME-participatory-planner.hf.space`
|
| 104 |
|
| 105 |
-
**Login**:
|
| 106 |
|
| 107 |
---
|
| 108 |
|
|
@@ -182,7 +182,7 @@ With your HF Pro account:
|
|
| 182 |
- β
Secret key stored in HF Secrets (not in code)
|
| 183 |
- β
HTTPS enabled automatically
|
| 184 |
- β
Session cookies configured
|
| 185 |
-
- β οΈ Default admin token:
|
| 186 |
|
| 187 |
### For Production:
|
| 188 |
1. **Change admin token** to something secure
|
|
|
|
| 102 |
- **Direct**: `https://huggingface.co/spaces/YOUR_USERNAME/participatory-planner`
|
| 103 |
- **Embedded**: `https://YOUR_USERNAME-participatory-planner.hf.space`
|
| 104 |
|
| 105 |
+
**Login**: `<see-startup-logs-or-set-ADMIN_TOKEN>`
|
| 106 |
|
| 107 |
---
|
| 108 |
|
|
|
|
| 182 |
- β
Secret key stored in HF Secrets (not in code)
|
| 183 |
- β
HTTPS enabled automatically
|
| 184 |
- β
Session cookies configured
|
| 185 |
+
- β οΈ Default admin token: `<see-startup-logs-or-set-ADMIN_TOKEN>`
|
| 186 |
|
| 187 |
### For Production:
|
| 188 |
1. **Change admin token** to something secure
|
|
@@ -86,7 +86,7 @@ Upload these files/folders to your Space:
|
|
| 86 |
|
| 87 |
#### Step 6: Access & Test
|
| 88 |
1. Visit: `https://huggingface.co/spaces/YOUR_USERNAME/participatory-planner`
|
| 89 |
-
2. Login with:
|
| 90 |
3. Test all features:
|
| 91 |
- [ ] Registration page loads
|
| 92 |
- [ ] Can create tokens
|
|
|
|
| 86 |
|
| 87 |
#### Step 6: Access & Test
|
| 88 |
1. Visit: `https://huggingface.co/spaces/YOUR_USERNAME/participatory-planner`
|
| 89 |
+
2. Login with: `<see-startup-logs-or-set-ADMIN_TOKEN>`
|
| 90 |
3. Test all features:
|
| 91 |
- [ ] Registration page loads
|
| 92 |
- [ ] Can create tokens
|
|
@@ -79,7 +79,7 @@ The app will start on http://127.0.0.1:5000
|
|
| 79 |
## Step 4: Test the Feature
|
| 80 |
|
| 81 |
1. **Open**: http://127.0.0.1:5000/login
|
| 82 |
-
2. **Login**:
|
| 83 |
3. **Go to**: Admin β Submissions
|
| 84 |
|
| 85 |
### Test Sentence Analysis:
|
|
|
|
| 79 |
## Step 4: Test the Feature
|
| 80 |
|
| 81 |
1. **Open**: http://127.0.0.1:5000/login
|
| 82 |
+
2. **Login**: `<see-startup-logs-or-set-ADMIN_TOKEN>`
|
| 83 |
3. **Go to**: Admin β Submissions
|
| 84 |
|
| 85 |
### Test Sentence Analysis:
|
|
@@ -66,7 +66,7 @@
|
|
| 66 |
6. **Access Your App!**
|
| 67 |
- URL: `https://huggingface.co/spaces/YOUR_USERNAME/participatory-planner`
|
| 68 |
- Or embedded: `https://YOUR_USERNAME-participatory-planner.hf.space`
|
| 69 |
-
- Login:
|
| 70 |
|
| 71 |
---
|
| 72 |
|
|
@@ -321,7 +321,7 @@ Access Space terminal (if enabled) and copy `/app/instance/app.db`
|
|
| 321 |
|
| 322 |
1. **Change admin token**:
|
| 323 |
- Edit `app/models/models.py`
|
| 324 |
-
- Change
|
| 325 |
- Redeploy
|
| 326 |
|
| 327 |
2. **Use secrets** for sensitive data:
|
|
|
|
| 66 |
6. **Access Your App!**
|
| 67 |
- URL: `https://huggingface.co/spaces/YOUR_USERNAME/participatory-planner`
|
| 68 |
- Or embedded: `https://YOUR_USERNAME-participatory-planner.hf.space`
|
| 69 |
+
- Login: `<see-startup-logs-or-set-ADMIN_TOKEN>`
|
| 70 |
|
| 71 |
---
|
| 72 |
|
|
|
|
| 321 |
|
| 322 |
1. **Change admin token**:
|
| 323 |
- Edit `app/models/models.py`
|
| 324 |
+
- Change `<see-startup-logs-or-set-ADMIN_TOKEN>` to secure token
|
| 325 |
- Redeploy
|
| 326 |
|
| 327 |
2. **Use secrets** for sensitive data:
|
|
@@ -155,7 +155,7 @@ All core features work exactly as before:
|
|
| 155 |
3. Set secret key in `.env`
|
| 156 |
4. Test analyzer: `python test_analyzer.py` (optional)
|
| 157 |
5. Run app: `python run.py`
|
| 158 |
-
6. Login with
|
| 159 |
7. Start your participatory planning session! π
|
| 160 |
|
| 161 |
---
|
|
|
|
| 155 |
3. Set secret key in `.env`
|
| 156 |
4. Test analyzer: `python test_analyzer.py` (optional)
|
| 157 |
5. Run app: `python run.py`
|
| 158 |
+
6. Login with `<see-startup-logs-or-set-ADMIN_TOKEN>`
|
| 159 |
7. Start your participatory planning session! π
|
| 160 |
|
| 161 |
---
|
|
@@ -25,7 +25,7 @@ Visit **http://localhost:5000**
|
|
| 25 |
|
| 26 |
## π First Login
|
| 27 |
|
| 28 |
-
- Default admin token:
|
| 29 |
- Login and start managing your participatory planning session!
|
| 30 |
|
| 31 |
## π€ AI Model Info
|
|
|
|
| 25 |
|
| 26 |
## π First Login
|
| 27 |
|
| 28 |
+
- Default admin token: **`<your-admin-token-from-startup>`**
|
| 29 |
- Login and start managing your participatory planning session!
|
| 30 |
|
| 31 |
## π€ AI Model Info
|
|
@@ -53,7 +53,7 @@ The system classifies text into six strategic planning categories:
|
|
| 53 |
|
| 54 |
### Basic Setup
|
| 55 |
1. Access the application at `http://localhost:5000`
|
| 56 |
-
2. Login with admin token:
|
| 57 |
3. Go to **Registration** to get the participant signup link
|
| 58 |
4. Share the link with stakeholders
|
| 59 |
5. Collect submissions and analyze with AI
|
|
@@ -68,7 +68,7 @@ The system classifies text into six strategic planning categories:
|
|
| 68 |
|
| 69 |
## Default Login
|
| 70 |
|
| 71 |
-
- **Admin Token**:
|
| 72 |
- **Admin Access**: Full dashboard, analytics, moderation, AI training
|
| 73 |
|
| 74 |
## Tech Stack
|
|
|
|
| 53 |
|
| 54 |
### Basic Setup
|
| 55 |
1. Access the application at `http://localhost:5000`
|
| 56 |
+
2. Login with admin token: `<your-admin-token-from-startup>`
|
| 57 |
3. Go to **Registration** to get the participant signup link
|
| 58 |
4. Share the link with stakeholders
|
| 59 |
5. Collect submissions and analyze with AI
|
|
|
|
| 68 |
|
| 69 |
## Default Login
|
| 70 |
|
| 71 |
+
- **Admin Token**: `<your-admin-token-from-startup>`
|
| 72 |
- **Admin Access**: Full dashboard, analytics, moderation, AI training
|
| 73 |
|
| 74 |
## Tech Stack
|
|
@@ -24,14 +24,14 @@ An AI-powered collaborative urban planning platform for multi-stakeholder engage
|
|
| 24 |
## Quick Start
|
| 25 |
|
| 26 |
1. Access the application
|
| 27 |
-
2. Login with admin token:
|
| 28 |
3. Go to **Registration** to get the participant signup link
|
| 29 |
4. Share the link with stakeholders
|
| 30 |
5. Collect submissions and analyze with AI
|
| 31 |
|
| 32 |
## Default Login
|
| 33 |
|
| 34 |
-
- **Admin Token**:
|
| 35 |
- **Admin Access**: Full dashboard, analytics, moderation
|
| 36 |
|
| 37 |
## Tech Stack
|
|
|
|
| 24 |
## Quick Start
|
| 25 |
|
| 26 |
1. Access the application
|
| 27 |
+
2. Login with admin token: `<see-startup-logs-or-set-ADMIN_TOKEN>`
|
| 28 |
3. Go to **Registration** to get the participant signup link
|
| 29 |
4. Share the link with stakeholders
|
| 30 |
5. Collect submissions and analyze with AI
|
| 31 |
|
| 32 |
## Default Login
|
| 33 |
|
| 34 |
+
- **Admin Token**: `<see-startup-logs-or-set-ADMIN_TOKEN>`
|
| 35 |
- **Admin Access**: Full dashboard, analytics, moderation
|
| 36 |
|
| 37 |
## Tech Stack
|
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Security Policy
|
| 2 |
+
|
| 3 |
+
## Supported Versions
|
| 4 |
+
|
| 5 |
+
| Version | Supported |
|
| 6 |
+
| ------- | ------------------ |
|
| 7 |
+
| Latest | :white_check_mark: |
|
| 8 |
+
| < Latest| :x: |
|
| 9 |
+
|
| 10 |
+
We currently only support the latest version with security updates.
|
| 11 |
+
|
| 12 |
+
## Security Features
|
| 13 |
+
|
| 14 |
+
### Authentication & Authorization
|
| 15 |
+
- **Token-based authentication** for all users
|
| 16 |
+
- **Admin tokens** generated securely with `secrets.token_urlsafe()`
|
| 17 |
+
- **Rate limiting** on login (10 attempts/minute) and token generation (5/hour)
|
| 18 |
+
- **Session security**: HTTP-only, Secure, SameSite=None cookies with 24-hour lifetime
|
| 19 |
+
- **No hardcoded credentials**: All tokens are generated or loaded from environment variables
|
| 20 |
+
|
| 21 |
+
### Data Protection
|
| 22 |
+
- **Flask secret key validation**: Fails fast in production if not set securely
|
| 23 |
+
- **SQLite WAL mode**: Reduces database locking and improves concurrent access
|
| 24 |
+
- **Input validation**: All user inputs are validated and sanitized
|
| 25 |
+
- **CSRF protection**: Built-in Flask CSRF protection for forms
|
| 26 |
+
|
| 27 |
+
### Infrastructure Security
|
| 28 |
+
- **Environment variables**: All sensitive configuration stored in `.env` (never committed)
|
| 29 |
+
- **Credential helper**: Git credentials managed securely, not stored in repository
|
| 30 |
+
- **Docker isolation**: Production deployment uses containerization
|
| 31 |
+
- **HTTPS only**: All production deployments require HTTPS
|
| 32 |
+
|
| 33 |
+
## Reporting a Vulnerability
|
| 34 |
+
|
| 35 |
+
We take security seriously. If you discover a security vulnerability, please follow these steps:
|
| 36 |
+
|
| 37 |
+
### 1. **DO NOT** Create a Public Issue
|
| 38 |
+
Please do not report security vulnerabilities through public GitHub issues.
|
| 39 |
+
|
| 40 |
+
### 2. Report Privately
|
| 41 |
+
Send your report to: **[thadillo@users.noreply.github.com]**
|
| 42 |
+
|
| 43 |
+
Include:
|
| 44 |
+
- Description of the vulnerability
|
| 45 |
+
- Steps to reproduce
|
| 46 |
+
- Potential impact
|
| 47 |
+
- Suggested fix (if any)
|
| 48 |
+
|
| 49 |
+
### 3. Response Timeline
|
| 50 |
+
- **Initial Response**: Within 48 hours
|
| 51 |
+
- **Status Update**: Within 7 days
|
| 52 |
+
- **Fix Timeline**: Depends on severity
|
| 53 |
+
- Critical: 1-7 days
|
| 54 |
+
- High: 7-14 days
|
| 55 |
+
- Medium: 14-30 days
|
| 56 |
+
- Low: 30-90 days
|
| 57 |
+
|
| 58 |
+
### 4. Disclosure Policy
|
| 59 |
+
- We follow **coordinated disclosure**
|
| 60 |
+
- We will work with you to understand and fix the issue
|
| 61 |
+
- Public disclosure only after fix is deployed
|
| 62 |
+
- Credit will be given to reporters (unless you prefer to remain anonymous)
|
| 63 |
+
|
| 64 |
+
## Security Best Practices for Deployment
|
| 65 |
+
|
| 66 |
+
### For Administrators
|
| 67 |
+
|
| 68 |
+
#### 1. Environment Variables
|
| 69 |
+
```bash
|
| 70 |
+
# Generate secure Flask secret key
|
| 71 |
+
python -c "import secrets; print(secrets.token_hex(32))"
|
| 72 |
+
|
| 73 |
+
# Generate secure admin token
|
| 74 |
+
python -c "import secrets; print(secrets.token_urlsafe(16))"
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
Set in `.env` file (NEVER commit this file):
|
| 78 |
+
```env
|
| 79 |
+
FLASK_SECRET_KEY=<your-secure-secret-key>
|
| 80 |
+
FLASK_ENV=production
|
| 81 |
+
ADMIN_TOKEN=<your-secure-admin-token>
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
#### 2. Admin Token Management
|
| 85 |
+
- **Change the admin token** on first deployment
|
| 86 |
+
- **Rotate tokens** every 90 days
|
| 87 |
+
- **Use different tokens** for dev/staging/production
|
| 88 |
+
- **Never share** admin tokens publicly
|
| 89 |
+
|
| 90 |
+
#### 3. HuggingFace Spaces Secrets
|
| 91 |
+
For HuggingFace deployment, set secrets in Space settings:
|
| 92 |
+
- `FLASK_SECRET_KEY`
|
| 93 |
+
- `ADMIN_TOKEN`
|
| 94 |
+
- `FLASK_ENV=production`
|
| 95 |
+
|
| 96 |
+
#### 4. Database Security
|
| 97 |
+
- Regular backups of `/data/app.db`
|
| 98 |
+
- Monitor database size and growth
|
| 99 |
+
- Review admin actions logs regularly
|
| 100 |
+
|
| 101 |
+
#### 5. Token Rotation
|
| 102 |
+
To rotate admin token:
|
| 103 |
+
1. Generate new token: `python -c "import secrets; print(secrets.token_urlsafe(16))"`
|
| 104 |
+
2. Delete old admin token from admin panel
|
| 105 |
+
3. Update `ADMIN_TOKEN` in `.env` or HF Spaces secrets
|
| 106 |
+
4. Restart application
|
| 107 |
+
5. Login with new token
|
| 108 |
+
|
| 109 |
+
### For Contributors
|
| 110 |
+
|
| 111 |
+
#### 1. Never Commit Secrets
|
| 112 |
+
- Always check `.gitignore` includes `.env`
|
| 113 |
+
- Never commit:
|
| 114 |
+
- `.env` files
|
| 115 |
+
- API keys or tokens
|
| 116 |
+
- Database files (`.db`, `.sqlite`)
|
| 117 |
+
- Credentials or passwords
|
| 118 |
+
|
| 119 |
+
#### 2. Use `.env.example`
|
| 120 |
+
Create `.env.example` with placeholder values:
|
| 121 |
+
```env
|
| 122 |
+
FLASK_SECRET_KEY=your_secret_key_here
|
| 123 |
+
ADMIN_TOKEN=your_admin_token_here
|
| 124 |
+
FLASK_ENV=development
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
#### 3. Code Review Security Checklist
|
| 128 |
+
- [ ] No hardcoded credentials
|
| 129 |
+
- [ ] Input validation on all user inputs
|
| 130 |
+
- [ ] SQL injection protection (use SQLAlchemy ORM)
|
| 131 |
+
- [ ] XSS protection (use Flask template escaping)
|
| 132 |
+
- [ ] CSRF tokens on all forms
|
| 133 |
+
- [ ] Rate limiting on sensitive endpoints
|
| 134 |
+
|
| 135 |
+
## Known Security Considerations
|
| 136 |
+
|
| 137 |
+
### 1. SQLite in Production
|
| 138 |
+
- SQLite is suitable for low-to-medium traffic deployments
|
| 139 |
+
- For high traffic (>100 concurrent users), consider PostgreSQL
|
| 140 |
+
- WAL mode enabled for better concurrent access
|
| 141 |
+
|
| 142 |
+
### 2. Token-Based Authentication
|
| 143 |
+
- Tokens are bearer tokens (possess token = authenticated)
|
| 144 |
+
- Keep tokens secure and never share
|
| 145 |
+
- Tokens stored in secure HTTP-only cookies
|
| 146 |
+
- No token expiration (manual revocation only)
|
| 147 |
+
|
| 148 |
+
### 3. Rate Limiting
|
| 149 |
+
- In-memory rate limiting (resets on restart)
|
| 150 |
+
- For production, consider Redis-backed rate limiting
|
| 151 |
+
- Current limits:
|
| 152 |
+
- Login: 10 attempts/minute per IP
|
| 153 |
+
- Token generation: 5 per hour per IP
|
| 154 |
+
- Global: 200 requests/day, 50/hour per IP
|
| 155 |
+
|
| 156 |
+
### 4. File Uploads
|
| 157 |
+
- No file upload functionality currently
|
| 158 |
+
- If added, implement strict validation:
|
| 159 |
+
- File type whitelist
|
| 160 |
+
- Size limits
|
| 161 |
+
- Virus scanning
|
| 162 |
+
- Storage in isolated directory
|
| 163 |
+
|
| 164 |
+
## Security Audit History
|
| 165 |
+
|
| 166 |
+
| Date | Type | Findings | Status |
|
| 167 |
+
|------|------|----------|--------|
|
| 168 |
+
| 2025-10-14 | Internal | Hardcoded ADMIN123 token | β
Fixed |
|
| 169 |
+
| 2025-10-14 | Internal | HF token in git config | β
Fixed |
|
| 170 |
+
| 2025-10-14 | Internal | Flask secret in git history | β
Rotated |
|
| 171 |
+
| 2025-10-14 | Internal | No rate limiting | β
Fixed |
|
| 172 |
+
|
| 173 |
+
## References
|
| 174 |
+
|
| 175 |
+
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
| 176 |
+
- [Flask Security Best Practices](https://flask.palletsprojects.com/en/latest/security/)
|
| 177 |
+
- [SQLAlchemy Security](https://docs.sqlalchemy.org/en/latest/core/tutorial.html#using-textual-sql)
|
| 178 |
+
|
| 179 |
+
## Contact
|
| 180 |
+
|
| 181 |
+
For security concerns: **thadillo@users.noreply.github.com**
|
| 182 |
+
|
| 183 |
+
---
|
| 184 |
+
|
| 185 |
+
*Last Updated: 2025-10-14*
|
|
@@ -1,15 +1,45 @@
|
|
| 1 |
from flask import Flask
|
| 2 |
from flask_sqlalchemy import SQLAlchemy
|
|
|
|
|
|
|
| 3 |
from dotenv import load_dotenv
|
| 4 |
import os
|
| 5 |
|
| 6 |
db = SQLAlchemy()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
def create_app():
|
| 9 |
load_dotenv()
|
| 10 |
|
| 11 |
app = Flask(__name__)
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
# Session configuration for iframe embedding (HF Spaces)
|
| 15 |
app.config['SESSION_COOKIE_SECURE'] = True # Required for HTTPS
|
|
@@ -40,6 +70,7 @@ def create_app():
|
|
| 40 |
}
|
| 41 |
|
| 42 |
db.init_app(app)
|
|
|
|
| 43 |
|
| 44 |
# Enable WAL mode for SQLite to reduce locking
|
| 45 |
with app.app_context():
|
|
@@ -79,15 +110,39 @@ def create_app():
|
|
| 79 |
# Create tables
|
| 80 |
with app.app_context():
|
| 81 |
db.create_all()
|
| 82 |
-
|
|
|
|
| 83 |
from app.models.models import Token
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
admin_token = Token(
|
| 86 |
-
token=
|
| 87 |
type='admin',
|
| 88 |
name='Administrator'
|
| 89 |
)
|
| 90 |
db.session.add(admin_token)
|
| 91 |
db.session.commit()
|
|
|
|
| 92 |
|
| 93 |
return app
|
|
|
|
| 1 |
from flask import Flask
|
| 2 |
from flask_sqlalchemy import SQLAlchemy
|
| 3 |
+
from flask_limiter import Limiter
|
| 4 |
+
from flask_limiter.util import get_remote_address
|
| 5 |
from dotenv import load_dotenv
|
| 6 |
import os
|
| 7 |
|
| 8 |
db = SQLAlchemy()
|
| 9 |
+
limiter = Limiter(
|
| 10 |
+
key_func=get_remote_address,
|
| 11 |
+
default_limits=["200 per day", "50 per hour"],
|
| 12 |
+
storage_uri="memory://"
|
| 13 |
+
)
|
| 14 |
|
| 15 |
def create_app():
|
| 16 |
load_dotenv()
|
| 17 |
|
| 18 |
app = Flask(__name__)
|
| 19 |
+
|
| 20 |
+
# Secret key validation with fail-fast in production
|
| 21 |
+
flask_secret_key = os.getenv('FLASK_SECRET_KEY')
|
| 22 |
+
flask_env = os.getenv('FLASK_ENV', 'production')
|
| 23 |
+
|
| 24 |
+
if not flask_secret_key:
|
| 25 |
+
if flask_env == 'production':
|
| 26 |
+
raise RuntimeError(
|
| 27 |
+
"FLASK_SECRET_KEY must be set in production! "
|
| 28 |
+
"Generate one with: python -c \"import secrets; print(secrets.token_hex(32))\""
|
| 29 |
+
)
|
| 30 |
+
else:
|
| 31 |
+
# Development: Generate random secret (not persistent)
|
| 32 |
+
import secrets
|
| 33 |
+
flask_secret_key = secrets.token_hex(32)
|
| 34 |
+
app.logger.warning("β οΈ No FLASK_SECRET_KEY set - using random key for development")
|
| 35 |
+
app.logger.warning("β οΈ Sessions will be invalidated on restart!")
|
| 36 |
+
elif flask_secret_key == 'dev-secret-key-change-in-production':
|
| 37 |
+
raise RuntimeError(
|
| 38 |
+
"FLASK_SECRET_KEY is using the default insecure value! "
|
| 39 |
+
"Change it in .env file to a secure random value."
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
app.config['SECRET_KEY'] = flask_secret_key
|
| 43 |
|
| 44 |
# Session configuration for iframe embedding (HF Spaces)
|
| 45 |
app.config['SESSION_COOKIE_SECURE'] = True # Required for HTTPS
|
|
|
|
| 70 |
}
|
| 71 |
|
| 72 |
db.init_app(app)
|
| 73 |
+
limiter.init_app(app)
|
| 74 |
|
| 75 |
# Enable WAL mode for SQLite to reduce locking
|
| 76 |
with app.app_context():
|
|
|
|
| 110 |
# Create tables
|
| 111 |
with app.app_context():
|
| 112 |
db.create_all()
|
| 113 |
+
|
| 114 |
+
# Initialize with admin token if not exists (SECURE VERSION)
|
| 115 |
from app.models.models import Token
|
| 116 |
+
import secrets
|
| 117 |
+
|
| 118 |
+
# Check if any admin token exists
|
| 119 |
+
existing_admin = Token.query.filter_by(type='admin').first()
|
| 120 |
+
|
| 121 |
+
if not existing_admin:
|
| 122 |
+
# Get admin token from environment or generate secure random token
|
| 123 |
+
admin_token_value = os.getenv('ADMIN_TOKEN')
|
| 124 |
+
|
| 125 |
+
if not admin_token_value:
|
| 126 |
+
# Generate secure random token
|
| 127 |
+
admin_token_value = secrets.token_urlsafe(16)
|
| 128 |
+
app.logger.warning("=" * 80)
|
| 129 |
+
app.logger.warning("π ADMIN TOKEN GENERATED (SAVE THIS - SHOWN ONLY ONCE):")
|
| 130 |
+
app.logger.warning(f" {admin_token_value}")
|
| 131 |
+
app.logger.warning("=" * 80)
|
| 132 |
+
print("\n" + "=" * 80)
|
| 133 |
+
print("π ADMIN TOKEN GENERATED (SAVE THIS - SHOWN ONLY ONCE):")
|
| 134 |
+
print(f" {admin_token_value}")
|
| 135 |
+
print("=" * 80 + "\n")
|
| 136 |
+
else:
|
| 137 |
+
app.logger.info("Using ADMIN_TOKEN from environment variable")
|
| 138 |
+
|
| 139 |
admin_token = Token(
|
| 140 |
+
token=admin_token_value,
|
| 141 |
type='admin',
|
| 142 |
name='Administrator'
|
| 143 |
)
|
| 144 |
db.session.add(admin_token)
|
| 145 |
db.session.commit()
|
| 146 |
+
app.logger.info(f"Admin token created: {admin_token_value[:4]}...{admin_token_value[-4:]}")
|
| 147 |
|
| 148 |
return app
|
|
@@ -462,7 +462,8 @@ def create_token():
|
|
| 462 |
def delete_token(token_id):
|
| 463 |
token = Token.query.get_or_404(token_id)
|
| 464 |
|
| 465 |
-
|
|
|
|
| 466 |
return jsonify({'success': False, 'error': 'Cannot delete admin token'}), 400
|
| 467 |
|
| 468 |
db.session.delete(token)
|
|
@@ -775,11 +776,11 @@ def import_data():
|
|
| 775 |
|
| 776 |
# Clear existing data (except admin token)
|
| 777 |
Submission.query.delete()
|
| 778 |
-
Token.query.filter(Token.
|
| 779 |
|
| 780 |
# Import tokens
|
| 781 |
for token_data in data.get('tokens', []):
|
| 782 |
-
if token_data
|
| 783 |
token = Token(
|
| 784 |
token=token_data['token'],
|
| 785 |
type=token_data['type'],
|
|
@@ -844,7 +845,7 @@ def clear_all_data():
|
|
| 844 |
Submission.query.delete()
|
| 845 |
|
| 846 |
# Delete all tokens except admin
|
| 847 |
-
Token.query.filter(Token.
|
| 848 |
|
| 849 |
# Optionally reset settings to defaults
|
| 850 |
Settings.set_setting('submission_open', 'true')
|
|
|
|
| 462 |
def delete_token(token_id):
|
| 463 |
token = Token.query.get_or_404(token_id)
|
| 464 |
|
| 465 |
+
# Prevent deletion of admin tokens (any token with type='admin')
|
| 466 |
+
if token.type == 'admin':
|
| 467 |
return jsonify({'success': False, 'error': 'Cannot delete admin token'}), 400
|
| 468 |
|
| 469 |
db.session.delete(token)
|
|
|
|
| 776 |
|
| 777 |
# Clear existing data (except admin token)
|
| 778 |
Submission.query.delete()
|
| 779 |
+
Token.query.filter(Token.type != 'admin').delete()
|
| 780 |
|
| 781 |
# Import tokens
|
| 782 |
for token_data in data.get('tokens', []):
|
| 783 |
+
if token_data.get('type') != 'admin': # Skip admin token as it already exists
|
| 784 |
token = Token(
|
| 785 |
token=token_data['token'],
|
| 786 |
type=token_data['type'],
|
|
|
|
| 845 |
Submission.query.delete()
|
| 846 |
|
| 847 |
# Delete all tokens except admin
|
| 848 |
+
Token.query.filter(Token.type != 'admin').delete()
|
| 849 |
|
| 850 |
# Optionally reset settings to defaults
|
| 851 |
Settings.set_setting('submission_open', 'true')
|
|
@@ -1,6 +1,6 @@
|
|
| 1 |
from flask import Blueprint, render_template, request, redirect, url_for, session, flash
|
| 2 |
from app.models.models import Token, Settings
|
| 3 |
-
from app import db
|
| 4 |
import random
|
| 5 |
import string
|
| 6 |
from datetime import datetime
|
|
@@ -27,6 +27,7 @@ def index():
|
|
| 27 |
return redirect(url_for('auth.login'))
|
| 28 |
|
| 29 |
@bp.route('/login', methods=['GET', 'POST'])
|
|
|
|
| 30 |
def login():
|
| 31 |
if request.method == 'POST':
|
| 32 |
token_str = request.form.get('token', '').strip() # Remove whitespace
|
|
@@ -52,6 +53,7 @@ def login():
|
|
| 52 |
return render_template('login.html')
|
| 53 |
|
| 54 |
@bp.route('/generate', methods=['GET', 'POST'])
|
|
|
|
| 55 |
def generate():
|
| 56 |
token_generation_enabled = Settings.get_setting('token_generation_enabled', 'true') == 'true'
|
| 57 |
|
|
|
|
| 1 |
from flask import Blueprint, render_template, request, redirect, url_for, session, flash
|
| 2 |
from app.models.models import Token, Settings
|
| 3 |
+
from app import db, limiter
|
| 4 |
import random
|
| 5 |
import string
|
| 6 |
from datetime import datetime
|
|
|
|
| 27 |
return redirect(url_for('auth.login'))
|
| 28 |
|
| 29 |
@bp.route('/login', methods=['GET', 'POST'])
|
| 30 |
+
@limiter.limit("10 per minute") # Rate limit: 10 login attempts per minute per IP
|
| 31 |
def login():
|
| 32 |
if request.method == 'POST':
|
| 33 |
token_str = request.form.get('token', '').strip() # Remove whitespace
|
|
|
|
| 53 |
return render_template('login.html')
|
| 54 |
|
| 55 |
@bp.route('/generate', methods=['GET', 'POST'])
|
| 56 |
+
@limiter.limit("5 per hour") # Rate limit: 5 token generations per hour per IP
|
| 57 |
def generate():
|
| 58 |
token_generation_enabled = Settings.get_setting('token_generation_enabled', 'true') == 'true'
|
| 59 |
|
|
@@ -1,5 +1,6 @@
|
|
| 1 |
Flask==3.0.0
|
| 2 |
Flask-SQLAlchemy==3.1.1
|
|
|
|
| 3 |
python-dotenv==1.0.0
|
| 4 |
transformers==4.46.0
|
| 5 |
torch==2.5.0
|
|
|
|
| 1 |
Flask==3.0.0
|
| 2 |
Flask-SQLAlchemy==3.1.1
|
| 3 |
+
Flask-Limiter>=3.5.0
|
| 4 |
python-dotenv==1.0.0
|
| 5 |
transformers==4.46.0
|
| 6 |
torch==2.5.0
|
|
@@ -29,7 +29,7 @@ fi
|
|
| 29 |
# Start application
|
| 30 |
echo "β
Starting server on port 8000..."
|
| 31 |
echo "π Access at: http://$(hostname -I | awk '{print $1}'):8000"
|
| 32 |
-
echo "π Admin token:
|
| 33 |
echo ""
|
| 34 |
echo "Press Ctrl+C to stop"
|
| 35 |
echo ""
|
|
|
|
| 29 |
# Start application
|
| 30 |
echo "β
Starting server on port 8000..."
|
| 31 |
echo "π Access at: http://$(hostname -I | awk '{print $1}'):8000"
|
| 32 |
+
echo "π Admin token: Check startup logs or set ADMIN_TOKEN in .env"
|
| 33 |
echo ""
|
| 34 |
echo "Press Ctrl+C to stop"
|
| 35 |
echo ""
|