Secrets, Config, and Security Headers
The vulnerabilities in this lesson don't require a clever attacker β a connection string committed to a repo, or a missing response header, is found by automated scanners within hours. These are the "hygiene" findings that dominate public-sector security assessments, and they're also the cheapest to fix.
Secrets never live in source
A secret is anything that grants access: connection strings, API keys, SMTP credentials, signing keys. The rule is absolute β no secret is ever committed to source control, because repos get cloned, forked, and backed up forever; rotating a leaked key is painful, scrubbing git history more so. Where secrets actually go:
- Local dev: User Secrets (
dotnet user-secrets) β values live in your profile, outside the repo, and layer overappsettings.jsonautomatically. - Deployed environments: environment variables or a vault (Azure Key Vault),
injected at deploy time. Non-secret per-environment values belong in
appsettings.{Environment}.json, which ships with the app.
Every source flows through the same IConfiguration layering, so code reads a name; the
value is injected per environment. (Legacy note: web.config transforms were the .NET
Framework version of this β don't carry them forward.) If git grep password finds anything,
you have work to do.
HTTPS and HSTS
All traffic is HTTPS, full stop β the login cookie from lesson one is a bearer token, and any
HTTP hop exposes it. Enforce it server-side, not by hoping: UseHttpsRedirection in the
middleware pipeline. Then add HSTS via UseHsts
(Strict-Transport-Security), which tells browsers to refuse plain HTTP for your domain β
closing the window where a user's first request goes out unencrypted. Start with a short
max-age and lengthen it once you're sure every subdomain really serves TLS.
Security headers: cheap armor
A handful of response headers disable entire attack classes. The core set:
X-Content-Type-Options: nosniffβ stops MIME-sniffing uploads into executable content.X-Frame-Options: DENY(orContent-Security-Policy: frame-ancestors) β blocks clickjacking.Content-Security-Policyβ whitelists where scripts/styles may load from; the strongest XSS backstop. Legacy apps full of inline<script>blocks (jQuery-era code, Kendo widget initializers) will need a staged rollout β useContent-Security-Policy-Report-Onlyfirst.- Remove the chatty ones:
Server,X-Powered-By,X-AspNet-Versionadvertise your patch level.
Set them in one small piece of middleware near the top of the pipeline (Kestrel already
omits Server by default when AddServerHeader is false; a reverse proxy like IIS or Nginx
may re-add its own). Verify with your browser's dev tools or an online header scanner.
Patching dependencies
Your app includes code you didn't write β NuGet packages, jQuery, Bootstrap, Kendo β and
scanners will flag known-vulnerable versions (OWASP A06). Make updating routine:
dotnet list package --vulnerable (or GitHub Dependabot) in CI, a scheduled cadence for
framework and JS-library bumps, and a documented owner. An unpatched five-year-old jQuery is a
finding you'll write a remediation plan for; a monthly patch habit means you never have to.