This document outlines the security measures implemented in the Finanpy application and provides guidance for secure deployment.
All security subtasks have been completed and verified:
- 6.9.1: SECURE_SSL_REDIRECT configured for production
- 6.9.2: SESSION_COOKIE_SECURE configured for production
- 6.9.3: CSRF_COOKIE_SECURE configured for production
- 6.9.4: SECURE_HSTS_SECONDS configured with 1-year duration
- 6.9.5: All views reviewed and verified for proper permissions
- 6.9.6: SQL injection protection verified (Django ORM only)
- 6.9.7: XSS protection verified (autoescaping enabled)
Location: /Users/azambuja/projects/finanpy/core/settings.py (lines 154-157)
if not DEBUG:
# Force all connections to use HTTPS instead of HTTP
SECURE_SSL_REDIRECT = config('SECURE_SSL_REDIRECT', default=True, cast=bool)- Automatically redirects all HTTP requests to HTTPS in production
- Prevents man-in-the-middle attacks by ensuring encrypted connections
- Only active when
DEBUG=Falseto allow local development
- Environment Variable:
SECURE_SSL_REDIRECT=True - Default: Automatically enabled in production (DEBUG=False)
- Requirement: Valid SSL/TLS certificate must be configured on your web server
- Obtain and install SSL certificate (Let's Encrypt, commercial CA, or cloud provider)
- Configure web server (Nginx/Apache) or load balancer for HTTPS
- Verify HTTPS is working before enabling this setting
- Test with
SECURE_SSL_REDIRECT=Truein staging environment
Location: /Users/azambuja/projects/finanpy/core/settings.py (lines 159-161)
if not DEBUG:
# Ensure session cookies are only sent over HTTPS
SESSION_COOKIE_SECURE = config('SESSION_COOKIE_SECURE', default=True, cast=bool)- Prevents session cookies from being transmitted over insecure HTTP connections
- Protects against session hijacking via network sniffing
- Critical for protecting user authentication state
Without this setting, an attacker on the same network could:
- Intercept session cookies over HTTP
- Impersonate the user
- Gain unauthorized access to the account
- Environment Variable:
SESSION_COOKIE_SECURE=True - Default: Automatically enabled in production (DEBUG=False)
- Requirement: Site must be fully accessible via HTTPS
Location: /Users/azambuja/projects/finanpy/core/settings.py (lines 163-165)
if not DEBUG:
# Ensure CSRF cookies are only sent over HTTPS
CSRF_COOKIE_SECURE = config('CSRF_COOKIE_SECURE', default=True, cast=bool)- Ensures CSRF protection tokens are only transmitted over secure connections
- Prevents CSRF token theft via network sniffing
- Works in conjunction with Django's built-in CSRF middleware
- Protects against Cross-Site Request Forgery attacks
- Prevents attackers from forging authenticated requests
- Essential for protecting state-changing operations (create, update, delete)
- Environment Variable:
CSRF_COOKIE_SECURE=True - Default: Automatically enabled in production (DEBUG=False)
- Note: Django's CSRF middleware is already enabled in MIDDLEWARE
Location: /Users/azambuja/projects/finanpy/core/settings.py (lines 167-177)
if not DEBUG:
# HSTS Configuration
SECURE_HSTS_SECONDS = config('SECURE_HSTS_SECONDS', default=31536000, cast=int)
SECURE_HSTS_INCLUDE_SUBDOMAINS = config('SECURE_HSTS_INCLUDE_SUBDOMAINS', default=True, cast=bool)
SECURE_HSTS_PRELOAD = config('SECURE_HSTS_PRELOAD', default=True, cast=bool)- Instructs browsers to ONLY access the site via HTTPS for the specified duration
- Prevents protocol downgrade attacks
- Protects against cookie hijacking and SSL stripping attacks
SECURE_HSTS_SECONDS: 31536000 (1 year)
- Duration (in seconds) that browsers will remember to only use HTTPS
- Recommended: Start with 300 seconds (5 minutes) for testing
- Production: 31536000 seconds (1 year) or longer
SECURE_HSTS_INCLUDE_SUBDOMAINS: True
- Applies HSTS policy to all subdomains
- Only enable if ALL subdomains support HTTPS
- Be cautious if you have multiple subdomains
SECURE_HSTS_PRELOAD: True
- Allows submission to browser HSTS preload lists
- Requires HSTS_SECONDS >= 1 year and INCLUDE_SUBDOMAINS=True
- See: https://hstspreload.org/
WARNING: HSTS is irreversible for the duration specified!
- Once enabled, browsers will refuse HTTP connections
- Cannot be disabled client-side before expiration
- Test thoroughly in staging before production
Recommended Deployment Strategy:
- Start with
SECURE_HSTS_SECONDS=300(5 minutes) - Test thoroughly for 24-48 hours
- Increase to
SECURE_HSTS_SECONDS=86400(1 day) - Test for another week
- Finally set to
SECURE_HSTS_SECONDS=31536000(1 year)
All view files were audited to verify:
- Proper authentication requirements
- User data filtering by
request.user - No cross-user data leakage
- Appropriate use of LoginRequiredMixin
Public Views (Intentionally public):
LoginView: Public access required for authenticationSignupView: Public access required for registrationCustomLogoutView: Handles logout (no sensitive data)
Protected Views:
DashboardView: UsesLoginRequiredMixin✓- Filters:
Account.objects.filter(user=user)✓ - Filters:
Transaction.objects.filter(account__user=user)✓ - All queries properly filtered by authenticated user ✓
- Filters:
All views use LoginRequiredMixin ✓
-
AccountListView:- Filters:
Account.objects.filter(user=self.request.user)✓ - Optimized with
select_related('user')✓
- Filters:
-
AccountCreateView:- Sets:
form.instance.user = self.request.user✓ - Associates account with authenticated user ✓
- Sets:
-
AccountUpdateView:- Filters:
Account.objects.filter(user=self.request.user)✓ - Prevents editing other users' accounts ✓
- Filters:
-
AccountDeleteView:- Filters:
Account.objects.filter(user=self.request.user)✓ - Prevents deleting other users' accounts ✓
- Protected from deletion if transactions exist (ProtectedError) ✓
- Filters:
All views use LoginRequiredMixin ✓
-
CategoryListView:- Filters:
Category.objects.filter(user=self.request.user)✓ - Optimized with
select_related('user')✓
- Filters:
-
CategoryCreateView:- Sets:
form.instance.user = self.request.user✓ - Associates category with authenticated user ✓
- Sets:
-
CategoryUpdateView:- Filters:
Category.objects.filter(user=self.request.user)✓ - Prevents editing other users' categories ✓
- Filters:
-
CategoryDeleteView:- Filters:
Category.objects.filter(user=self.request.user)✓ - Prevents deleting other users' categories ✓
- Protected from deletion if transactions exist (ProtectedError) ✓
- Filters:
All views use LoginRequiredMixin ✓
-
TransactionListView:- Filters:
Transaction.objects.filter(account__user=self.request.user)✓ - Optimized with
select_related('account', 'category')✓ - Context filters:
Account.objects.filter(user=self.request.user)✓ - Context filters:
Category.objects.filter(user=self.request.user)✓
- Filters:
-
TransactionCreateView:- Form receives:
kwargs['user'] = self.request.user✓ - Form filters accounts and categories by user ✓
- Form receives:
-
TransactionUpdateView:- Filters:
Transaction.objects.filter(account__user=self.request.user)✓ - Form receives:
kwargs['user'] = self.request.user✓ - Prevents editing other users' transactions ✓
- Filters:
-
TransactionDeleteView:- Filters:
Transaction.objects.filter(account__user=self.request.user)✓ - Prevents deleting other users' transactions ✓
- Filters:
All views use LoginRequiredMixin ✓
-
ProfileDetailView:- Overrides
get_object(): Returns only current user's profile ✓ - No pk in URL - always shows authenticated user's profile ✓
- Overrides
-
ProfileUpdateView:- Overrides
get_object(): Returns only current user's profile ✓ - No pk in URL - always edits authenticated user's profile ✓
- Overrides
✅ Authentication: All protected views use LoginRequiredMixin
✅ Authorization: All queries filter by user=request.user or account__user=request.user
✅ Data Isolation: No cross-user data leakage possible
✅ Query Optimization: Uses select_related() to prevent N+1 queries
✅ Protected Deletions: Accounts and Categories protected if transactions exist
✅ Form Security: User assignment happens server-side, not from form data
The following views are intentionally accessible without authentication:
LoginView: Required for users to authenticateSignupView: Required for new user registrationCustomLogoutView: Handles logout, redirects to public home page
Note: The home page view (if exists) may also be public by design.
Searched entire codebase for:
- Raw SQL usage:
.raw(),.execute(),cursor() - Direct SQL query execution
No raw SQL queries found ✓
All database queries use Django ORM exclusively:
Model.objects.filter()Model.objects.get()Model.objects.create().aggregate(),.annotate()- QuerySet methods with automatic parameterization
Django automatically escapes and parameterizes all values:
# SECURE: Django ORM (used throughout Finanpy)
Account.objects.filter(name=user_input)
# Generates: SELECT * FROM accounts WHERE name = %s
# Parameters: [user_input] -- safely escaped
# DANGEROUS: Raw SQL (NOT USED in Finanpy)
cursor.execute(f"SELECT * FROM accounts WHERE name = '{user_input}'")
# Vulnerable to: ' OR '1'='1- ✅ Zero raw SQL queries in codebase
- ✅ All queries use Django ORM
- ✅ All parameters automatically escaped
- ✅ No string concatenation in queries
- ✅ No
.extra()with unsafe SQL
- NEVER use raw SQL without parameterization
- ALWAYS use Django ORM for queries
- If raw SQL is absolutely necessary (rare), use parameterized queries:
Model.objects.raw('SELECT * FROM table WHERE id = %s', [id])
Searched entire codebase for:
- Unsafe template filters:
|safe - Disabled autoescaping:
{% autoescape off %}
No unsafe template operations found ✓
Django automatically escapes all variables in templates:
{# SECURE: Automatic escaping (used throughout Finanpy) #}
{{ account.name }}
{# If name = "<script>alert('xss')</script>" #}
{# Renders as: <script>alert('xss')</script> #}
{# DANGEROUS: Manual bypass (NOT USED in Finanpy) #}
{{ account.name|safe }}
{# Would render actual <script> tag - VULNERABLE #}Location: /Users/azambuja/projects/finanpy/core/settings.py (lines 67-80)
Template configuration includes:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'OPTIONS': {
'context_processors': [...],
# Autoescaping is enabled by default (not explicitly disabled)
},
},
]- ✅ No
|safefilters in templates - ✅ No
{% autoescape off %}blocks - ✅ Autoescaping enabled globally (Django default)
- ✅ All user input automatically escaped
- ✅ JSON data properly serialized with
json.dumps()
All templates audited:
/Users/azambuja/projects/finanpy/templates/base.html/Users/azambuja/projects/finanpy/templates/dashboard.html/Users/azambuja/projects/finanpy/templates/auth/*.html/Users/azambuja/projects/finanpy/templates/accounts/*.html/Users/azambuja/projects/finanpy/templates/categories/*.html/Users/azambuja/projects/finanpy/templates/transactions/*.html/Users/azambuja/projects/finanpy/templates/profiles/*.html
SECURE_BROWSER_XSS_FILTER (Enabled)
SECURE_BROWSER_XSS_FILTER = TrueEnables browser's built-in XSS filtering as an additional layer.
SECURE_CONTENT_TYPE_NOSNIFF (Enabled)
SECURE_CONTENT_TYPE_NOSNIFF = TruePrevents browsers from MIME-sniffing responses, which could lead to XSS.
Beyond the Sprint 6.9 requirements, additional security headers are configured:
Location: /Users/azambuja/projects/finanpy/core/settings.py (line 187)
X_FRAME_OPTIONS = 'DENY'Purpose: Prevents the site from being embedded in iframes Protection: Defends against clickjacking attacks Impact: Site cannot be embedded in frames on other domains
Location: /Users/azambuja/projects/finanpy/core/settings.py (line 181)
SECURE_CONTENT_TYPE_NOSNIFF = TruePurpose: Prevents MIME-sniffing attacks Protection: Forces browsers to respect declared content types Impact: Reduces risk of content-type confusion attacks
The following middleware provides additional security:
Location: /Users/azambuja/projects/finanpy/core/settings.py (lines 55-63)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # Security headers
'django.contrib.sessions.middleware.SessionMiddleware', # Session management
'django.middleware.common.CommonMiddleware', # Common utilities
'django.middleware.csrf.CsrfViewMiddleware', # CSRF protection
'django.contrib.auth.middleware.AuthenticationMiddleware', # Authentication
'django.contrib.messages.middleware.MessageMiddleware', # Messages
'django.middleware.clickjacking.XFrameOptionsMiddleware', # Clickjacking protection
]All critical security middleware is properly configured and ordered.
- Generate strong SECRET_KEY (50+ random characters)
- Set DEBUG=False in production environment
- Configure ALLOWED_HOSTS with actual domain(s)
- Obtain and install SSL/TLS certificate
- Configure web server for HTTPS (Nginx/Apache/Load Balancer)
- Update .env file with production settings
- Review environment variables in .env.example
- Set up database backups (if using PostgreSQL/MySQL)
- SECURE_SSL_REDIRECT=True (after HTTPS is working)
- SESSION_COOKIE_SECURE=True
- CSRF_COOKIE_SECURE=True
- SECURE_HSTS_SECONDS=31536000 (start lower, increase gradually)
- SECURE_HSTS_INCLUDE_SUBDOMAINS=True (if applicable)
- SECURE_HSTS_PRELOAD=True (if submitting to preload list)
Run the deployment check command:
python manage.py check --deployExpected in Development (DEBUG=True):
- Warnings about security settings not enabled
- These are normal and will be resolved when DEBUG=False
Expected in Production (DEBUG=False):
- No warnings if all settings are properly configured
- Address any remaining warnings before going live
- Verify HTTPS is working correctly
- Test all authentication flows
- Verify session persistence
- Test CSRF protection on forms
- Check browser security headers (use securityheaders.com)
- Verify HSTS headers are present
- Test cross-user data isolation
- Monitor application logs for security events
# Full deployment security check
python manage.py check --deploy
# Run all checks
python manage.py check
# Database checks
python manage.py check --database default# Django shell - check security settings
python manage.py shell
>>> from django.conf import settings
>>> settings.DEBUG # Should be False in production
>>> settings.ALLOWED_HOSTS # Should include your domain
>>> settings.SECRET_KEY[:10] # Check length (don't print full key)# Install django-extensions
pip install django-extensions
# Run with SSL (self-signed)
python manage.py runserver_plus --cert-file cert.pem-
Assess the Impact
- Determine scope of the issue
- Identify affected users
- Document the vulnerability
-
Immediate Actions
- Disable affected functionality if critical
- Rotate SECRET_KEY if compromised
- Invalidate sessions if necessary
- Notify affected users if data was exposed
-
Remediation
- Apply security patches
- Update dependencies
- Review and fix vulnerable code
- Deploy fixes immediately
-
Post-Incident
- Conduct security audit
- Update security documentation
- Implement additional monitoring
- Review access logs
If you discover a security vulnerability:
- DO NOT open a public GitHub issue
- Email security contact privately (if configured)
- Provide detailed reproduction steps
- Allow time for patches before public disclosure
Weekly:
- Review application logs for anomalies
- Check for failed login attempts
- Monitor unusual data access patterns
Monthly:
- Update dependencies:
pip list --outdated - Review Django security releases
- Check for security advisories
Quarterly:
- Run comprehensive security audit
- Review and update security documentation
- Test incident response procedures
- Review user access permissions
# Check for security vulnerabilities in dependencies
pip install safety
safety check
# Update dependencies (test thoroughly after)
pip install --upgrade django
pip install --upgrade -r requirements.txtAll security requirements for Sprint 6, Tarefa 6.9 have been successfully implemented and verified:
✅ 6.9.1: SSL/HTTPS redirect configured ✅ 6.9.2: Session cookie security enabled ✅ 6.9.3: CSRF cookie security enabled ✅ 6.9.4: HSTS configured with 1-year duration ✅ 6.9.5: All views audited - proper authentication and authorization ✅ 6.9.6: SQL injection protection verified - only ORM used ✅ 6.9.7: XSS protection verified - autoescaping enabled
Additional protections configured:
- Content-Type nosniff
- XSS filter enabled
- Clickjacking protection (X-Frame-Options)
- CSRF middleware enabled
- Query optimization with select_related
- Protected deletions (accounts/categories with transactions)
The application follows Django security best practices and implements defense-in-depth strategies. All security settings are properly documented in .env.example and automatically activate in production when DEBUG=False.
Document Version: 1.0 Last Updated: 2025-10-26 Sprint: 6 - Tarefa 6.9 Status: ✅ Complete