toga-ai 1.0.528 → 1.0.529

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -108,9 +108,15 @@ divergent shapes (read `emailAddress` with `email` fallback).
108
108
 
109
109
  **SAML** (rumcsi, towfoundation, newcenturyholdingsllc) is handled in `framework.php`
110
110
  (`meta`/`acs`/`sls`); newcenturyholdingsllc and towfoundation **auto-create `people` rows** for
111
- unknown SAML users. rumcsi additionally accepts a legacy `?hash=<base64 email>` auto-login — **an unauthenticated
112
- account-takeover path that appears to be the client's actual production login**; do not remove it
113
- before completing the `acs` handler (see [login flows](features/login-flows.md)).
111
+ unknown SAML users. rumcsi additionally accepts a `?hash=<base64 email>` auto-login — **an unauthenticated
112
+ account-takeover path that is also, per the routing evidence, RUMC's live production SSO login**:
113
+ the block holds the only rumcsi route to `/enterprise_dashboard` (where a real production SSO
114
+ login lands), and togaview has no `?saml=` handling, so the 2.0 SAML gateway is not involved.
115
+ Only rumcsi's `acs` handler fails to populate the session (newcenturyholdingsllc `:819` and
116
+ towfoundation `:1180` do), which is why only rumcsi has a magic link. **Deleting it breaks RUMC
117
+ login into an IdP redirect loop** — sign the token or complete the `acs` handler instead, in
118
+ coordination with the external link generator. A cold-login capture to confirm the inbound
119
+ `?hash=` hop is still outstanding (see [login flows](features/login-flows.md)).
114
120
 
115
121
  ## Dashboards
116
122
  Five dashboards, one per audience/business model, each delegating to a client override or the
@@ -72,26 +72,65 @@ lookup silently found nothing. Removed, and `email LIKE '…'` changed to `=` in
72
72
 
73
73
  ## Gotchas / known issues
74
74
 
75
- ### OPEN SECURITY ITEM — rumcsi `?hash=` unauthenticated account takeover (deliberately left in place)
75
+ ### OPEN SECURITY ITEM — rumcsi `?hash=` is the LIVE production SSO login (do not delete it)
76
76
  `_/app/framework.php` authenticates a rumcsi user on nothing but
77
77
  `base64_decode(urldecode($_GET['hash']))` matching an active `people` row for the rumcsi
78
78
  clientid — **no password, signature, nonce, or expiry** — then populates ~18 session keys and
79
79
  redirects to `/enterprise_dashboard`. Anyone who can guess a RUMC user's email can log in as
80
80
  them.
81
81
 
