Files
compass_teams_nhs/docs/phase-2-harness-notes.md
Paperclip Builder 78d2822b39 Phase 2: harden Compass fetcher for the Cloudflare login-POST challenge
- retry the login on a fresh browser context (stale cf_chl_rc_ni cookies loop
  the next challenge) and fail loudly with cloudflare_login_post_challenge
- detect the Compass login-rejection message and second-factor prompts
- docs: record that Cloudflare's managed challenge blocks the headless login
  POST (GET passes), what was tried, and the unblock options

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-15 16:14:50 +11:00

4.3 KiB

Phase 2 — Compass login + fetch notes

Status: blocked on Cloudflare. The credential is available and the login page loads, but Cloudflare's managed challenge blocks the login POST under the headless harness. See AUD-104.

Harness used

  • pi-browser-harness v0.11.0 (/home/sam/.agents/extensions/pi-browser-harness/package.json).
  • Entrypoint: the pi extension src/index.ts plus its long-lived daemon src/daemon/index.ts. The daemon bridges pi → Unix socket (/tmp/pi-browser-daemon.sock) → Chrome DevTools Protocol.
  • Chrome: systemd user service chrome-pi.service (/home/sam/.config/systemd/user/chrome-pi.service), --headless=new, --remote-debugging-port=9222, --user-data-dir=/home/sam/.chrome-pi.
  • The Phase 2 fetcher (scripts/compass-fetch.mjs) talks to the harness daemon socket and drives the harness's Chrome over CDP.

Compass

  • URL: https://nhs-vic.compass.education/login.aspx (Northcote High School).
  • Login form: #username, #password, hidden browserFingerprint, __RequestVerificationToken, submit #button1 (formaction="/login?handler=Login"). Also present: a SAML button and "Parent sign in with one-time code" (formaction="/login?handler=OtpSignin").

Cloudflare finding (important)

Compass is behind a Cloudflare managed challenge. With the default HeadlessChrome user agent, the challenge loops forever (title="Just a moment...", body "Compass is verifying your connection..."). The fix that works:

  1. Override the user agent to a normal desktop Chrome UA (Network.setUserAgentOverride), and
  2. run the page in an isolated browser context (Target.createBrowserContext) so stale cf_clearance / cf_chl_rc_ni cookies from earlier attempts do not loop the challenge.

With both, the challenge clears in ~5 s and the real login page loads. Both are implemented in scripts/compass-fetch.mjs.

The login POST is the hard part

When the form is submitted (POST /login?handler=Login) Cloudflare returns a 403 managed challenge and serves Just a moment.... Under headless Chrome that challenge does not clear (iframes=0, body "Compass is verifying your connection..."), so Compass never sees the login. Captured response headers on the blocked POST show server: cloudflare; the GET of the same page passes.

Tested and did not fix it: a normal desktop-Chrome UA override, isolated browser contexts, Sec-CH-UA user-agent metadata, focus/visibility emulation, client hints, and long waits (up to 4 minutes).

The POST passes only intermittently. It was observed returning 200 twice, but five further attempts (including after a 75 s cooldown, each in a fresh context) were all challenged. scripts/compass-fetch.mjs retries the login on a fresh browser context up to COMPASS_LOGIN_ATTEMPTS (default 4) and fails loudly with cloudflare_login_post_challenge (exit 2) if none get through.

This is the Phase 2 risk: the harness can read Compass but cannot log in from headless Chrome.

Proof stored (2026-10-15)

var/raw/harry/<runId>/login.html
var/raw/harry/<runId>/login.meta.json
var/raw/harry/<runId>/manifest.json
  • var/ is git-ignored. Raw pages stay local, never committed.
  • login — https://nhs-vic.compass.education/login.aspx, title Login | Compass, 125 741 bytes, sha256 4d3b731c34d64b6978d4e1bd7f4a7772ae2b5d8687241db3d7163c2beb325f01.

Blocked on

A reliable way past Cloudflare's managed challenge for the login POST. The credential itself is no longer missing: it is in ~/.config/environment.d/10-secrets.conf as COMPASS_USERNAME / COMPASS_PASSWORD.

Options (owner: Sam, because they are host/infra or browser decisions):

  1. Give the harness a headful Chrome (e.g. add xvfb to the .13 NixOS config and run Chrome non-headless). Managed challenges normally pass there.
  2. Supply a cf_clearance cookie from a real browser session on the same public IP and user agent, as a secret to inject.
  3. Confirm a non-CF login path (SAML/SSO or the Compass mobile API) to pursue.

Smallest useful scope for Phase 3

Until a login succeeds we cannot enumerate the per-child page URLs, so the smallest useful scope cannot be fixed yet. It is expected to be the "today/planner" page plus the messages/notices list, which is what answers "what is overdue".