Wurdey.com
← Back to blog

security

Application Security 101: Headers, CSP, and TLS

Most application breaches don't start with some cinematic zero-day. They start with a missing header, an expired patch, or a TLS configuration that still speaks a protocol nobody should have used in a decade. The good news: the controls that stop the majority of real-world attacks are well understood, and you can layer them in without rewriting your app. This guide walks through the essentials, what each one actually defends against, and where the common mistakes hide.

Start with HTTPS — and then enforce it

Serving your app over HTTPS encrypts traffic between the browser and your server so nobody on the network path can read or tamper with it. That part most teams get right. The part they miss is enforcement. If your site answers on both http:// and https://, an attacker on a shared network can intercept the first plaintext request and downgrade the connection before the redirect ever happens.

The fix is HSTS (HTTP Strict Transport Security). It's a single response header that tells browsers to only ever connect over HTTPS for a set period, even if a user types the bare domain:

  • Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Once a browser has seen that header, it refuses to make an insecure request to your domain at all. Adding your domain to the browser preload list extends that protection to the very first visit, before any header has been seen.

Test before you preload

HSTS is sticky by design. If you set a long max-age with includeSubDomains and then discover a subdomain that can't do HTTPS, you can't quickly undo it — browsers honor the cached policy until it expires. Roll out with a short max-age first, confirm every subdomain works, then raise it.

Content-Security-Policy: your anti-XSS seatbelt

Content-Security-Policy (CSP) is the single most valuable header for defending against cross-site scripting (XSS) — the class of attack where an attacker gets their JavaScript to run inside your users' browsers. CSP works by telling the browser exactly which sources of scripts, styles, images, and connections are allowed. Anything not on the list simply doesn't execute or load.

A basic policy might look like this:

  • default-src 'self' — only load resources from your own origin by default
  • script-src 'self' — no inline scripts, no third-party scripts unless explicitly allowed
  • object-src 'none' — kill legacy plugin vectors like Flash outright
  • frame-ancestors 'self' — control who can embed your app in a frame

The reason CSP matters so much: even if an attacker finds a way to inject a <script> tag through a comment field or a URL parameter, a strict policy means the browser refuses to run it. It turns a critical vulnerability into a non-event.

CSP has a learning curve because it will break things you didn't realize depended on inline scripts or external domains. Deploy it in Content-Security-Policy-Report-Only mode first. That sends violation reports without blocking anything, so you can see what would break and tighten the policy safely before you enforce it.

The supporting cast of security headers

A handful of smaller headers each close a specific, well-known gap. None are hard to add, and together they meaningfully raise the cost of attacking your app.

X-Frame-Options

Defends against clickjacking — an attack that loads your site invisibly inside an attacker's page and tricks users into clicking things they can't see. Set X-Frame-Options: DENY (or use the modern frame-ancestors directive in your CSP, which supersedes it).

X-Content-Type-Options

X-Content-Type-Options: nosniff stops browsers from guessing (sniffing) the content type of a response. Without it, a file you serve as plain text could be reinterpreted as executable JavaScript. One line, meaningful protection.

Referrer-Policy

Controls how much of your URLs leak to other sites when a user clicks an outbound link. Referrer-Policy: strict-origin-when-cross-origin is a sensible default that prevents session tokens or private path data embedded in URLs from bleeding into third-party analytics and ad networks.

Permissions-Policy

Lets you explicitly disable browser features your app doesn't use — camera, microphone, geolocation, USB. If a page is ever compromised, the attacker can't reach hardware you've already turned off. For example: Permissions-Policy: camera=(), microphone=(), geolocation=().

Verify, don't assume

It's easy to set a header in config and never confirm it's actually being sent — a caching layer, CDN, or reverse proxy can strip or override it. Check the live response with your browser's network tab or a headers scanner after every deploy, not just once.

Patch the things you didn't write

Your application code is only part of the attack surface. The rest is everything it depends on: the web server, the runtime, the operating system, and the long list of open-source libraries in your dependency tree. When a CVE (a publicly disclosed vulnerability) is announced for any of them, attackers begin scanning the internet for unpatched instances within hours.

Staying ahead of this comes down to a few disciplined habits:

  1. Inventory your dependencies. You can't patch what you don't know you're running. Use lockfiles and automated dependency scanning to keep a live list.
  2. Subscribe to advisories for your stack — server, database, framework, and major libraries — so you hear about critical CVEs when they drop, not weeks later.
  3. Patch on a schedule, and out-of-band for criticals. Routine updates monthly; anything rated critical gets applied immediately.
  4. Test patches before production so an urgent security fix doesn't take your app down in a different way.

The uncomfortable truth is that most successful breaches exploit vulnerabilities for which a patch already existed. Patching isn't glamorous, but it's the highest-return security work most teams can do.

Kill weak TLS ciphers and dead protocols

Having HTTPS enabled doesn't mean your encryption is strong. Servers often still support ancient protocols like SSLv3, TLS 1.0, and TLS 1.1, along with weak cipher suites, purely for backward compatibility. Those old protocols have known, exploitable weaknesses that allow attackers to decrypt or downgrade supposedly secure connections.

Modern practice is straightforward:

  • Support TLS 1.2 and TLS 1.3 only; disable everything older.
  • Remove ciphers using RC4, 3DES, or other broken algorithms.
  • Prefer suites that provide forward secrecy, so a future key compromise can't retroactively decrypt past traffic.

You don't have to guess whether you got it right. Free scanners grade your TLS configuration and flag every weak protocol and cipher still enabled, giving you a clear checklist to work through.

Defense in depth is the whole point

No single control here is a silver bullet. HSTS, CSP, the header set, disciplined patching, and strong TLS overlap deliberately — so that when one layer fails or a new bug appears, the others still stand between an attacker and your users' data.

Where this connects to the rest of your security posture

These controls apply to any real application, but they don't replace the fundamentals of keeping a platform healthy. If you run on WordPress, the same principles show up as hardened headers, current core and plugins, and a locked-down TLS config — all part of solid WordPress security and ongoing maintenance. And if a site does get compromised despite good hygiene, fast malware removal and hacked-site cleanup are what limit the damage.

Key takeaways
  • HTTPS plus HSTS — encryption isn't enough; enforce it so connections can't be downgraded to plaintext.
  • Content-Security-Policy is your strongest defense against XSS; roll it out in report-only mode first.
  • The core header set — X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy — each closes a specific, well-known gap for almost no effort.
  • Patch dependencies and servers on a schedule; most breaches exploit CVEs that already had a fix available.
  • Disable old TLS protocols and weak ciphers, and verify with a free scanner rather than assuming.

At Wurdey, we treat these controls as baseline configuration, not add-ons. Whether we're maintaining a WordPress site or building and hosting a custom application, we set strict headers and CSP, enforce HTTPS with HSTS, keep dependencies and servers patched, and harden TLS from day one — then monitor to make sure it stays that way. If you'd like a second set of eyes on your application's security posture, get in touch and we'll take a look.

Next step

Find out what a scanner sees on your site.

Run a free, non-invasive scan in about a minute. You'll get a plain-English security grade and the fixes that matter, no signup required.