82
- It was removed during TRUE-80671 and then **deliberately reverted**, because it may BE the
83
- production login mechanism: the rumcsi SAML `acs` handler does `App_MVC::routeTo('/')` on success
84
- and **never populates `$_SESSION`** (unchanged since commit 9e7d306, "phase one … make sure
85
- redirect works"); repo-wide only two places ever build a rumcsi session — this hash block and
86
- `mvc/login/post.php:462`, and the latter is unreachable because the SAML gate redirects to the
87
- IdP before a password POST can be processed; and RUMC login is confirmed **working in
88
- production**. Nothing in togaview/togadesk/library generates such a link, so links are presumably
89
- generated outside our codebase.
90
-
91
- - **Next step:** obtain the post-SSO redirect URL from Jonathan to confirm whether it carries
92
- `hash=`.
93
- - **Proper fix:** complete the rumcsi `acs` handler (mirror newcenturyholdingsllc:
94
- `$auth->getNameId()` → `people` lookup → session build) — **not** restoring the bypass.
82
+ It was removed during TRUE-80671 and then **deliberately reverted**. Routing evidence gathered
83
+ afterwards shows this is not a dormant legacy backdoor — it is **how RUMC actually logs in**:
84
+
85
+ - Jonathan performed a real production SSO login and was returned to
86
+ `https://rumcsi.togaview.agilantsolutions.com/enterprise_dashboard`.
87
+ - Inside the rumcsi block, **`framework.php:278` is the ONLY path to `/enterprise_dashboard`** —
88
+ and it sits **inside the hash block**, behind `if ($rumcsiUser)`. The SAML `acs` handler instead
89
+ does `App_MVC::routeTo('/')`. The only other RUMC route to that page is
90
+ `mvc/login/post.php:511`, which is the *password* login. **Landing on `/enterprise_dashboard`
91
+ after an SSO login is the hash block's signature.**
92
+ - The absence of `?hash=` in the final URL is **expected, not exculpatory**: the block consumes
93
+ the parameter and issues a fresh redirect, so the query string is gone from the address bar.
94
+ - **The modern alternative is ruled out.** The 2.0 SAML gateway (repo `saml`,
95
+ `Controller/Index.php:456-460`) hands off with `?saml=<base64 JSON whose client and user UUIDs
96
+ are encrypted via `_String::encryptWithKey`>` — the correct mechanism, which `tools` uses.
97
+ togaview has **zero** handling for `?saml=` (no `$_GET['saml']` anywhere). RUMC does not come
98
+ through that gateway, and there is no hidden session-population path.
99
+ - **Had the deletion shipped, RUMC SSO would have broken outright:** authenticate at the IdP →
100
+ `acs` → `/` → no `contactId` → bounced back to the IdP. A redirect loop, not a login. The
101
+ revert was correct.
102
+
103
+ **Still open — who generates the link.** Nothing in togaview/togadesk/library builds a `hash=`
104
+ URL, so the generator is external (likely RUMC's IdP-initiated flow or their own portal).
105
+ **Confirming test:** a **cold** SSO login in a private window with DevTools → Network →
106
+ *Preserve log*, capturing the full redirect chain and checking whether the hop **into** togaview
107
+ carries `?hash=`. Jonathan's test may not have exercised a cold login at all — with a live session
108
+ cookie the `!array_key_exists('contactId', $_SESSION)` gate is skipped entirely and
109
+ `/enterprise_dashboard` is simply where he already was.
110
+
111
+ **Fix shape — a coordinated change, never a deletion.** Two options, both requiring coordination
112
+ with whoever generates the link:
113
+ - **(a) Keep the handoff but sign it.** Replace the unsigned base64 email with an HMAC-signed
114
+ token via `App_String::encrypt`/`decrypt` (`library/app/string.php`). Smallest change; kills
115
+ guessability.
116
+ - **(b) Complete the rumcsi `acs` handler** so it builds the session itself, then retire the
117
+ handoff.
118
+
119
+ ⚠ **Do NOT delete the block first, under either option.**
120
+
121
+ ### Why rumcsi is the only one of the three SAML clients with a magic-link login
122
+ The real lesson is the contrast between the three `acs` handlers in `_/app/framework.php`:
123
+
124
+ | Client | `acs` handler | Sets `contactId` / session? |
125
+ |---|---|---|
126
+ | newcenturyholdingsllc | `:819` | **Yes** — completes the login |
127
+ | towfoundation | `:1180` | **Yes** — completes the login |
128
+ | rumcsi | `:399-417` | **No** — only `routeTo('/')` (since 9e7d306, "phase one … make sure redirect works") |
129
+
130
+ rumcsi's handler was never finished, and the `?hash=` block is what filled the gap. When adding a
131
+ SAML client, the `acs` handler **must** populate the session — an unfinished one invites exactly
132
+ this kind of unsigned side door.
133
+
95
134
  - Note the surviving SAML branch is gated on `!inTestMode()`, so **test mode leaves rumcsi with
96
135
  no login path at all**.
97
136
 
@@ -106,6 +145,14 @@ generated outside our codebase.
106
145
  - The flows are order-dependent: a contact matching an earlier flow never reaches later ones.
107
146
 
108
147
  ## Change history
148
+ - 2026-08-05 (later, TRUE-80671) — **Upgraded the rumcsi `?hash=` finding from "may be" to
149
+ evidenced:** `framework.php:278` inside the hash block is the only rumcsi route to
150
+ `/enterprise_dashboard`, where a real production SSO login landed; togaview has no `?saml=`
151
+ handling at all, so the 2.0 SAML gateway is ruled out; deleting the block would have produced an
152
+ IdP redirect loop, not a login. Recorded the three-way `acs` handler contrast (newcentury :819 /
153
+ towfoundation :1180 populate the session, rumcsi :399-417 never did), both fix shapes (sign the
154
+ token with `App_String::encrypt`, or finish `acs`), and the outstanding cold-login DevTools test.
155
+ (mhammontree)
109
156
  - 2026-08-05 (TRUE-80671, uncommitted) — Fixed the unguarded `$user_name[1]` session-name build
110
157
  that **locked mononym / shared-mailbox users out entirely** (4 sites); removed
111
158
  `sqlProtect(sqlEscape())` and switched email lookups to `=`; recorded the rumcsi `?hash=`
@@ -35,13 +35,19 @@ Richmond University Medical Center (**RUMCSI**) is a framework **1.0** client se
35
35
  - **TOGa View SAML.** `togaview/_/app/framework.php` has a RUMCSI-specific host branch that selects
36
36
  branding (`$_SESSION['stylePath']`) and the SAML login path — an unregistered togaview hostname
37
37
  falls through to default branding and skips SAML.
38
- - **⚠ Portal login is (probably) an unauthenticated `?hash=` link.** The rumcsi SAML `acs`
39
- handler never populates `$_SESSION`, and the only working rumcsi session builder is a
40
- `_/app/framework.php` block that authenticates on nothing but a base64-encoded email in
41
- `?hash=`. RUMC login works in production, so that path may be load-bearing; it was
42
- deliberately left in place pending confirmation of the real post-SSO redirect URL. Full
43
- analysis and the proper fix: [TOGa View login
44
- flows](../../1.0/apps/togaview/features/login-flows.md#open-security-item--rumcsi-hash-unauthenticated-account-takeover-deliberately-left-in-place).
38
+ - **⚠ Portal login IS an unauthenticated `?hash=` magic link — and it is load-bearing.** The
39
+ rumcsi SAML `acs` handler never populates `$_SESSION`; the only working rumcsi session builder
40
+ is a `_/app/framework.php` block that authenticates on nothing but a base64-encoded email in
41
+ `?hash=` (no signature, nonce, or expiry). The routing evidence is firm: `framework.php:278`,
42
+ inside that block, is the **only** rumcsi route to `/enterprise_dashboard`, which is exactly
43
+ where a real production SSO login landed
44
+ (`rumcsi.togaview.agilantsolutions.com/enterprise_dashboard`), and togaview has **no** `?saml=`
45
+ handling, so the 2.0 SAML gateway is not involved. **Deleting the block breaks RUMC SSO into an
46
+ IdP redirect loop** — it must be signed or replaced by a completed `acs` handler, in
47
+ coordination with whoever generates the link (the generator is external to our repos and still
48
+ unidentified). Full analysis, both fix shapes, and the outstanding cold-login confirmation test:
49
+ [TOGa View login
50
+ flows](../../1.0/apps/togaview/features/login-flows.md#open-security-item--rumcsi-hash-is-the-live-production-sso-login-do-not-delete-it).
45
51
  Note also that the SAML branch is gated on `!inTestMode()`, so **test mode leaves rumcsi with
46
52
  no login path**.
47
53
  - **Hardcoded client-ID branching.** RUMCSI appears in TOGa Desk's hardcoded client-ID branching
@@ -49,6 +55,9 @@ Richmond University Medical Center (**RUMCSI**) is a framework **1.0** client se
49
55
 
50
56
  ## Change history
51
57
 
58
+ - 2026-08-05 (later) — routing evidence confirms the `?hash=` path is the live SSO login (only
59
+ route to `/enterprise_dashboard`; no `?saml=` handling in togaview); deleting it would break
60
+ RUMC login into a redirect loop. (mhammontree)
52
61
  - 2026-08-05 — recorded the `?hash=` portal-login path as an open takeover risk left in place
53
62
  pending SSO confirmation, and the test-mode no-login-path gap (TRUE-80671). (mhammontree)
54
63
  - 2026-08-04 — Client profile created during the TRUE-80139 read-only review of the RUMCSI branded
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.528",
3
+ "version": "1.0.529",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",