Skip to content

feat(s136): page authentication, and a regulated boot that refuses to serve without it - #129

Merged
HackTuah merged 7 commits into
mainfrom
slice/136-page-authentication
Oct 8, 2026
Merged

HackTuah merged 7 commits into
mainfrom
slice/136-page-authentication

Conversation

@HackTuah

@HackTuah HackTuah commented Oct 8, 2026

Copy link
Copy Markdown
Member

Slice 136, opened on the owner's 2026-10-08 answer sheet (D1), finding F-132-1.

The exposure on main: the web pages carried no authentication. Anyone who could reach the port could:

  • answer approvals;
  • start an MCP stdio command from /mcp "create";
  • download the keys from GET /settings/export.tar.gz?keys=1;
  • edit the gateway allow-list.

The hardened image (#117) binds every interface.

What changes

  • TRINITY_WEB_AUTH in none | oidc | local_token.
    • Unset means none on a loopback bind and a boot refusal on any other bind, in every profile. So the headless image needs TRINITY_WEB_AUTH set to start (D-136-1).
    • :regulated refuses none and local_token on a non-loopback bind.
    • :regulated refuses x-forwarded-proto trust unless TRINITY_TRUSTED_PROXY is set.
  • oidc: an OpenID Connect login (authorization code + PKCE S256, state, nonce, iss checked). It uses oidcc 3.9.0 and oidcc_plug 0.5.1, both past their 2026 CVEs, with the pins asserted in a test. Roles view, approve and administer are mapped from Keycloak, Entra (group overage refused with a diagnostic), Okta and generic claims.
  • Every route and live_session is guarded: a plug before the router, plus on_mount, held by a route census. /mcp create, /gateways edit, /oban, the dashboards and the export need administer.
  • A Host allow-list answers 403 to a foreign Host before anything routes (DNS rebinding; Phoenix and Bandit have none). check_origin is an explicit list in every profile.
  • Who approved: approval and privileged-route receipts carry the principal's sub, iss and role, on a new access chain scope.
  • local_token, for a LAN outside :regulated: exchanged once for a SameSite=Strict cookie, compared in constant time, rate-limited, with a persistent shared-token banner.

Proof: 12 automatic ACs, each shown red with its guard removed. AC10 (Entra overage) and AC11 (desktop Host check) are manual. The full suite passes on SQLite (1116) and Postgres. The changed tests (88), format, credo and plan_check were re-run in review.

Known before merge

…boot that refuses to serve without it)

The web pages carried no authentication: anyone who could reach the port could answer
approvals, start an MCP stdio command from /mcp, download the keys from the settings export
and edit the gateway allow-list, and the hardened image binds every interface.

- TRINITY_WEB_AUTH in {none, oidc, local_token}. Unset is none on a loopback bind and a boot
  refusal on any other bind, in every profile. :regulated refuses none and local_token on a
  non-loopback bind, and refuses x-forwarded-proto trust without TRINITY_TRUSTED_PROXY.
- oidc: an OpenID Connect login (code + PKCE S256, state, nonce, iss) with oidcc 3.9 and
  oidcc_plug 0.5.1 pinned past their 2026 CVEs; roles view, approve and administer mapped from
  Keycloak, Entra (with overage refused), Okta and generic claims.
- Every route and live_session is guarded (a plug before the router and on_mount), held by a
  route census; privileged routes need administer.
- A Host allow-list refuses a foreign Host before anything routes (DNS rebinding);
  check_origin is an explicit list in every profile.
- Approval and privileged-route receipts carry the principal's sub, iss and role, on a new
  access chain scope.
- local_token for a LAN outside :regulated: exchanged once for a SameSite=Strict cookie,
  constant-time, rate-limited, with a shared-token banner.

Signed-off-by: Ayla Croft <aylacroft@proton.me>
The image workflow and docs/regulated/headless-image.md, both new on main with slice 130, start the image with no TRINITY_WEB_AUTH, which slice 136 refuses on a non-loopback bind: the workflow's container now runs with a local_token, and the doc's example with oidc.

Signed-off-by: Ayla Croft <aylacroft@proton.me>
Signed-off-by: Ayla Croft <aylacroft@proton.me>

# Conflicts:
#	CHANGELOG.md
…aves Partial

Slice 132's mapping, merged to main, requires every census, every regulated refusal and every responsibility-matrix row to be cited. Slice 136 adds two censuses (route, policy), three refusals (web auth, origin, trusted proxy) and five matrix rows, so the merge failed test/control_mapping_test.exs. New rows: AC-3 twice, AC-6(9), SC-8, SC-23. The IA-2 row and the matrix's web-pages row no longer describe the gap 136 closes.

Signed-off-by: Ayla Croft <aylacroft@proton.me>
…work on a FIPS node

Plug encrypts a session cookie with XChaCha20-Poly1305 only, and the FIPS provider refuses it: 128 tests failed on both fips runs of f0591b4 with "Forbidden in FIPS" from Plug.Crypto.MessageEncryptor, every one a page request. The cookie holds a server-side session id and, during a login, the PKCE verifier and nonce; it is HttpOnly, SameSite, Secure behind TLS and HMAC-SHA256 signed. A test holds the shape of the cookie the application sends (SFMyNTY., not XCP.) on every leg; it fails with encryption_salt put back.

Signed-off-by: Ayla Croft <aylacroft@proton.me>
Signed-off-by: Ayla Croft <aylacroft@proton.me>

# Conflicts:
#	CHANGELOG.md
#	ci/ironbank/Dockerfile
#	docs/regulated/control-mapping.md
…t slice 135 asks for one

Merged with slice 135, every regulated case in boot_refusal_node_test stopped at 135's egress refusal (network: :allow with no TRINITY_REGULATED_EGRESS) before the web-auth check it names. The base environment carries the list, as 135 did for regulated_boot_node_test.

Signed-off-by: Ayla Croft <aylacroft@proton.me>
@HackTuah
HackTuah merged commit 15d9cc5 into main Oct 8, 2026
17 checks passed
@HackTuah
HackTuah deleted the slice/136-page-authentication branch October 8, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant