Security & Compliance Standards
This skill defines mandatory security and compliance patterns. Violations of these patterns are blocking issues in code review.
Auth0 Integration
Backend (ASP.NET Core)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = $"https://{config["Auth0:Domain"]}/";
options.Audience = config["Auth0:Audience"];
options.MapInboundClaims = false; // CRITICAL: preserves "sub", "email" claim names
options.NameClaimType = "sub";
});
CRITICAL: MapInboundClaims = false prevents ASP.NET from remapping JWT claims to XML namespace URIs. Without this, User.FindFirst("sub") returns null.
Claims Extraction
private string CurrentUserId => User.FindFirst("sub")?.Value
?? throw new UnauthorizedAccessException("No sub claim");
private string? UserEmail => User.FindFirst("email")?.Value;
Frontend (Auth0 React SDK)
// Token accessor for API client
const { getAccessTokenSilently } = useAuth0()
setTokenAccessor(() => getAccessTokenSilently())
- Tokens stored in memory only — NEVER localStorage or sessionStorage
- Use Auth0's built-in refresh token rotation
- AuthGate component handles login redirect + user provisioning
Token Best Practices
- Access tokens: Short-lived (5-15 min), stored in memory
- Refresh tokens: HttpOnly cookie or Auth0 SDK rotation
- ID tokens: Never sent to APIs — use access tokens
- For BFF pattern: Backend holds tokens, frontend uses session cookies
OWASP Top 10 Mitigations
1. Injection (SQL, NoSQL, Command)
- Use parameterized queries (EF Core does this by default)
- Never concatenate user input into SQL:
$"SELECT * FROM users WHERE id = '{id}'" is FORBIDDEN
- Dapper: Always use
@param parameters, never string interpolation
2. Broken Authentication
- Auth0 handles password hashing, MFA, brute-force protection
- Implement account lockout for custom auth flows
- Never log tokens or passwords
3. Sensitive Data Exposure
- TLS everywhere (enforced at infrastructure level)
- Encrypt PII at rest (see PII section below)
- Never return sensitive fields in API responses (passwords, tokens, full ID numbers)
4. XXE (XML External Entities)
- Don't parse XML from user input; use JSON exclusively
5. Broken Access Control
[Authorize] at controller level, [AllowAnonymous] per-method only when justified
- Always filter by
CurrentUserId or TenantId in queries — never trust client-provided IDs
- Check ownership:
if (goal.UserId != CurrentUserId) return Forbid();
6. Security Misconfiguration
- Remove Swagger in production:
if (app.Environment.IsDevelopment()) app.UseSwagger();
- Set security headers (CSP, X-Frame-Options, HSTS)
- Never expose stack traces in production error responses
7. XSS (Cross-Site Scripting)
- React auto-escapes JSX by default — never use
dangerouslySetInnerHTML
- Sanitize markdown rendering if accepting user-generated content
- CSP headers:
Content-Security-Policy: default-src 'self'
8. Insecure Deserialization
- Use
System.Text.Json with strict type handling
- Never deserialize untrusted data into arbitrary types
9. Insufficient Logging
- Log authentication events (login, logout, failed attempts)
- Log authorization failures
- Log data access patterns for sensitive resources
- NEVER log tokens, passwords, or PII
10. SSRF (Server-Side Request Forgery)
- Validate and allowlist URLs before making backend HTTP calls
- Never let user input control entire URLs for backend fetches
PII / Data Protection Compliance
What is PII in Our Systems
| Field |
Classification |
Treatment |
| Email |
PII |
Encrypt at rest, mask in logs |
| Phone |
PII |
Encrypt at rest, mask in logs |
| Full Name |
PII |
Encrypt at rest |
| ID/Passport Number |
Sensitive PII |
Encrypt at rest, never log, mask in UI |
| Tax ID (TIN) |
Sensitive PII |
Encrypt at rest, never log, mask in UI |
| Date of Birth |
PII |
Encrypt at rest |
| Address |
PII |
Encrypt at rest |
| Bank Account |
Financial PII |
Encrypt at rest, never log, mask in UI |
| Income/Net Worth |
Financial PII |
Encrypt at rest |
Data Protection Requirements
- Explicit consent: Collect only with clear, specific consent
- Purpose limitation: Use PII only for the stated purpose
- Data minimization: Collect only what's needed
- Breach notification: Report compromises to the relevant authority promptly
- Right to erasure: Users can request complete deletion of their PII
- Accountability: Organization must have a designated data protection officer/role
Implementation Patterns
Encryption at Rest
// Use ASP.NET Core Data Protection for column-level encryption
public class EncryptedStringConverter : ValueConverter<string, string>
{
public EncryptedStringConverter(IDataProtector protector)
: base(v => protector.Protect(v), v => protector.Unprotect(v)) { }
}
Masking in Logs
// NEVER log PII directly
_logger.LogInformation("User {UserId} updated profile", userId); // OK
_logger.LogInformation("User {Email} logged in", email); // FORBIDDEN
Masking in UI
// Show only last 4 digits of ID numbers
function maskId(id: string): string {
return '\u2022'.repeat(id.length - 4) + id.slice(-4)
}
Right to Erasure
public async Task EraseUserDataAsync(Guid userId, CancellationToken ct)
{
// Cascade delete all PII — keep only anonymized transaction records
var user = await _db.Users.Include(u => u.Profile).FirstAsync(u => u.Id == userId, ct);
user.Email = $"deleted_{userId}@erased.local";
user.Profile.FirstName = "Deleted";
user.Profile.LastName = "User";
user.Profile.Phone = null;
// ... scrub all PII fields
await _db.SaveChangesAsync(ct);
}
PCI DSS 4.0 (Payment Card Data)
Scope Reduction
- NEVER store card numbers (PAN) — use payment processor tokenization (e.g., Stripe)
- Use hosted payment forms (Stripe Elements, processor ACH forms) to keep card data off our servers
- If PAN must transit our backend: encrypt immediately, log only last 4 digits
Requirements
- TLS 1.2+ for all cardholder data in transit
- Strong cryptography for stored cardholder data (AES-256)
- Unique user IDs for system access
- Restrict access to cardholder data on a need-to-know basis
- Log and monitor all access to cardholder data
- Regular vulnerability scans
Our Approach
Use third-party payment processors (e.g., Stripe) that handle PCI compliance. Our responsibility is:
- Never store raw card data
- Use tokenized references only
- Secure API keys for payment services
- Log payment events (amounts, status) without card details
Secrets Management
Rules
- Environment variables for all secrets — never in code or config files
.env files: NEVER committed, listed in .gitignore
- API keys: Backend only — NEVER in frontend code
- Auth0 client secrets: Backend only
- Database passwords: Environment variables
- Third-party API keys: Backend only, use appropriate auth scheme (e.g., HTTP Basic, Bearer)
Configuration Pattern
// appsettings.json — structure only, no values
"ExternalService": {
"ApiKey": "", // Set via EXTERNALSERVICE__APIKEY env var
"ApiSecret": "" // Set via EXTERNALSERVICE__APISECRET env var
}
Deployment
- Secrets set as platform-specific service variables (e.g., Railway, Azure, AWS)
- Never in Dockerfiles or docker-compose.yml production configs
- Use platform-native reference variables for cross-service secrets
Audit Trail
For sensitive operations, log:
public record AuditEntry(
string Action, // "user.profile.updated", "goal.created", "deposit.initiated"
string UserId,
string ResourceType,
string ResourceId,
DateTimeOffset Timestamp,
Dictionary<string, object?> Metadata // Changed fields, but NO PII values
);
- Log WHO did WHAT to WHICH resource, WHEN
- Never log the actual PII values in audit entries
- Retain audit logs per regulatory requirements
1---2name: security-compliance3description: Security and compliance standards — Auth0 JWT integration, OWASP Top 10 mitigations, PCI DSS 4.0, PII/POPIA data protection, encryption at rest and in transit, secrets management, and audit trails. Use this skill when implementing authentication, handling sensitive data, storing PII, processing payments, reviewing security posture, or ensuring regulatory compliance. MUST use for any code touching user data, financial data, or authentication flows.4---56# Security & Compliance Standards78This skill defines mandatory security and compliance patterns. Violations of these patterns are blocking issues in code review.910## Auth0 Integration1112### Backend (ASP.NET Core)13```csharp14builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)15 .AddJwtBearer(options =>16 {17 options.Authority = $"https://{config["Auth0:Domain"]}/";18 options.Audience = config["Auth0:Audience"];19 options.MapInboundClaims = false; // CRITICAL: preserves "sub", "email" claim names20 options.NameClaimType = "sub";21 });22```2324**CRITICAL**: `MapInboundClaims = false` prevents ASP.NET from remapping JWT claims to XML namespace URIs. Without this, `User.FindFirst("sub")` returns null.2526### Claims Extraction27```csharp28private string CurrentUserId => User.FindFirst("sub")?.Value29 ?? throw new UnauthorizedAccessException("No sub claim");30private string? UserEmail => User.FindFirst("email")?.Value;31```3233### Frontend (Auth0 React SDK)34```tsx35// Token accessor for API client36const { getAccessTokenSilently } = useAuth0()37setTokenAccessor(() => getAccessTokenSilently())38```3940- Tokens stored in memory only — NEVER localStorage or sessionStorage41- Use Auth0's built-in refresh token rotation42- AuthGate component handles login redirect + user provisioning4344### Token Best Practices45- Access tokens: Short-lived (5-15 min), stored in memory46- Refresh tokens: HttpOnly cookie or Auth0 SDK rotation47- ID tokens: Never sent to APIs — use access tokens48- For BFF pattern: Backend holds tokens, frontend uses session cookies4950## OWASP Top 10 Mitigations5152### 1. Injection (SQL, NoSQL, Command)53- Use parameterized queries (EF Core does this by default)54- Never concatenate user input into SQL: `$"SELECT * FROM users WHERE id = '{id}'"` is FORBIDDEN55- Dapper: Always use `@param` parameters, never string interpolation5657### 2. Broken Authentication58- Auth0 handles password hashing, MFA, brute-force protection59- Implement account lockout for custom auth flows60- Never log tokens or passwords6162### 3. Sensitive Data Exposure63- TLS everywhere (enforced at infrastructure level)64- Encrypt PII at rest (see PII section below)65- Never return sensitive fields in API responses (passwords, tokens, full ID numbers)6667### 4. XXE (XML External Entities)68- Don't parse XML from user input; use JSON exclusively6970### 5. Broken Access Control71- `[Authorize]` at controller level, `[AllowAnonymous]` per-method only when justified72- Always filter by `CurrentUserId` or `TenantId` in queries — never trust client-provided IDs73- Check ownership: `if (goal.UserId != CurrentUserId) return Forbid();`7475### 6. Security Misconfiguration76- Remove Swagger in production: `if (app.Environment.IsDevelopment()) app.UseSwagger();`77- Set security headers (CSP, X-Frame-Options, HSTS)78- Never expose stack traces in production error responses7980### 7. XSS (Cross-Site Scripting)81- React auto-escapes JSX by default — never use `dangerouslySetInnerHTML`82- Sanitize markdown rendering if accepting user-generated content83- CSP headers: `Content-Security-Policy: default-src 'self'`8485### 8. Insecure Deserialization86- Use `System.Text.Json` with strict type handling87- Never deserialize untrusted data into arbitrary types8889### 9. Insufficient Logging90- Log authentication events (login, logout, failed attempts)91- Log authorization failures92- Log data access patterns for sensitive resources93- NEVER log tokens, passwords, or PII9495### 10. SSRF (Server-Side Request Forgery)96- Validate and allowlist URLs before making backend HTTP calls97- Never let user input control entire URLs for backend fetches9899## PII / Data Protection Compliance100101### What is PII in Our Systems102| Field | Classification | Treatment |103|-------|---------------|-----------|104| Email | PII | Encrypt at rest, mask in logs |105| Phone | PII | Encrypt at rest, mask in logs |106| Full Name | PII | Encrypt at rest |107| ID/Passport Number | Sensitive PII | Encrypt at rest, never log, mask in UI |108| Tax ID (TIN) | Sensitive PII | Encrypt at rest, never log, mask in UI |109| Date of Birth | PII | Encrypt at rest |110| Address | PII | Encrypt at rest |111| Bank Account | Financial PII | Encrypt at rest, never log, mask in UI |112| Income/Net Worth | Financial PII | Encrypt at rest |113114### Data Protection Requirements1151. **Explicit consent**: Collect only with clear, specific consent1162. **Purpose limitation**: Use PII only for the stated purpose1173. **Data minimization**: Collect only what's needed1184. **Breach notification**: Report compromises to the relevant authority promptly1195. **Right to erasure**: Users can request complete deletion of their PII1206. **Accountability**: Organization must have a designated data protection officer/role121122### Implementation Patterns123124#### Encryption at Rest125```csharp126// Use ASP.NET Core Data Protection for column-level encryption127public class EncryptedStringConverter : ValueConverter<string, string>128{129 public EncryptedStringConverter(IDataProtector protector)130 : base(v => protector.Protect(v), v => protector.Unprotect(v)) { }131}132```133134#### Masking in Logs135```csharp136// NEVER log PII directly137_logger.LogInformation("User {UserId} updated profile", userId); // OK138_logger.LogInformation("User {Email} logged in", email); // FORBIDDEN139```140141#### Masking in UI142```typescript143// Show only last 4 digits of ID numbers144function maskId(id: string): string {145 return '\u2022'.repeat(id.length - 4) + id.slice(-4)146}147```148149#### Right to Erasure150```csharp151public async Task EraseUserDataAsync(Guid userId, CancellationToken ct)152{153 // Cascade delete all PII — keep only anonymized transaction records154 var user = await _db.Users.Include(u => u.Profile).FirstAsync(u => u.Id == userId, ct);155 user.Email = $"deleted_{userId}@erased.local";156 user.Profile.FirstName = "Deleted";157 user.Profile.LastName = "User";158 user.Profile.Phone = null;159 // ... scrub all PII fields160 await _db.SaveChangesAsync(ct);161}162```163164## PCI DSS 4.0 (Payment Card Data)165166### Scope Reduction167- **NEVER store card numbers (PAN)** — use payment processor tokenization (e.g., Stripe)168- Use hosted payment forms (Stripe Elements, processor ACH forms) to keep card data off our servers169- If PAN must transit our backend: encrypt immediately, log only last 4 digits170171### Requirements172- TLS 1.2+ for all cardholder data in transit173- Strong cryptography for stored cardholder data (AES-256)174- Unique user IDs for system access175- Restrict access to cardholder data on a need-to-know basis176- Log and monitor all access to cardholder data177- Regular vulnerability scans178179### Our Approach180Use third-party payment processors (e.g., Stripe) that handle PCI compliance. Our responsibility is:1811. Never store raw card data1822. Use tokenized references only1833. Secure API keys for payment services1844. Log payment events (amounts, status) without card details185186## Secrets Management187188### Rules189- **Environment variables** for all secrets — never in code or config files190- `.env` files: NEVER committed, listed in `.gitignore`191- API keys: Backend only — NEVER in frontend code192- Auth0 client secrets: Backend only193- Database passwords: Environment variables194- Third-party API keys: Backend only, use appropriate auth scheme (e.g., HTTP Basic, Bearer)195196### Configuration Pattern197```csharp198// appsettings.json — structure only, no values199"ExternalService": {200 "ApiKey": "", // Set via EXTERNALSERVICE__APIKEY env var201 "ApiSecret": "" // Set via EXTERNALSERVICE__APISECRET env var202}203```204205### Deployment206- Secrets set as platform-specific service variables (e.g., Railway, Azure, AWS)207- Never in Dockerfiles or docker-compose.yml production configs208- Use platform-native reference variables for cross-service secrets209210## Audit Trail211212For sensitive operations, log:213```csharp214public record AuditEntry(215 string Action, // "user.profile.updated", "goal.created", "deposit.initiated"216 string UserId,217 string ResourceType,218 string ResourceId,219 DateTimeOffset Timestamp,220 Dictionary<string, object?> Metadata // Changed fields, but NO PII values221);222```223224- Log WHO did WHAT to WHICH resource, WHEN225- Never log the actual PII values in audit entries226- Retain audit logs per regulatory requirements