Security
This document defines the security baseline requirements for all projects following these standards. These rules are non-negotiable. Violations MUST be caught in code review before any PR is merged.
For the OWASP ASVS / API Top 10 control catalog, enforcement mapping, and CI gate checklist, see docs/conventions/shared/security-controls.md. Object-level authorization tests: docs/conventions/backend/object-authorization.md.
Agent Quick Rules {#agent-quick-rules}
- MUST NOT commit secrets, credentials, or real connection strings to source control.
- MUST validate all external input at the application boundary before Domain.
- Actor identity MUST come from JWT claims; MUST NOT accept actor IDs from request bodies for the authenticated user.
- MUST use parameterized queries; MUST NOT concatenate SQL strings.
- MUST use
IOptions<T>with validation; MUST NOT read rawconfiguration["Key"]. - Rate limiting and CORS MUST be configured explicitly per environment.
Full convention: docs/conventions/shared/security.md
1. Never Commit Secrets
No API keys, connection strings, passwords, tokens, or credentials of any kind MUST appear in source code, committed configuration files, or version history.
Acceptable approaches:
- Environment variables injected at runtime (server
.env, Docker Composeenv_file) - GitHub Actions secrets for CI and deploy pipelines
- Encrypted secrets on the server (for example SOPS, age, or provider-specific secret tooling)
- A
.envfile that is listed in.gitignoreand never committed
If a secret is accidentally committed: Rotate the secret immediately. Do not rely on rewriting git history as the primary mitigation; assume the secret is compromised the moment it appears in any commit.
The .gitignore for every project MUST include .env, *.pfx, *.p12, appsettings.Development.json (if it contains real secrets), and any other file that could contain credentials.
2. Input Validation
All external input (HTTP request bodies, query parameters, route values, file uploads) MUST be validated at the application boundary before it reaches the Domain layer.
Validation in the Application layer (via ICommandValidator and IQueryValidator) is the primary line of defense. The API layer MUST NOT forward unvalidated input directly to the domain.
Never trust input because it comes from an internal frontend. Validate every HTTP request as if it could come from any source.
3. SQL Injection
Always use parameterized queries. EF Core LINQ queries are parameterized by default and are safe. Raw SQL queries using FormattableString (via FromSqlInterpolated) are also safe because EF Core parameterizes the interpolated values.
// GOOD: EF Core LINQ - parameterized automaticallyvar post = await dbContext.Posts .Where(p => p.AuthorId == authorId) .FirstOrDefaultAsync(cancellationToken);
// GOOD: FormattableString raw SQL - EF Core parameterizes automaticallyvar post = await dbContext.Posts .FromSqlInterpolated($"SELECT * FROM \"Posts\" WHERE \"AuthorId\" = {authorId.Value}") .FirstOrDefaultAsync(cancellationToken);
// BAD: string concatenation - SQL injection vulnerabilityvar post = await dbContext.Posts .FromSqlRaw($"SELECT * FROM \"Posts\" WHERE \"AuthorId\" = '{authorId.Value}'") .FirstOrDefaultAsync(cancellationToken);Never use FromSqlRaw with string interpolation. Only use FromSqlRaw with explicitly named parameters (@param) when FromSqlInterpolated is not an option.
4. Authentication and Authorization
Authentication is handled by ASP.NET Core middleware. Do not implement custom authentication logic inside endpoints.
- Every endpoint that handles protected data MUST call
.RequireAuthorization(). - Endpoints that are intentionally public MUST call
.AllowAnonymous()explicitly to make the intent clear. - Authorization policy names MUST be defined as
const stringfields in a centralAuthorizationPoliciesstatic class, not as inline magic strings.
Policy constant names are defined in docs/conventions/backend/authentication-and-authorization.md (RequireAuthenticatedUser, RequireAdminRole). Do not redefine them here.
// GOOD:app.MapPost("/posts", HandleAsync) .RequireAuthorization(AuthorizationPolicies.RequireAuthenticatedUser);
// BAD:app.MapPost("/posts", HandleAsync) .RequireAuthorization("RequireAuthenticatedUser"); // BAD: magic string5. Rate Limiting
Public endpoints, authentication endpoints, file uploads, search endpoints, expensive commands, and webhooks MUST have rate limiting. Use named policies from docs/blueprints/backend/program-cs.md (RateLimitPolicies.AuthenticatedApi) and apply with .RequireRateLimiting() on route groups.
// GOOD: named policy applied to a route groupbuilder.Services.AddRateLimiter(options =>{ options.AddFixedWindowLimiter("authenticated-api", limiter => { limiter.PermitLimit = 120; limiter.Window = TimeSpan.FromMinutes(1); limiter.QueueLimit = 0; });});
app.UseRateLimiter();
app.MapGroup("/api") .RequireAuthorization() .RequireRateLimiting("authenticated-api");// BAD: public search endpoint has no limiterapp.MapGet("/search", HandleAsync);Rate limiting policies MUST be load tested before production.
6. CORS
CORS is not an authorization mechanism. It relaxes browser same-origin rules and MUST be configured narrowly.
- Allow only known frontend origins per environment.
- Do not use
AllowAnyOrigin()with credentials. - Define policy names as constants.
- Apply CORS before authorization in the ASP.NET Core middleware order.
// GOOD: explicit originsbuilder.Services.AddCors(options =>{ options.AddPolicy("Frontend", policy => { policy .WithOrigins("https://app.example.com") .AllowAnyHeader() .AllowAnyMethod(); });});// BAD: any origin with credentialspolicy.AllowAnyOrigin() .AllowCredentials();7. Content Security Policy
Next.js projects MUST define a Content Security Policy before production. Copy the nonce-based implementation from docs/blueprints/frontend/csp-headers.md. Use nonce-based CSP for applications that handle sensitive data or strict compliance requirements. Nonce-based CSP forces dynamic rendering and disables static optimization for affected routes, so document that trade-off in the project ADR.
At minimum, production CSP MUST include:
default-src 'self'object-src 'none'base-uri 'self'form-action 'self'frame-ancestors 'none'
Third-party script, image, analytics, and monitoring domains must be explicitly listed.
8. Audit Logging
Security-sensitive business actions MUST emit audit events. Audit events are not normal logs and MUST be retained according to the project data policy.
Audit events include:
- Authentication and authorization changes.
- Role, permission, tenant, or team membership changes.
- Changes to externally visible business state.
- Data export.
- Administrative impersonation.
- Payment, billing, and compliance actions.
Audit records MUST include actor, action, target, timestamp, correlation ID, and tenant scope when applicable. Do not store secrets in audit records.
9. PII and Data Classification
Every production project MUST classify data before launch.
| Class | Examples | Rule |
|---|---|---|
| Public | Published content, public metadata | May appear in logs when needed |
| Internal | Non-sensitive operational metadata | Limit to authenticated users |
| Confidential | Names, email addresses, phone numbers | Do not log by default |
| Restricted | Secrets, tokens, payment data, credentials | Never log or expose to client code |
Frontend server components and Server Actions MUST return minimal DTOs to client components. Do not pass full user, tenant, or permission objects to client components.
10. Secrets Rotation
Projects MUST document how each production secret is rotated. Rotation must include:
- Owner.
- Storage location.
- Rotation frequency.
- Rollback plan.
- How dependent services pick up the new value.
Secrets used for signing or encryption need key versioning so old data or sessions can be read during rotation.
11. Dependency Scanning
Run dotnet list package --vulnerable as part of the CI pipeline on every pull request. Any PR that introduces a vulnerable package MUST either:
- Update to a patched version, or
- Include a documented exception in the PR description explaining why the vulnerability is not exploitable in this context and when it will be resolved.
Do not merge PRs with known vulnerable dependencies without an explicit decision.
Frontend projects MUST also run pnpm audit in CI. For high-severity supply-chain advisories, pin the lockfile to a known safe version and document the advisory in the PR.
12. OWASP Top 10
All engineers working on security-sensitive changes MUST review the OWASP Top 10. The rules in this document address the most common of these risks, but they are not exhaustive.
When designing a feature that handles sensitive data, authentication, or authorization, consult the OWASP guidelines for the relevant risk category before starting implementation.