Files
session-history/ohm/0030/SESSION-0030.0-TRANSCRIPT-2026-05-28T16-12--2026-05-28T18-41.md
T

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-hardeningPR #1 → merged (7b32a46). 161 tests pass.

  • L1 — secret redactor threaded into _PhaseRunner; both success and failure phase-detail paths are scrubbed before persistence/printing.
  • I6redact_live matches 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).
  • I5overlay set warns 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) orphaned ohm-app.db\r* files left by the historical CRLF path bug.
  • L5 — deleted the dead default-allow-rdp firewall 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, but TestClient talks plain http, so the cookie was never returned and every authenticated flow failed → the test env now sets SESSION_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 existing RFC_APP_INSECURE_WEBHOOKS pattern).
  • 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 autouse conftest.py fixture. 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 off into 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 live ohm-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 to 750, the non-root SSH user couldn't expand the glob; re-ran under sudo sh -c.
  • After merging flotilla PR #1, git checkout main && git pull didn't fast-forward (no upstream tracking) and the tree briefly reverted; recovered with git merge --ff-only origin/main.
  • The operator hit a SyntaxError running secret bind: a separate trial-merge-v1.2.0 branch had a conflicted, unresolved merge of session-0028's flotilla v1.2.0 into the working tree (conflict markers in deploy.py, in the exact _PhaseRunner block 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 the git.wiggleverse.org remote I'd pushed/tagged to. Dual-pushed main + the v0.27.0 tag to the benstull remote; 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), merge 7b32a46; exercised by OHM deploy deploys.id=35.
  • rfc-app PR #1 (ben.stull/rfc-app), merge fe044ed, tag v0.27.0; dual-pushed to git.benstull.org/benstull/rfc-app (the VM's deploy source). OHM deploy deploys.id=37.
  • OHM pin: ohm-rfc/.rfc-app-version 0.26.0 → 0.27.0 (c91f7c0).
  • GCP: VM ohm-rfc-app/us-central1-a/wiggleverse-ohm; new SA ohm-rfc-app-vm@…; deleted FW rule default-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:

  1. IAP SSH rollout (§19.2) — closes L5 (default-allow-ssh 0.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), grant roles/iap.tunnelResourceAccessor, verify gcloud compute ssh --tunnel-through-iap works, then remove the 0.0.0.0/0 SSH rule, and flip the flotilla deploy path / deployment record to tunnel_through_iap=true. Don't remove 0.0.0.0/0 until IAP is verified.
  2. 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.
  3. Info items — I3 (delete the dead text/html email 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.
  4. Cleanupsudo rm the orphaned ohm-app.db\r* files on the VM once confirmed orphaned; consider repo-side path-alignment of the stale deploy/nginx + deploy/systemd templates (/opt/rfc-app/opt/ohm-rfc-app, User=rfc-appohm-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).