How csoh.org Is Secured

A security community should be auditable. This page lays out - with specifics you can verify yourself - exactly how this site is secured: a strict Content-Security-Policy, Subresource Integrity on every asset, HSTS and Cross-Origin isolation, keyless deploys with no stored cloud secrets, and a Trivy-scanned pipeline. Every claim links to the control that enforces it.

· · Security engineering · View source on GitHub

Why this page exists. csoh.org is a community for cloud security practitioners, so the site itself ought to hold up to the same scrutiny we apply to everyone else. This page documents how it's secured and, more usefully, tells you how to check each claim without taking our word for it. If you find something wrong, the disclosure section at the bottom tells you how to report it.

The short version. csoh.org is a static site - no server-side code, no database, no user accounts, no third-party JavaScript that can execute. That erases whole classes of vulnerability by construction. On top of that we run a deliberately strict set of browser and transport controls, sign every asset, and deploy through a pipeline that holds no long-lived cloud credentials.

On this page

  1. The biggest control: it's a static site
  2. Content-Security-Policy and no inline scripts
  3. The rest of the HTTP security headers
  4. Subresource Integrity on every asset
  5. Analytics without tracking
  6. Supply chain: dependencies and the build
  7. Keyless deploys: no stored cloud secrets
  8. The CI security gates
  9. Reporting a vulnerability
  10. Verify all of this yourself

The biggest control: it's a static site

The single most important security decision here was made before any header was set: csoh.org is static HTML. There is no application server, no database, no login, no session, no user-supplied input that reaches a backend. A visitor's browser downloads files and renders them; nothing they send is executed anywhere.

That one property removes most of the OWASP Top 10 by construction - there is no SQL to inject, no server-side deserialization, no authentication to bypass, no session to hijack, no SSRF surface. What's left to defend is narrower and well understood: the integrity of the files we ship, the browser's handling of them, and the pipeline that publishes them. The rest of this page is about those three things.

Content-Security-Policy and no inline scripts

The attack this stops: cross-site scripting (XSS) - getting the browser to run JavaScript it shouldn't, whether injected into the page or loaded from a hostile third party.

Every response carries a strict Content-Security-Policy. Scripts, styles, and fonts may load only from our own origin; there is no 'unsafe-inline' and no 'unsafe-eval'. Objects and plugins are banned outright, framing is denied, and the base URI and form targets are pinned to ourselves:

default-src 'self'; script-src 'self'; style-src 'self';
object-src 'none'; base-uri 'self'; form-action 'self';
frame-ancestors 'none'

Because script-src is 'self' with no inline allowance, an injected <script> simply does not run - the browser refuses it. To keep that guarantee honest, a CI gate (below) rejects any inline <script> block in the source, so we can never accidentally come to rely on one. The only executable script on the whole site is our own main.js, served from our origin and integrity-checked.

The rest of the HTTP security headers

Alongside the CSP, every response sets a full complement of hardening headers. Each one closes a specific hole:

These are the same headers a scanner grades - see the verify-it-yourself section to run one.

Subresource Integrity on every asset

The attack this stops: a tampered or swapped asset - if our stylesheet or script were ever modified in transit or at an origin, the browser should refuse it rather than run it.

Every shared asset (style.css, main.js, and the vendored analytics script) is referenced with a Subresource Integrity hash - a SHA-384 fingerprint in the integrity attribute. If the delivered bytes don't match the fingerprint the page declares, the browser will not use the file. A tooling script recomputes those hashes whenever an asset changes and stamps them across every page, and a mismatch fails loudly rather than silently.

Analytics without tracking

There is exactly one third-party script on the site: a cookieless analytics counter (GoatCounter). It sets no cookies, collects no personal data, and is the only host the CSP's connect-src allows besides ourselves. Before it can even load, our own main.js checks for a Do Not Track or Global Privacy Control signal and opts the visitor out entirely if one is set. No ad networks, no tag managers, no session recorders - none of the usual third-party JavaScript that turns a "simple" site into a supply-chain liability.

Supply chain: dependencies and the build

The attack this stops: a compromised dependency or build step slipping malicious code into what we publish.

The runtime dependency surface is close to zero - the site ships hand-written HTML, one stylesheet, and one script, with no client-side framework and no npm build. The automation that maintains the site (link checking, news updates, SEO audits) runs as GitHub Actions, and every third-party action is pinned to a full commit SHA, not a moving tag, so a hijacked tag can't swap in new code. The one place we build a container image (for the GCP origin) runs a Trivy vulnerability scan as a gate - the deploy to that cloud does not proceed if the scan finds an unacceptable issue.

Keyless deploys: no stored cloud secrets

The attack this stops: theft of long-lived cloud credentials from the repo or CI - the root cause of a long line of real breaches.

csoh.org is served active/active from AWS, GCP, and Azure, and the pipeline deploys to all three without storing a single cloud password. Each deploy uses OIDC: GitHub mints a short-lived, signed token that proves "this is the csoh.org repo's main branch," and each cloud is configured to trust that token for a narrowly-scoped role. There are no access keys, service-account JSON files, or client secrets in the repository or in CI, so a leaked repo yields no cloud access. The full architecture is its own write-up: How We Deploy Across AWS, GCP & Azure.

The CI security gates

Several checks run on every change and block the merge or deploy if they fail, so the guarantees above can't quietly erode:

All of these run read-only against the code; none can modify the site or post on our behalf. The workflows are in the public repo if you want to read them.

Reporting a vulnerability

We publish a machine-readable security.txt (per RFC 9116) and a human-readable Security Policy. If you find a vulnerability on csoh.org, email admin@csoh.org or raise it at a Friday session. We take reports seriously - it would be a strange thing for a cloud-security community not to.

Verify all of this yourself

None of the above should be taken on faith. Here's how to check it in a few minutes:

If any of it doesn't match what this page claims, that's a bug - please tell us.

Explore how the site is built

This page is one of a set written in the open. See How We Deploy Across AWS, GCP & Azure for the multi-cloud architecture, How We Use GitHub Actions for the automation, and How CSOH Is Funded for the money side.