If you discover a security vulnerability, please email security@groundshare.app with:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
Do NOT open a public GitHub issue for security vulnerabilities.
- JWT Bearer tokens (access: 15min, refresh: 7 days with rotation)
- Access tokens stored in memory only (not localStorage)
- Google OAuth via official
Google.Apis.Authlibrary with audience validation - BCrypt password hashing (work factor 12)
- All production secrets stored in Azure Key Vault
- App Service authenticates via Managed Identity (no secrets in env vars)
appsettings.jsoncontains only placeholder values- Pre-commit hook (gitleaks) blocks accidental secret commits
- FluentValidation on all API request DTOs
- File uploads validated by magic-byte sniffing (not just Content-Type)
- All database access uses parameterized stored procedures
/auth/login: 5/min per IP/auth/register: 3/hour per IP/api/files/upload: 20/min per IP- Global: 100/min per IP
Content-Security-Policyon both API and frontendStrict-Transport-Security(HSTS, 1 year)X-Frame-Options: DENYX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=()
- Private container (no public access)
- Time-limited SAS URLs for read access (1 hour)
- Managed Identity for write access (no connection strings)
| Secret | Rotation | How |
|---|---|---|
| JWT signing key | Every 90 days | Update Jwt--Key in Key Vault |
| Google API keys | Every 90 days | Regenerate in Google Cloud Console, update Key Vault |
| SQL admin password | Every 90 days | az sql server update, update Key Vault |
| Data | Backup | Retention |
|---|---|---|
| Azure SQL | Automatic point-in-time restore | 7 days (Basic tier) |
| Blob Storage | Soft delete enabled | 7 days |
| Key Vault | Soft delete + purge protection | 90 days |
To delete a user's data:
- Delete from
[user]table (cascades to events, reviews, comments, votes, favorites, notifications) - Delete associated blobs from Azure Storage
- Remove entries from
[audit_log]after retention period
- Application Insights captures all request/error telemetry
- Alert: 5+ server exceptions in 5 minutes triggers email notification
- Audit log table records: login success/failure, password changes, account deletion
- Structured logging via Serilog with PII redaction
- gitleaks: Secret scanning on every PR
- npm audit: Dependency vulnerability check (HIGH+CRITICAL block merge)
- dotnet list package --vulnerable: NuGet vulnerability check
- Dependabot: Weekly dependency update PRs
Refresh-token rotation is single-winner with a grace window and token-reuse
detection — the whole flow is in RefreshTokenDAL + sp_RotateRefreshToken /
the reuse-detection SP in GroundShareDB.sql:
- Atomic rotation (
sp_RotateRefreshToken): revoke the presented token and record its successor's hash onrefresh_token.Replaced_Byin one statement, so a token is never left revoked-with-no-replacement. - Race-safe (grace window): two near-simultaneous refreshes of the same
token (e.g. two tabs) no longer log the user out — the loser sees the token
revoked-recently with a
Replaced_Byand is handed back the recorded successor instead of a 401. The frontend's single-flight guard (refreshPromiseinservices/api/client.ts) still collapses concurrent refreshes within one client. - Reuse detection (theft signal): a revoked token replayed outside the
grace window stamps
Reused_At, revokes every still-active token for the user, and nulls outReplaced_Byacross the whole family so no family token can mint a fresh session afterward.
This closes what was previously logged here as a deferred backlog item.
- No DB migration versioning —
GroundShareDB.sqlis a single re-runnable script; schema history and rollback are by hand. Acceptable at current scale; revisit if the schema churn or the team grows.