Security Checklist - Production Hardening Audit¶
Audit Date: January 10, 2026 Auditor: Engineer D (Production Hardening) Status: PASS with recommendations
1. Rate Limiting¶
Status: PASS¶
All public-facing endpoints have rate limiting via slowapi with Redis backing.
| Route Category | Rate Limit | File Location |
|---|---|---|
/auth/verify/start |
5/minute | app/api/auth_routes.py:73 |
/auth/verify/confirm |
10/minute | app/api/auth_routes.py:117 |
/auth/login |
10/minute | app/api/auth_routes.py:317 |
/auth/refresh |
10/minute | app/api/auth_routes.py:474 |
/payment/checkout/* |
3/minute | app/api/payment_routes.py:96,156,394 |
/account (DELETE) |
3/hour | app/api/account_routes.py:142 |
/account/export |
2/day | app/api/account_routes.py:337 |
/support/report-bug |
5/hour | app/api/support_routes.py:158 |
/support/feedback |
10/hour | app/api/support_routes.py:229 |
/support/ticket |
5/hour | app/api/support_routes.py:295 |
Redis Backing¶
- Implementation:
app/scheduler/redis_rate_limiter.py - Features:
- Burst protection: 5 messages per 30 seconds
- Minute limit: 10 messages/minute
- Hourly limit: 60 messages/hour
- Daily limit: 200 messages/day
- Fail-open behavior: If Redis unavailable, allows requests (prevents outages from breaking the app)
Recommendations¶
- Add rate limiting to
/edge/*routes (currently relies on edge agent authentication) - Consider per-user rate limiting for authenticated endpoints (currently IP-based)
2. Input Validation¶
Status: PASS¶
All API endpoints use Pydantic models for request validation.
Examples:
# app/api/user_routes.py
class UpdateProfileRequest(BaseModel):
name: Optional[str] = Field(None, max_length=100)
pronouns: Optional[str] = Field(None, max_length=50)
email: Optional[EmailStr] = None
display_name: Optional[str] = Field(None, max_length=100)
Validation Coverage¶
- String length limits on all text fields
- Email validation via
EmailStr - UUID validation for identifiers
- Enum validation for status fields
3. SQL Injection Prevention¶
Status: PASS¶
All database operations use SQLAlchemy ORM with parameterized queries.
Query Pattern (Safe):
# Parameterized queries throughout codebase
user = db.query(User).filter_by(phone=user_phone).first()
db.query(User).filter(User.id == user_id).first()
No raw SQL with f-strings found in:
- app/api/*.py
- app/payment/service.py
- app/oauth/routes.py
- app/database/*.py
4. Authentication & Authorization¶
Status: PASS¶
JWT Token Security¶
- Location:
app/identity/session_manager.py - Algorithm: HS256
- Access Token Expiry: 60 minutes
- Refresh Token Expiry: 30 days
- Validation: Proper expiry and signature verification
# Token verification includes expiration check
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
Secret Key Validation¶
- Production validation: Prevents default secret key in production
- Location:
app/config.py:57-66
@field_validator("secret_key")
def validate_secret_key(cls, v: str, info) -> str:
if environment == "production" and v == "dev-secret-key-change-in-production":
raise ValueError("Cannot use default SECRET_KEY in production!")
OAuth Token Encryption¶
- Implementation: Fernet symmetric encryption
- Location:
app/oauth/token_manager.py - Key Management: FERNET_KEY environment variable
5. Webhook Signature Verification¶
Status: PASS¶
Stripe Webhooks¶
- Location:
app/payment/stripe_client.py:167-196 - Method:
stripe.Webhook.construct_event()with signature verification
Twilio OTP Verification¶
- Location:
app/identity/twilio_client.py - Method: Twilio Verify API with service SID authentication
verification_check = self.client.verify.v2.services(
self.verify_service_sid
).verification_checks.create(to=phone, code=code)
Edge Agent Authentication¶
- Location:
app/edge/auth.py,app/security/hmac_auth.py - Method: HMAC signature with timestamp validation
6. CORS Configuration¶
Status: PASS¶
- Location:
app/main.py:292-304,app/config.py:30-55 - Implementation: Dynamic origins based on environment
Allowed Origins by Environment:
| Environment | Origins |
|---|---|
| Development | localhost:3000, localhost:8502, archety-dev.vercel.app, archety-backend-dev.up.railway.app |
| Production | archety.vercel.app, archety-web.vercel.app, archety-backend-prod.up.railway.app |
Headers:
- allow_credentials: True
- allow_methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
- allow_headers: ["Content-Type", "Authorization", "X-Requested-With", "X-CSRF-Token"]
7. CSRF Protection¶
Status: PASS¶
- Location:
app/security/csrf.py,app/middleware/csrf_middleware.py - Library: fastapi-csrf-protect
- Configuration:
- Cookie:
httponly=True,samesite=lax - Secure cookie in production
- Selective validation (web-facing endpoints only)
8. Sensitive Data in Logs¶
Status: PASS with Recommendations¶
Good Practices Found:
- Phone numbers truncated: phone[-4:] (last 4 digits only)
- User IDs truncated: user_id[:8]... (first 8 chars)
- Tokens not logged in full
Examples:
logger.info(f"Created session for user {user_id[:8]}... (phone: {phone[-4:]})")
logger.info(f"Generated token for edge agent {edge_agent_id}")
Recommendations¶
- Review
app/oauth/routes.py:92-105- logs full user_phone during OAuth flow - Consider masking middle digits of phone in all logs:
+1***555***1234
9. Sentry Integration¶
Status: PASS¶
- Location:
app/main.py:186-203 - Features:
- FastAPI and Starlette integrations
- Environment tagging
- 10% sampling for performance/profiling
- PII Protection:
send_default_pii=False
sentry_sdk.init(
dsn=settings.sentry_dsn,
integrations=[
StarletteIntegration(transaction_style="endpoint"),
FastApiIntegration(transaction_style="endpoint"),
],
traces_sample_rate=0.1,
profiles_sample_rate=0.1,
environment=settings.environment,
send_default_pii=False, # Privacy protection
)
10. Correlation IDs / Request Tracing¶
Status: PASS¶
- Location:
app/main.py:311-345 - Features:
- Auto-generated UUID for each request
- Accepts
X-Request-IDheader for distributed tracing - Stored in
request.state.correlation_id - Returned in
X-Request-IDresponse header - Included in structured log messages
correlation_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())
request.state.correlation_id = correlation_id
11. Additional Security Features¶
Content Moderation¶
- Status: Planned (Phase 9)
- Current: Basic keyword filtering
Password/Secret Exposure¶
- Status: PASS
- No passwords stored (phone OTP authentication only)
- Secrets loaded from environment variables
- Production validators prevent default secrets
HTTPS¶
- Status: PASS
- Railway deployment enforces HTTPS
- Secure cookies enabled in production
Summary¶
| Category | Status | Notes |
|---|---|---|
| Rate Limiting | PASS | Redis-backed, all public endpoints covered |
| Input Validation | PASS | Pydantic models throughout |
| SQL Injection | PASS | SQLAlchemy ORM only |
| JWT Security | PASS | Proper expiry, algorithm, validation |
| OAuth Token Encryption | PASS | Fernet encryption |
| Webhook Signatures | PASS | Stripe + Twilio verified |
| CORS | PASS | Environment-specific origins |
| CSRF | PASS | Selective middleware |
| Log Sanitization | PASS* | Minor phone logging improvements recommended |
| Sentry | PASS | Full integration with PII protection |
| Correlation IDs | PASS | Full request tracing |
Action Items (Non-blocking)¶
- Add rate limiting to edge agent routes
- Improve phone number masking in OAuth logs
- Consider per-user rate limiting for authenticated endpoints
Last Updated: January 10, 2026