File size: 7,872 Bytes
4223796
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
"""
Auditoría de Seguridad — CrowData Backend.

Ejecutar: python -m app.security.audit
"""
import os
import sys
import re
sys.path.insert(0, "E:/crowdata/backend")

from pathlib import Path

class SecurityAudit:
    def __init__(self):
        self.findings = []
        self.passed = []
        self.warnings = []

    def check(self, name, condition, detail="", severity="HIGH"):
        if condition:
            self.passed.append(f"[PASS] {name}")
        else:
            self.findings.append(f"[FAIL-{severity}] {name}: {detail}")

    def warn(self, name, detail=""):
        self.warnings.append(f"[WARN] {name}: {detail}")

    def audit_env_file(self):
        env_path = Path("E:/crowdata/backend/.env")
        if not env_path.exists():
            self.check("ENV file exists", False, ".env file not found")
            return

        content = env_path.read_text(encoding="utf-8")

        # SECRET_KEY
        secret_match = re.search(r"SECRET_KEY=(.+)", content)
        if secret_match:
            secret = secret_match.group(1).strip()
            self.check("SECRET_KEY is set", len(secret) > 20,
                       f"SECRET_KEY too short ({len(secret)} chars)", "HIGH")
            self.check("SECRET_KEY is not default", secret not in ("", "changeme", "supersecret"),
                       "SECRET_KEY is a weak default value", "CRITICAL")
        else:
            self.check("SECRET_KEY exists", False, "SECRET_KEY not set in .env", "CRITICAL")

        # DATABASE_URL
        db_match = re.search(r"DATABASE_URL=(.+)", content)
        if db_match:
            db_url = db_match.group(1).strip()
            self.check("DATABASE_URL is set", len(db_url) > 0, "DATABASE_URL is empty", "HIGH")
            if db_url.startswith("sqlite"):
                self.warn("Using SQLite", "Consider PostgreSQL for production")
        else:
            self.check("DATABASE_URL exists", False, "DATABASE_URL not set", "HIGH")

        # SMTP credentials
        smtp_user = re.search(r"SMTP_USER=(.+)", content)
        smtp_pass = re.search(r"SMTP_PASSWORD=(.+)", content)
        if smtp_user and smtp_pass:
            self.check("SMTP credentials configured", True)
        else:
            self.warn("SMTP not configured", "Email delivery won't work")

    def audit_jwt_config(self):
        from app.config import get_settings
        settings = get_settings()

        self.check("JWT secret is set", len(settings.secret_key) > 0,
                   "JWT secret_key is empty", "CRITICAL")
        self.check("JWT expiration is reasonable",
                   settings.access_token_expire_minutes <= 1440,
                   f"Token expires in {settings.access_token_expire_minutes} minutes",
                   "MEDIUM")
        self.check("JWT algorithm is secure",
                   settings.jwt_algorithm in ("HS256", "HS384", "HS512", "RS256", "RS384", "RS512"),
                   f"Algorithm: {settings.jwt_algorithm}", "HIGH")

    def audit_cors(self):
        from app.main import ALLOWED_ORIGINS
        has_wildcard = "*" in ALLOWED_ORIGINS
        self.check("CORS no wildcard", not has_wildcard,
                   "CORS allows all origins (*)", "HIGH")
        self.check("CORS has specific origins", len(ALLOWED_ORIGINS) > 0,
                   "No CORS origins configured", "MEDIUM")

    def audit_password_hashing(self):
        from app.auth.config import get_jwt_strategy
        # fastapi-users uses bcrypt by default
        self.check("Password hashing (fastapi-users)", True,
                   "Using fastapi-users with bcrypt", "INFO")

    def audit_rate_limiting(self):
        from app.middleware.rate_limit import RATE_LIMITS, REPORT_LIMITS
        self.check("Rate limiting configured", len(RATE_LIMITS) > 0,
                   "No rate limits defined", "HIGH")
        self.check("Report limits configured", len(REPORT_LIMITS) > 0,
                   "No report limits defined", "HIGH")

    def audit_admin_protection(self):
        # Check that admin endpoints require superuser
        admin_file = Path("E:/crowdata/backend/app/admin/router.py")
        if admin_file.exists():
            content = admin_file.read_text(encoding="utf-8")
            self.check("Admin requires superuser",
                       "current_active_superuser" in content,
                       "Admin endpoints not protected by superuser check", "CRITICAL")

    def audit_sql_injection(self):
        # Check for raw SQL in service files
        service_files = list(Path("E:/crowdata/backend/app").rglob("*.py"))
        raw_sql_count = 0
        for f in service_files:
            try:
                content = f.read_text(encoding="utf-8")
                if "text(" in content and "execute" in content:
                    raw_sql_count += 1
            except Exception:
                pass
        self.warn("Raw SQL usage", f"{raw_sql_count} files use raw SQL (via SQLAlchemy text())")

    def audit_sensitive_data_logs(self):
        # Check for sensitive data in logger calls
        service_files = list(Path("E:/crowdata/backend/app").rglob("*.py"))
        issues = []
        for f in service_files:
            try:
                content = f.read_text(encoding="utf-8")
                lines = content.split("\n")
                for i, line in enumerate(lines):
                    if "logger" in line and ("password" in line.lower() or "token" in line.lower()):
                        if "hash" not in line.lower() and "hashed" not in line.lower():
                            issues.append(f"{f.name}:{i+1}")
            except Exception:
                pass
        if issues:
            self.warn("Sensitive data in logs", f"{len(issues)} potential occurrences")
        else:
            self.check("No sensitive data in logs", True)

    def audit_dependency_versions(self):
        req_file = Path("E:/crowdata/backend/requirements.txt")
        if req_file.exists():
            content = req_file.read_text(encoding="utf-8")
            self.check("Requirements file exists", True)
            # Check for known vulnerable patterns
            if "fastapi-users" in content:
                self.check("fastapi-users present", True)
            if "sqlalchemy" in content.lower():
                self.check("SQLAlchemy present", True)
        else:
            self.warn("No requirements.txt found")

    def run(self):
        print("=" * 60)
        print("  AUDITORÍA DE SEGURIDAD — CrowData Backend")
        print("=" * 60)
        print()

        print("1. Archivo de configuración (.env)")
        self.audit_env_file()
        print()

        print("2. Configuración JWT")
        self.audit_jwt_config()
        print()

        print("3. CORS")
        self.audit_cors()
        print()

        print("4. Password Hashing")
        self.audit_password_hashing()
        print()

        print("5. Rate Limiting")
        self.audit_rate_limiting()
        print()

        print("6. Protección Admin")
        self.audit_admin_protection()
        print()

        print("7. SQL Injection")
        self.audit_sql_injection()
        print()

        print("8. Datos sensibles en logs")
        self.audit_sensitive_data_logs()
        print()

        print("9. Dependencias")
        self.audit_dependency_versions()
        print()

        # Results
        print("=" * 60)
        print("  RESULTADOS")
        print("=" * 60)

        for p in self.passed:
            print(f"  {p}")

        for f in self.findings:
            print(f"  {f}")

        for w in self.warnings:
            print(f"  {w}")

        print()
        print(f"  Passed: {len(self.passed)}")
        print(f"  Failed: {len(self.findings)}")
        print(f"  Warnings: {len(self.warnings)}")
        print()


if __name__ == "__main__":
    audit = SecurityAudit()
    audit.run()