From b517217c1199acfa5510a3520d0b8b3ff7699702 Mon Sep 17 00:00:00 2001 From: Chris Gallagher Date: Fri, 4 Sep 2026 13:01:07 -0400 Subject: [PATCH] feat: use soc@appalachiancloud.co as the security.txt contact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the personal address with the estate's per-domain soc@ contact. This site is a subdomain and has no MX of its own, so it uses the parent apex's address rather than soc@edulab.appalachiancloud.co, which would not resolve. The address is served by the Google Workspace catch-all rather than a real mailbox — a standing dependency now recorded in AGENTS.md and the CHANGELOG, since switching Workspace to explicit recipients would bounce it silently on the one channel a reporter is told to use. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_012Y7PCTaciw6HtEC1eLL185 --- .well-known/security.txt | 2 +- AGENTS.md | 6 +++++- CHANGELOG.md | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.well-known/security.txt b/.well-known/security.txt index 094cd1e..774fd5a 100644 --- a/.well-known/security.txt +++ b/.well-known/security.txt @@ -5,7 +5,7 @@ # contact below. We aim to acknowledge reports within 5 business days. # Please do not run automated scans that degrade service for other users. -Contact: mailto:cgallagher@keengeek.com +Contact: mailto:soc@appalachiancloud.co Expires: 2027-08-31T00:00:00.000Z Preferred-Languages: en Canonical: https://edulab.appalachiancloud.co/.well-known/security.txt diff --git a/AGENTS.md b/AGENTS.md index 647db3a..fb3e85f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,11 @@ - Unlike Boss BOS, edu-lab uses **AES256** (not KMS) with no origin-request policy and no `aws:RequestedRegion` condition, so it does not need those fixes. - **Asset caching / cache-busting (#16, fixed):** CSS/JS are uploaded `immutable, max-age=1yr` on **unversioned filenames**, so CloudFront's `/*` invalidation alone does *not* refresh **returning browsers**. The deploy fixes this by rewriting the HTML pages (`index.html` + `contact.html`, a loop in `ci.yml`'s "Sync static site to S3" step) at upload time to append `?v=` (`${GITHUB_SHA::8}`) to each local `.css`/`.js` ref; the HTML pages themselves stay `no-cache`. **Do not** reference CSS/JS from the HTML in a way the `sed` can't version (keep refs as bare local `name.css`/`name.js`), don't drop the versioning step — without it, CSS/JS changes are invisible to repeat visitors until hard-refresh — and **any new top-level `.html` page must be added to both the sync `--exclude` list and that loop**. - **S3 sync is a whole-repo publish — the `--exclude` list is the ONLY thing keeping repo internals off the public site.** The "Sync static site to S3" step runs `aws s3 sync . s3://…`, so *anything* not excluded (or not caught by the trailing `index.html` special-case) is served at the site root. The exclude list must cover every tooling/internal directory — `.git/*`, `.github/*`, `.claude/*`, `terraform/*`, `tests/*`, `node_modules/*`, plus `*.md`/`LICENSE`/`.gitignore`. `.claude/*` was **missing** and briefly exposed `/.claude/settings.json` + `/.claude/hooks/guardrail.py`; adding it plus the existing `--delete` purges those objects on the next `main` deploy. **When you add a new top-level dir that shouldn't ship, add its `--exclude` in the same PR.** -- **`security.txt` is the one path deliberately excluded from the sync *and* still published (RFC 9116).** `.well-known/security.txt` is in the sync's `--exclude` list yet is uploaded by its own `aws s3 cp` right after, with `--content-type "text/plain; charset=utf-8"` and `--cache-control "public, max-age=3600"`. Two reasons it cannot just ride the sync: the sync stamps `immutable, max-age=1yr`, and the file carries an **`Expires` date (currently `2027-08-31T00:00:00.000Z`) that must be refreshed before it lapses** — an expired security.txt is treated as invalid by scanners, and a year-long immutable cache would outlive the fix. **Renew it before 2027-08-31**, in lockstep with the copies on the other estate domains (matrix + shared date live in `mountain-infra/AGENTS.md`). Before this existed the path returned CloudFront's SPA fallback — HTTP 200 with `index.html` — so **verifying it means asserting `text/plain` and a `Contact:` line, not just a 200**. +- **`security.txt` is the one path deliberately excluded from the sync *and* still published (RFC 9116).** `.well-known/security.txt` is in the sync's `--exclude` list yet is uploaded by its own `aws s3 cp` right after, with `--content-type "text/plain; charset=utf-8"` and `--cache-control "public, max-age=3600"`. Two reasons it cannot just ride the sync: the sync stamps `immutable, max-age=1yr`, and the file carries an **`Expires` date (currently `2027-08-31T00:00:00.000Z`) that must be refreshed before it lapses** — an expired security.txt is treated as invalid by scanners, and a year-long immutable cache would outlive the fix. The `Contact:` is **`soc@appalachiancloud.co`** — this is a subdomain, so it uses the +parent apex's address (the subdomain carries no MX of its own), and that address +resolves through the Google Workspace **catch-all** rather than a real mailbox; if +Workspace ever moves to explicit recipients it bounces silently. **Renew it before +2027-08-31**, in lockstep with the copies on the other estate domains (matrix + shared date live in `mountain-infra/AGENTS.md`). Before this existed the path returned CloudFront's SPA fallback — HTTP 200 with `index.html` — so **verifying it means asserting `text/plain` and a `Contact:` line, not just a 200**. - **⚠️ `enable_custom_domain` MUST stay `true` (fixed 2026-07-06):** this two-phase toggle gates the CloudFront custom cert + alias + `TLSv1.2_2021`. It now **defaults to `true`** — the launched steady state. It was previously `false`, and because there is **no committed `terraform.tfvars`**, every `terraform plan/apply` silently wanted to revert the live site to the default `*.cloudfront.net` cert + `TLSv1` + no alias (i.e. take HTTPS down on `edulab.appalachiancloud.co`). The launch had set it `true` only *transiently* (a `-var`), which never persisted. **Guardrail: never hold a persistent infra state in a transient `-var` or an un-committed tfvars — bake the steady state into the committed default.** Only flip to `false` for a genuine from-scratch re-bootstrap (ACM cert not yet Issued). ## 📝 Documentation & change discipline diff --git a/CHANGELOG.md b/CHANGELOG.md index 381f20b..d9f8bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ Format loosely follows [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] +### Changed +- **`security.txt` contact is now `soc@appalachiancloud.co`** (was a personal + address). This site is a **subdomain**, so it uses the parent apex's address — + `edulab.appalachiancloud.co` has no MX of its own. The address resolves via the + Google Workspace catch-all rather than a real mailbox, so it depends on that + catch-all staying in place; see the estate-wide note in `mountain-infra/AGENTS.md`. + ### Added - **`security.txt` (RFC 9116)** — `.well-known/security.txt` now publishes a security contact for `edulab.appalachiancloud.co`. Before this the path