Skip to content

Security Remediation Progress

Companion tracker for security-remediation-runbook.md. One line per task. Status is one of: done / unverified / skipped / blocked. β€œunverified” means the work may have happened but the runbook’s β€œDone when” criterion was not demonstrated. Never upgrade a line to β€œdone” without the stated evidence.

Working order (from the runbook’s Suggested order): A1 β†’ A2 β†’ A3 β†’ B1 β†’ B2 β†’ B3 β†’ E1 β†’ C1 β†’ C3 β†’ E2 β†’ E3 β†’ F3 β†’ then D1, C4, B4; Track G folds into the EC2 migration.

TaskStatusDateNote
A1in progress2026-09-05Passkeys enabled on Google Workspace (the reset-path root β€” highest-value single account). Remaining: Cloudflare (which is also the registrar β€” see A4, so double weight), GitHub, AWS (new IAM admin and the root login, which still has a password even with no access keys). Verify, don’t assume: enabling passkeys is not the same as removing the weak fallback β€” confirm 2SV enforcement is β€œOnly security key”, applied to the super-admin account, with device-trust off, or an attacker just uses the password+SMS path. Consider Google Advanced Protection Program for the super-admin (free, enforces phishing-resistant factors, closes the app-password escape hatch). Recovery codes on paper, off the laptop.
A2partial2026-09-05DMARC already published at p=quarantine (not none), pct=100, adkim=s, aspf=r, fo=1. SPF include:_spf.google.com include:_spf.resend.com ~all. resend._domainkey and google._domainkey both published at the apex, so strict DKIM alignment should pass for both senders. [email protected] resolves (Cloudflare NS + email-routing MX) so reports are being delivered. Remaining: read the aggregate reports to confirm both senders pass aligned, then move p=quarantine β†’ p=reject. Superseded in part by the domain change β€” the new domain needs SPF/DKIM/DMARC built from scratch, ramped none β†’ quarantine β†’ reject over a few weeks of aggregate reports, since a new domain has no sending reputation. Keep the gevell.com record at reject permanently.
A3deferred β†’ new domain2026-09-05Do NOT register lookalikes of gevell.com β€” the org is changing domains. Re-target to the new domain once chosen. (Checked anyway: theaccessib1e.org, theaccesible.org, theaccessible.net were unregistered; theaccessible.com parked on HugeDomains.)
A4mostly done2026-09-05Registrar is Cloudflare; clientTransferProhibited already set, so registrar lock is on. Expiry 2027-03-02. Consequence: the Cloudflare account IS the registrar account β€” hardening it in A1 covers both, and makes Cloudflare the highest-value remaining enrollment. Gaps: no CAA records (any CA may issue for gevell.com) and DNSSEC unsigned β€” both one-click on Cloudflare, low urgency. Re-do all of this on the new domain.
G1done2026-09-05AWS CI auth switched from static keys to GitHub OIDC (#2679, c74313e31). Removes long-lived AWS credentials from the self-hosted runner ahead of the EC2 migration β€” the main thing to get right while CI still shares a box with other services.
β€”done2026-09-05AWS root access keys removed, replaced with an IAM admin. Local CLI assumes AWSReservedSSO_AdministratorAccess via Identity Center (short-lived, scoped) instead of root keys. Found outside the runbook while investigating Textract; folded into Track A. Removes the worst case under #8 (stolen laptop) and unblocks the OIDC work in Track G.

Domain migration (decided 2026-09-05)

The org is moving off gevell.com. This reorders the runbook.

B1/B2 move ahead of A1. A domain change is the strongest possible pretext for invoice fraud (#21): you are about to send university AP offices a legitimate β€œour domain has changed” message, which makes a fraudulent β€œand our remittance details have changed” follow-up far more credible than it would ever be otherwise. Get the anti-fraud language into the invoice template and contract before announcing the change, and announce it to anyone with an open invoice through a channel that is not email.

Keep the old domain forever. gevell.com stays registered, locked, and auto-renewing indefinitely β€” not just through the transition. If it lapses, whoever picks it up receives mail as us, and every stale customer AP record, password-reset flow, and historical invoice pointing at it becomes theirs. Hold DMARC at p=reject on it permanently; drop MX or forward to a monitored box. Budget it as a permanent line item.

SAML is not a blocker β€” verified. tenant_sso_config, tenant_sso_logins, and sso_intake all hold zero rows; no SAML login has ever occurred. api-stack.ts pins PUBLIC_BASE_URL for the SP entity ID and warns that changing it fails every audience check, but with no IdP registered there is nothing to renegotiate. It becomes a one-line config change at cutover rather than a multi-week customer dependency. Re-check this before cutover if an SSO customer signs in the meantime.

Cutover surface (all under our own control): PUBLIC_BASE_URL, Resend sending domain + DKIM, Stripe webhook endpoints, OAuth/SSO redirect URIs, CORS origins, R2 signed-URL hosts, Cloudflare Workers routes, the @anglinai/ui registry config, and every *.gevell.com subdomain in the product registry (packages/accessible-org-ui/src/products.ts).

Findings

Record where a problem was found and what to do about it β€” never the value itself. This file is committed to git, so a leaked credential written down here is a leaked credential twice.

Format: <repo/path or system> β€” <credential type> β€” <rotated? y/n> β€” <date>. For a secret-scanning hit, the only facts needed are which credential, where it surfaced, and whether it has been rotated. Anything found in history is already leaked: rotate it, don’t just delete the commit.

(none yet)