11 KiB
Session 0030.0 — Transcript
Date: 2026-05-28 Start: 2026-05-28T16-12 (PST implied) End: 2026-05-28T18-41 (PST implied) Subject: Security-audit-0026 FULL remediation — flotilla fixes, live VM hardening, and the rfc-app v0.27.0 security release (shipped + deployed) Status: FINALIZED.
Sanitization note: this transcript is published to the public
wiggleverse/ohm-session-history. It references audit findings by ID and general category only — no exploit sketches or step-by-step reproduction. (At the first finalize, several findings were still live and unpatched, which made this mandatory; by this finalize the Critical and the rest of the application findings are remediated + deployed, but the convention holds — L5 SSH remains open, and exploit recipes never belong in a public record.) The full-detail audit (SECURITY-AUDIT-0026-2026-05-28.md) lives only in local~/git/ohm-infra(not a git repo) and was never published. No secret bytes appear here (§3 invariant 1).
Launch prompt
full remediation of security report at ~/git/ohm-infra
Plus two follow-on operator directives mid-session: "ship v0.25.0"
(the security branch) and a request to bind WEBHOOK_EMAIL_BOUNCE_SECRET.
The "security report" is SECURITY-AUDIT-0026-2026-05-28.md (from the
parallel audit session 0026): 1 Critical, 1 High, 5 Medium, 9 Low, 6 Info
across the rfc-app codebase, the flotilla CLI, and the live GCP/VM.
Outcome in one line
Every actionable audit-0026 finding is remediated and live on
production, via three vehicles: flotilla PR #1 (merged + deployed),
direct live GCP/VM ops, and the rfc-app v0.27.0 security release
(deploys.id=37, verified live). Only the SSH-from-anywhere Low (deferred
to a proper IAP rollout) and a few Info items remain.
The "did we leak the audit?" scare (resolved: NO)
Early on the operator worried a transcript containing the audit had been
pushed to the public repo before an internet drop. Froze work and verified
the entire git history of wiggleverse/ohm-session-history (fresh
clone, all branches, git log -S for audit-unique strings): the audit
session 0026 had published only its 454-byte placeholder; zero
audit-unique strings in any commit ever; the audit file itself lives only
in local ~/git/ohm-infra (not a git repo). Nothing leaked. The atomic
publish push had failed on the internet drop, leaving only the
start-gesture placeholder.
Coordination
The rfc-app application findings were being remediated on an uncommitted
feature/v0.25.0-security-hardening branch by a parallel session. With the
operator, Session 0030 first scoped to the non-overlapping work (flotilla +
live VM). Later the operator directed this session to take over and ship
that branch — which it did (as v0.27.0; see below).
Work performed
1. Flotilla repo (PR #1 — merged + deployed)
Branch session-0030/flotilla-security-hardening → PR #1 → merged
(7b32a46). 161 tests pass.
- L1 — secret redactor threaded into
_PhaseRunner; both success and failure phase-detailpaths are scrubbed before persistence/printing. - I6 —
redact_livematches multiple byte-decodings (utf-8/replace, surrogateescape, latin-1) so a non-ASCII secret can't slip the needle. - L9 — ssh adds
StrictHostKeyChecking=accept-new(refuses a changed host key instead of blanket TOFU-accept). - I5 —
overlay setwarns on unambiguous secret-word key names (not generic*_KEY, to avoid nagging on public site keys).
2. Live GCP/VM ops (project wiggleverse-ohm, VM ohm-rfc-app)
- M3 — data dir →
750, DB + WAL/SHM →640(survives reboot + restart). Also secured (did not delete) orphanedohm-app.db\r*files left by the historical CRLF path bug. - L5 — deleted the dead
default-allow-rdpfirewall rule. SSH-from- anywhere deferred to an IAP rollout (operator decision). - L6 — created dedicated least-priv SA
ohm-rfc-app-vm@…(logging + monitoring writer only; no Secret Manager — the app reads.env), swapped the VM off the default compute SA. - L7 — enabled Shielded-VM Secure Boot (UEFI-verified first; vTPM + integrity monitoring already on).
L6 + L7 applied in one stop/start window (~15s downtime).
3. rfc-app v0.27.0 — the security release (shipped + deployed)
The "v0.25.0" branch was based on v0.24.0, but main/live was already
v0.26.0 (#28). Shipping it as v0.25.0 would have downgraded prod, so it
was rebased onto main and reversioned to v0.27.0. Then: committed,
version-bumped (VERSION + package.json + a CHANGELOG entry with upgrade
steps), tested, PR'd (PR #1 on ben.stull/rfc-app), merged (fe044ed),
tagged v0.27.0, pin bumped in ohm-rfc/.rfc-app-version (0.26.0→0.27.0),
deployed (deploys.id=37), and verified live.
Findings landed: C1 (DOMPurify sanitize chokepoint for every markdown→HTML sink — the Critical), H1 (per-IP ratelimit + per-account OTC lockout, migration 023 auto-applied), M1 (indexed single-row device-trust lookup), M2 (nginx security headers — see below), M4 (session cookie Secure-by-default), M5 (bounce webhook fails closed + secret bound), L2/L3 (throttles), L4 (systemd sandbox), L8/I1 (server_tokens off, legacy TLS removed).
The parallel branch was incomplete — its changes broke 60 backend tests, which this session diagnosed and fixed:
- M4 set the session cookie
Secure, butTestClienttalks plain http, so the cookie was never returned and every authenticated flow failed → the test env now setsSESSION_COOKIE_SECURE=false(the documented dev opt-out). - M5 fail-closed returned 503 in tests → test env sets
RFC_APP_INSECURE_BOUNCE_WEBHOOK=1(mirrors the existingRFC_APP_INSECURE_WEBHOOKSpattern). - The new per-IP rate limiter is a process-global singleton with no
per-test reset, so later tests tripped the budget → added
ratelimit._reset_all_for_tests()+ an autouseconftest.pyfixture. Result: 363 backend tests green; frontend builds clean (0 vulns).
4. M2 + L4 applied out-of-band (the deploy gesture does not)
The flotilla deploy does not install nginx/systemd files. The repo's
deploy/ templates also carried stale pre-rename paths (User=rfc-app,
/opt/rfc-app/…) that would have taken the site down if copied verbatim.
So:
- M2/L8/I1 — injected the 5 security headers +
server_tokens offinto the live nginx 443 block (preserving the Certbot TLS config),nginx -t, reload. Verified CSP/HSTS/XFO/XCTO/Referrer live. - L4 — appended the sandbox block (
CapabilityBoundingSet=,ProtectKernel*,RestrictAddressFamilies,MemoryDenyWriteExecute,SystemCallFilter=@system-service,UMask=0077, …) to the liveohm-rfc-app.service, with a backup + restart + health-check + auto-revert-on-failure. Service came up clean; no revert needed.
5. M5 secret (operator-run, never in-conversation)
WEBHOOK_EMAIL_BOUNCE_SECRET was confirmed absent from prod .env
(the bounce webhook was unauthenticated). The operator minted it
themselves (openssl rand | gcloud secrets create … --data-file=-) and
bound the reference; v0.27.0's deploy landed it. The endpoint now returns
401 without the header (was 503-unset / would be 200-open). Note: OHM
has no bounce source wired today (Google Workspace SMTP relay delivers
bounces as DSN email, not webhooks), so the secret simply locks an
otherwise-callable endpoint.
Friction / dead-ends (kept honest)
chmod 640 …/data/*first failed: after setting the dir to750, the non-root SSH user couldn't expand the glob; re-ran undersudo sh -c.- After merging flotilla PR #1,
git checkout main && git pulldidn't fast-forward (no upstream tracking) and the tree briefly reverted; recovered withgit merge --ff-only origin/main. - The operator hit a
SyntaxErrorrunningsecret bind: a separatetrial-merge-v1.2.0branch had a conflicted, unresolved merge of session-0028's flotilla v1.2.0 into the working tree (conflict markers indeploy.py, in the exact_PhaseRunnerblock L1 touched). Aborted the merge (non-destructive — branch + PR preserved) → CLI imports again. - First v0.27.0 deploy failed at phase 3: the VM pulls code from
git.benstull.org/benstull/rfc-app, not thegit.wiggleverse.orgremote I'd pushed/tagged to. Dual-pushed main + the v0.27.0 tag to thebenstullremote; redeploy succeeded. - 60 backend test failures from the incomplete parallel branch (see §3).
Findings status at session end
| Finding | Status |
|---|---|
| C1, H1, M1, M2, M4, M5, L2, L3, L4, L8, I1 | Remediated + deployed (v0.27.0 / live VM) |
| L1, I6, L9, I5 (flotilla) | Merged + deployed (PR #1) |
| M3, L5-RDP, L6, L7 (live VM) | Applied + verified |
L5 SSH 0.0.0.0/0 |
Deferred to IAP rollout (§19.2) |
| I2 (webhook replay) | Acceptable (idempotent handlers) |
| I3 (dead HTML-email branch), I4 (sync siteverify) | Open — minor |
Cross-references
- flotilla PR #1 (
wiggleverse/ohm-rfc-app-flotilla), merge7b32a46; exercised by OHM deploydeploys.id=35. - rfc-app PR #1 (
ben.stull/rfc-app), mergefe044ed, tag v0.27.0; dual-pushed togit.benstull.org/benstull/rfc-app(the VM's deploy source). OHM deploydeploys.id=37. - OHM pin:
ohm-rfc/.rfc-app-version0.26.0 → 0.27.0 (c91f7c0). - GCP: VM
ohm-rfc-app/us-central1-a/wiggleverse-ohm; new SAohm-rfc-app-vm@…; deleted FW ruledefault-allow-rdp; Secure Boot on. - Source audit: local-only
~/git/ohm-infra/SECURITY-AUDIT-0026-2026-05-28.md.
Operating instructions for the next session
The audit is remediated; the open threads are infra hardening and a stuck flotilla merge:
- IAP SSH rollout (§19.2) — closes L5 (
default-allow-ssh0.0.0.0/0) and retires the L9 first-connect-TOFU residue. Steps: add an IAP firewall rule (35.235.240.0/20→ tcp:22), grantroles/iap.tunnelResourceAccessor, verifygcloud compute ssh --tunnel-through-iapworks, then remove the 0.0.0.0/0 SSH rule, and flip the flotilla deploy path / deployment record totunnel_through_iap=true. Don't remove 0.0.0.0/0 until IAP is verified. - Resolve
trial-merge-v1.2.0— to ship flotilla v1.2.0 (session 0028's PR), do a proper 3-way merge: it conflicts with the L1 redaction change in_PhaseRunner.run(both edited the same block). Keep both — the redaction wrapping AND v1.2.0's robustness/record-maintenance verbs. - Info items — I3 (delete the dead
text/htmlemail branch or guard it so HTML email can't ship without escaping) and I4 (make the Turnstile siteverify call async so a slow CloudFlare call can't block the event loop). Both small, both rfc-app-core. - Cleanup —
sudo rmthe orphanedohm-app.db\r*files on the VM once confirmed orphaned; consider repo-side path-alignment of the staledeploy/nginx+deploy/systemdtemplates (/opt/rfc-app→/opt/ohm-rfc-app,User=rfc-app→ohm-rfc-app) so a future operator doesn't copy them verbatim.
Suggested next-session launch prompt:
Harden OHM SSH via IAP (§19.2): add the IAP firewall rule + tunnel role, verify IAP SSH, then remove the 0.0.0.0/0 SSH rule and switch flotilla's deploy path to tunnel-through-iap (closes audit-0026 L5 + L9). Then resolve the trial-merge-v1.2.0 conflict to ship flotilla v1.2.0 (it conflicts with the L1 redaction change in _PhaseRunner.run — keep both).