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
- The biggest control: it's a static site
- Content-Security-Policy and no inline scripts
- The rest of the HTTP security headers
- Subresource Integrity on every asset
- Analytics without tracking
- Supply chain: dependencies and the build
- Keyless deploys: no stored cloud secrets
- The CI security gates
- Reporting a vulnerability
- 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:
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:
- HSTS (
max-age=31536000; includeSubDomains; preload) - forces HTTPS for a year and is on the browser preload list, so a downgrade or SSL-strip attack never gets a plaintext first request. - X-Content-Type-Options: nosniff - stops the browser from second-guessing a file's declared type, defeating MIME-confusion attacks.
- X-Frame-Options: DENY (backed by CSP
frame-ancestors 'none') - the site cannot be embedded in an iframe, so clickjacking has nothing to work with. - Referrer-Policy: strict-origin-when-cross-origin - never leaks full URLs or query strings to other sites.
- Permissions-Policy - explicitly disables camera, microphone, geolocation, payment, USB, and the motion sensors. A static site needs none of them, so we turn them all off.
- Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Resource-Policy: same-origin - isolate our browsing context and resources from other origins, shrinking the surface for cross-origin side-channel attacks.
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:
- No inline scripts - rejects any executable inline
<script>, keeping the strict CSP enforceable. - URL safety - every outbound link is checked so we don't ship a link to a hijacked or malicious domain.
- HTML validation - the W3C validator plus checks for structured-data (JSON-LD) validity and image dimensions.
- Workflow, YAML, and Python lint - actionlint (with shellcheck), yamllint, and ruff catch mistakes in the automation itself before it runs.
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:
- Grade the headers: run csoh.org through securityheaders.com and Mozilla Observatory.
- Read the headers directly:
curl -sI https://csoh.organd look for the CSP, HSTS, X-Frame-Options, Permissions-Policy, and Cross-Origin lines. - Confirm the CSP blocks inline script: open the browser console on any page and try to inject a
<script>- you get a CSP violation instead of execution. - Check SRI: view source and confirm every shared-asset
<link>/<script>carries anintegrity="sha384-..."attribute. - Check HSTS preload status: look up csoh.org on hstspreload.org.
- Read the config: the headers, the deploy workflow, and the CI gates are all in the public repository.
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.