Scope is a multi-service real-world adventure platform. We take security seriously across every tier (Core/C#, Content/Django, Intel/Flask, Vue SPA, Nginx edge, infrastructure). This document describes our baseline posture, what to do if you discover a vulnerability, and the standards every commit must meet.
- Email:
security@scope.example.com(PGP key published on the website). - Do not open public GitHub issues for suspected vulnerabilities.
- Include reproduction steps, affected services/versions, and expected impact.
- We triage within 2 business days and aim to ship a fix inside 30 days for high/critical issues.
- Coordinated disclosure only. Please allow us to patch before publishing.
In scope: all services under this repository, deployed infrastructure (Terraform/K8s), and any image we publish to GHCR.
Out of scope: social-engineering, DDoS, rate-limit-bypass via residential
proxy rotation, and findings only reproducible on clients that opt in to
VITE_ENABLE_AUTH_MOCK_FALLBACK=true (development convenience).
- Authentication: Core issues HS256 JWTs with a >=32-byte secret
(
CORE_JWT_SECRET). Tokens carryiss,aud,sub,exp. No default fallback secret exists. - Password storage: bcrypt with work factor 12. Password policy enforces
=10 chars + 3 of 4 character classes + contextual checks (no username, no email local-part, not a top-50 common password).
- Breach check (opt-in): When
Hibp__Enabled=true, registration and password-reset paths call Have-I-Been-Pwned via the k-anonymity range API (first 5 SHA-1 hex chars only). Check is fail-open so HIBP outages cannot block user flows. Local policy is always enforced. - Account lockout: 5 consecutive failed logins lock the account for 15 minutes. Lockout state is audit-logged.
- Refresh tokens: stored as SHA-256 hashes. Plaintext values are never persisted or logged. Single-use rotation with replay detection: presenting an already-rotated or revoked token revokes every active refresh token for that user.
- Password reset:
POST /api/core/auth/password-reset/requestissues a cryptographically random token stored as a SHA-256 hash with a 30-minute expiry. Endpoint always returns 202 regardless of account existence to prevent enumeration. Completing a reset revokes all active refresh tokens. - Email verification:
POST /api/core/auth/email/verify/sendissues a 24-hour token (hashed at rest). Resend is throttled to one request per 5 minutes per account. - TOTP MFA: RFC 6238 SHA-1 TOTP, 6-digit / 30-second step, +/- 1 step
drift tolerance. Shared secret stored base-32 encoded; recovery codes
stored as SHA-256 hashes and single-use. Login returns
{ mfaRequired:true }401 when MFA is enabled and no code provided, prompting the client to collect a code and retry. - Authorization: Every mutating endpoint validates resource ownership /
membership. Use the project's helpers (
User.GetRequiredUserId(),can_manage_trip,IsAuthenticatedJWT). - CORS: Allowlisted origins only. Wildcards combined with credentials are a release-blocker.
- Rate limiting: In-process per-IP limits for auth and global paths. Edge
Nginx applies its own
limit_reqfor defense in depth. - Input validation: DRF serializers or C#
[ApiController]model binding with explicit DTOs. No raw SQL string interpolation. - CSP / security headers: Emitted by Core middleware, Django middleware, and the edge Nginx. Any page that requires a new external origin must update the CSP explicitly.
.envfiles and anything matchingauth-profiles.json,credentials*.json,*.pem,*.key,*.pfxare gitignored..env.exampleonly contains placeholders (CHANGE_ME_*).- CI workflows generate ephemeral values at runtime and never hard-code passwords or JWT secrets.
- Real credentials live in the platform's secret manager (GitHub encrypted secrets, AWS Secrets Manager, or k8s Secrets sealed with SOPS).
- Database access uses a least-privilege
scope_appuser.sais reserved for schema bootstrap in local Docker only.
- Every service container runs as a non-root user (
scope, UID 10001/10002/ 10003, ornginx), drops all Linux capabilities, and runs withno-new-privileges. - Production deployments must pin image digests, set
Encrypt=Trueon SQL connections, and use CA-signed certificates (noTrustServerCertificate). - Swagger UI is disabled outside Development unless
ENABLE_SWAGGER=trueis set explicitly./metricsis served only to the private network allowlist (METRICS_ALLOWED_CIDRS). - Nginx terminates at the edge and emits HSTS, CSP,
X-Content-Type-Options,X-Frame-Options: DENY,Referrer-Policy,Permissions-Policy,Cross-Origin-Opener-Policy, andCross-Origin-Resource-Policy.
Each pull request runs the full CI pipeline
(.github/workflows/ci.yml) which enforces:
| Control | Tooling |
|---|---|
| Static analysis (C#, Python, JS/TS) | GitHub CodeQL |
| Secret scanning | gitleaks (+ .github/gitleaks.toml) |
| Dependency CVE scanning | dotnet list package --vulnerable, pip-audit, npm audit |
| Filesystem & image vulnerability scan | Trivy (SARIF uploaded) |
| Django production hardening | manage.py check --deploy |
| Unit & integration tests | xUnit, pytest, Vitest |
Merging to main additionally requires:
- All CI jobs green (including the security jobs above).
- At least one reviewer approval.
- Dependabot alerts triaged within 7 days for High/Critical, 30 days for others. Configure these in the repo settings.
- Identify scope and contain (rotate affected secrets, revoke tokens, take offending services offline if necessary).
- Page the on-call via the #scope-security channel or PagerDuty.
- File a post-incident review within 5 business days using the template in
docs/incident-template.md. - Commit remediations with
fix(security): ...scope and link to the incident ticket.
If a secret is discovered in logs, repository history, chats, screenshots, etc., rotate immediately:
CORE_JWT_SECRET: generate a new 48-byte value (openssl rand -base64 48) and restart the Core deployment. All outstanding JWTs and refresh tokens become invalid.DJANGO_SECRET_KEY: rotate via secret manager; Django sessions will be invalidated.FLASK_SECRET_KEY: rotate; Flask sessions will be invalidated.- Database
scope_apppassword: rotate, update secrets, apply migration. - Cloud / provider tokens (e.g. OpenAI, AWS, Mapbox): revoke at the provider, rotate, and document the incident.
Never edit history to remove secrets—rotate them. History rewrites are only acceptable under the direction of the security team.