@tideorg/mcp 1.4.2 → 1.9.1

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/GAP_REGISTER.md +18 -8
  3. package/PRIVACY.md +41 -0
  4. package/README.md +207 -197
  5. package/adapters/AGENTS.md +3 -2
  6. package/adapters/CLAUDE.md +1 -1
  7. package/canon/anti-patterns.md +55 -62
  8. package/canon/concepts.md +46 -34
  9. package/canon/custom-contracts.md +12 -8
  10. package/canon/feature-mapping.md +27 -75
  11. package/canon/framework-matrix.md +69 -22
  12. package/canon/hosting-options.md +111 -0
  13. package/canon/iga-change-requests-api.md +211 -0
  14. package/canon/invariants.md +33 -35
  15. package/canon/security-gap-mapping.md +506 -0
  16. package/canon/security-runtime-probes.md +177 -0
  17. package/canon/tidecloak-bootstrap.md +21 -16
  18. package/canon/tidecloak-endpoints.md +60 -98
  19. package/canon/troubleshooting.md +115 -53
  20. package/canon/version-policy.md +8 -12
  21. package/mcp-server/dist/server.js +221 -19
  22. package/package.json +48 -45
  23. package/playbooks/add-auth-nextjs-existing.md +33 -17
  24. package/playbooks/add-auth-nextjs-fresh.md +1 -1
  25. package/playbooks/add-rbac-nextjs.md +7 -2
  26. package/playbooks/bootstrap-realm-from-template.md +33 -18
  27. package/playbooks/deploy-tidecloak-docker.md +31 -28
  28. package/playbooks/diagnose-missing-roles-or-claims.md +2 -2
  29. package/playbooks/initialize-admin-and-link-account.md +22 -19
  30. package/playbooks/protect-api-nextjs.md +40 -1
  31. package/playbooks/protect-aspnet-core-asgard.md +313 -0
  32. package/playbooks/protect-routes-nextjs.md +24 -10
  33. package/playbooks/provision-tidecloak-skycloak.md +213 -0
  34. package/playbooks/setup-forseti-e2ee.md +32 -50
  35. package/playbooks/setup-iga-admin-panel.md +113 -171
  36. package/playbooks/verify-jwt-server-side.md +20 -1
  37. package/prompts/build-private-customer-portal.md +1 -1
  38. package/prompts/security-gap-analysis.md +55 -0
  39. package/reference-apps/encrypted-communication/anti-patterns.md +3 -3
  40. package/reference-apps/encrypted-communication/bootstrap-sequence.md +2 -2
  41. package/reference-apps/encrypted-communication/scenario.md +2 -2
  42. package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +8 -8
  43. package/reference-apps/iga-admin-governance/anti-patterns.md +18 -16
  44. package/reference-apps/iga-admin-governance/bootstrap-sequence.md +10 -5
  45. package/reference-apps/iga-admin-governance/role-policy-matrix.md +12 -13
  46. package/reference-apps/iga-admin-governance/scenario.md +15 -13
  47. package/reference-apps/organisation-password-manager/bootstrap-sequence.md +5 -6
  48. package/reference-apps/policy-governed-signing/bootstrap-sequence.md +9 -9
  49. package/skills/tide-diagnostics/SKILL.md +1 -1
  50. package/skills/tide-integration/SKILL.md +9 -18
  51. package/skills/tide-mcp-qa/SKILL.md +139 -0
  52. package/skills/tide-rbac-and-e2ee/SKILL.md +5 -5
  53. package/skills/tide-reviewer/SKILL.md +1 -1
  54. package/skills/tide-security-analyst/SKILL.md +182 -0
  55. package/skills/tide-setup/SKILL.md +1 -1
  56. package/canon/delegation.md +0 -195
  57. package/playbooks/setup-server-delegation.md +0 -142
@@ -0,0 +1,177 @@
1
+ # Security Runtime Probes
2
+
3
+ Turns **INFERRED** static findings into **VERIFIED** ones by observing the live target's actual behavior. This is the second tier of the `tide-security-analyst` workflow: static inspection finds *candidate* gaps in code; runtime probing *confirms* which are real against the running system.
4
+
5
+ A static finding says "this route appears unprotected." A runtime probe says "this route returned 200 with no credentials — here is the response." The second is evidence; the first is a hypothesis.
6
+
7
+ ---
8
+
9
+ ## Authorization gate — read before probing anything
10
+
11
+ Runtime probing sends real requests to a real system. It is only permitted when **all** of these hold. If any is unmet, stop and stay in static-only mode.
12
+
13
+ 1. **Explicit authorization.** The operator owns the target or has written authorization to test it. Confirm this in the report's scope line. No probing of third-party systems on a hunch.
14
+ 2. **Non-production or agreed window.** Probing production is allowed only with explicit operator consent; prefer staging. Never probe a system you were merely *shown*, only one you were *asked to test*.
15
+ 3. **Target is a network endpoint the operator named.** Do not discover-and-probe adjacent hosts, subdomains, or internal services you happened to find in config.
16
+
17
+ **Absolute limits — these are not "best-effort", they are the boundary of this role:**
18
+
19
+ - **Non-destructive only.** GET and safe HEAD/OPTIONS. Never send probes that create, modify, or delete data. For write endpoints, confirm the *auth response* (401/403 vs 200) using a request the server rejects *before* it mutates — never complete a mutating call to "see if it works."
20
+ - **No exploitation.** Confirm a gap exists; do not weaponize it. Reaching an unauthorized endpoint and reading one response header confirms SG-05. Dumping the database behind it does not — that is an attack, not an analysis.
21
+ - **No credential attacks.** No brute force, no password spraying, no token cracking, no fuzzing that could lock accounts or trip abuse defenses. Observing that no rate limit exists is a note (out of scope, not a Tide gap); actually exhausting it is not permitted.
22
+ - **Rate-limited and low-volume.** A handful of deliberate requests per finding, not a scan. You are confirming named hypotheses, not enumerating the attack surface.
23
+ - **Stop on the first confirmation.** Once a probe confirms a finding, record it and move on. Do not escalate.
24
+
25
+ If the operator asks for active exploitation, penetration testing, or load/abuse testing, that is a different engagement with different rules — decline within this role and say so.
26
+
27
+ ---
28
+
29
+ ## How probing upgrades evidence
30
+
31
+ Each finding carries a confidence tag (`canon/security-gap-mapping.md`). Runtime probes move findings up this ladder:
32
+
33
+ | Before (static) | Probe | After |
34
+ |---|---|---|
35
+ | INFERRED — route has no visible auth in code | Unauthenticated GET returns 200 with data | **VERIFIED** |
36
+ | INFERRED — bearer tokens, no DPoP handling seen | Replay a captured token from a second client; it is accepted | **VERIFIED** (replayable) |
37
+ | ASSUMED — operator says "we verify JWTs" | Send a token with `alg: none` / tampered payload; it is accepted | **VERIFIED** (SG-13 real) |
38
+ | INFERRED — remote JWKS fetch in code | Observe a JWKS request to the certs endpoint at verification time | **VERIFIED** (SG-10) |
39
+
40
+ A probe that *fails to confirm* is just as valuable: it downgrades or drops a false positive. Record both outcomes. A finding that survived a probe attempt is far stronger than one that was only read from code.
41
+
42
+ ---
43
+
44
+ ## Probe procedures by gap
45
+
46
+ Each probe: the observation that confirms the gap, the safe request to make, and how to read the result. `$T` = the operator-named base URL.
47
+
48
+ ### SG-04 / SG-05 — client-only authz / unprotected APIs
49
+
50
+ **Confirms**: an endpoint enforces nothing server-side.
51
+
52
+ ```bash
53
+ # Enumerate candidate routes from static analysis, then for each sensitive one:
54
+ curl -s -o /dev/null -w "%{http_code}\n" "$T/api/<route>"
55
+ # 200 with no Authorization header on a sensitive route = CONFIRMED unprotected (SG-05)
56
+ # Compare: same route WITH a valid session vs WITHOUT — identical output = no server-side check (SG-04)
57
+ curl -s "$T/api/<route>" | head -c 300 # inspect: is real data returned unauthenticated?
58
+ ```
59
+ Read: 401/403 unauthenticated = protected (drop/downgrade the finding). 200 with data = confirmed. 200 with an empty/error body = ambiguous, note it, do not overclaim.
60
+
61
+ ### SG-03 — bearer token replay (no proof-of-possession)
62
+
63
+ **Confirms**: a stolen token works from anywhere, no key binding.
64
+
65
+ ```bash
66
+ # With operator-provided test token (their own session), replay from a clean client / different IP context:
67
+ curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TESTTOKEN" "$T/api/<protected>"
68
+ # 200 with a plain replayed bearer and no DPoP header = CONFIRMED replayable (SG-03)
69
+ # If the server demands a fresh DPoP proof, it will reject the bare bearer.
70
+ ```
71
+ Read: acceptance of a bare replayed bearer confirms the gap. Use only a token the operator supplied from their own account.
72
+
73
+ ### SG-13 — JWT algorithm confusion / unverified signature
74
+
75
+ **Confirms**: the verifier trusts the token's claimed algorithm or skips verification.
76
+
77
+ ```bash
78
+ # Using the operator's own valid token as a base, craft a NON-mutating GET with:
79
+ # (a) alg swapped to "none" and signature stripped, or
80
+ # (b) a modified claim (e.g. a role) re-signed with an empty/guessed key
81
+ # Send to a read-only protected route and observe acceptance.
82
+ curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $FORGED" "$T/api/<protected>"
83
+ # 200 = CONFIRMED the signature is not properly verified (SG-13 / SG-02 exposure)
84
+ ```
85
+ Read: this is the one probe that constructs a token — keep it read-only, use the operator's own account as the base, and do not use any elevated claim to perform an action. Acceptance confirms; rejection (401) means the verifier is sound on this vector. Never chain a confirmed forgery into a real privileged operation.
86
+
87
+ ### SG-10 — remote JWKS fetch
88
+
89
+ **Confirms**: the verifier fetches keys over the network at verification time.
90
+
91
+ ```bash
92
+ # Observe from the operator's own logs/network capture whether a request goes to
93
+ # /.well-known/jwks.json or /protocol/openid-connect/certs during token verification.
94
+ curl -s -o /dev/null -w "%{http_code}\n" "$T/.well-known/jwks.json"
95
+ # Endpoint existing is not proof of use; correlate with a verification-time fetch in the app's egress logs.
96
+ ```
97
+ Read: confirmation requires observing the *app* fetch keys during verify, not just that the endpoint responds. If you can only see the endpoint, keep the finding INFERRED.
98
+
99
+ ### SG-06 / SG-17 — server-readable sensitive data / user secrets
100
+
101
+ **Confirms**: sensitive data is returned in the clear or is app-decryptable.
102
+
103
+ ```bash
104
+ # With the operator's authorization, request a record the authenticated user owns:
105
+ curl -s -H "Authorization: Bearer $TESTTOKEN" "$T/api/<record>" | head -c 500
106
+ # Plaintext sensitive fields in the response = server can read them (SG-06/SG-17 confirmed for data-in-transit-from-server)
107
+ ```
108
+ Read: plaintext in the API response proves the server holds readable data. Do NOT attempt to reach other users' records to "prove" IDOR — that is exploitation and a separate (out-of-scope) class. Confirm only against the operator's own test account.
109
+
110
+ ### SG-15 — session lifecycle
111
+
112
+ **Confirms**: sessions don't rotate / don't expire as claimed.
113
+
114
+ ```bash
115
+ # Non-destructive: capture the session id/cookie before and after login with the operator's test account.
116
+ # Same id after authentication = no rotation (fixation exposure). Operator drives the login; you observe.
117
+ # For expiry: note token exp; re-use after the claimed lifetime in a read-only call.
118
+ ```
119
+ Read: an unrotated identifier across a privilege boundary confirms fixation exposure. Observe only; the operator performs the login.
120
+
121
+ ### SG-01 / SG-02 — surface confirmation only
122
+
123
+ These are confirmed from **artifacts**, not live probing: a schema showing a password-hash column (SG-01), a signing secret visible in the operator-shared config/env (SG-02). Do not attempt to crack hashes or extract keys — possession of the artifact plus its role in the auth path is the confirmation. Keep these static.
124
+
125
+ ---
126
+
127
+ ## Probes that are NOT allowed in this role
128
+
129
+ - Reaching **other users'** data to demonstrate IDOR/broken-object-level-authz (exploitation; also an out-of-scope class per `canon/security-gap-mapping.md`).
130
+ - Any **write/delete** to confirm a mutating endpoint is unprotected — confirm via the auth-layer response before the mutation, never by completing it.
131
+ - **Injection, XSS, SSRF, traversal** payloads — these confirm out-of-scope classes and constitute active exploitation.
132
+ - **Rate-limit / brute-force / load** testing — a different engagement.
133
+ - Probing anything the operator did not explicitly name as in scope.
134
+
135
+ Finding one of these classes statically is reportable (in the out-of-scope section); *actively exploiting* it to confirm is not part of this role.
136
+
137
+ ---
138
+
139
+ ## Recording probe results
140
+
141
+ For every probe, the finding in the report gains a **Runtime confirmation** line:
142
+
143
+ ```
144
+ - Runtime confirmation: <exact request> → <observed response, e.g. "200, JSON with 14 user records, no auth header"> [VERIFIED]
145
+ ```
146
+
147
+ or, when a probe failed to confirm:
148
+
149
+ ```
150
+ - Runtime confirmation: <request> → <e.g. "401 Unauthorized"> — finding NOT confirmed at runtime; downgraded to <INFERRED/dropped>
151
+ ```
152
+
153
+ A report that ran the runtime tier states, in its Method line, `both (static + runtime)` and lists which SG findings were probed. Findings that were *only* inferred and not probed stay tagged INFERRED — never present an unprobed hypothesis as though it were confirmed.
154
+
155
+ ---
156
+
157
+ ## Verification (of this probe pass)
158
+
159
+ - [ ] Authorization and scope confirmed in writing before any request was sent.
160
+ - [ ] Every probe was non-destructive (no create/update/delete completed).
161
+ - [ ] No probe reached data outside the operator's own test account.
162
+ - [ ] No out-of-scope class was actively exploited to "confirm" it.
163
+ - [ ] Each probed finding has a Runtime confirmation line with the exact request and observed response.
164
+ - [ ] Findings not probed remain tagged at their static confidence level.
165
+
166
+ ## Anti-Patterns
167
+
168
+ - **AP-SEC-7** — Probing a system the operator showed but did not authorize testing. Authorization is per-target and explicit.
169
+ - **AP-SEC-8** — Completing a mutating request to "check if it's protected." Confirm at the auth layer; never mutate.
170
+ - **AP-SEC-9** — Escalating a confirmed gap into exploitation (dumping data, reaching other accounts, chaining a forged token into a real action). Confirm and stop.
171
+ - **AP-SEC-10** — Presenting an unprobed static hypothesis with runtime-level confidence. Only a probe upgrades the tag.
172
+
173
+ ## Status Legend
174
+
175
+ - **VERIFIED** — observed directly (static artifact or runtime response)
176
+ - **INFERRED** — strongly implied by code, not yet probed
177
+ - **ASSUMED** — operator statement, unconfirmed
@@ -61,12 +61,12 @@ For projects that do not use the init script, follow the manual sequence below.
61
61
 
62
62
  ## Docker Images
63
63
 
64
- | Image | Use when | Forseti support | `tide-roles-mapper` | ORK env vars required |
65
- |-------|----------|----------------|---------------------|-----------------------|
66
- | `tideorg/tidecloak-dev:latest` | Standard development, full Tide protocol | Yes | **Yes** | **No** — built-in defaults |
67
- | `tideorg/tidecloak-stg-dev:latest` | Staging, testing pre-release features | Yes | **No** — silently dropped on import | **Yes** — must be provided |
64
+ | Image | Use when | Forseti support | ORK env vars required |
65
+ |-------|----------|----------------|-----------------------|
66
+ | `tideorg/tidecloak-dev:latest` | Standard development, full Tide protocol | Yes | **No** — built-in defaults |
67
+ | `tideorg/tidecloak-stg-dev:latest` | Staging, testing pre-release features | Yes | **Yes** — must be provided |
68
68
 
69
- **Staging mapper gap**: The `tide-roles-mapper` protocolMapper provider exists on the production image (`tidecloak-dev`) but not staging (`tidecloak-stg-dev`). Realm import silently drops the mapper with no warning. Include standard Keycloak role mappers (`realm roles`, `client roles`) as fallback in the template alongside the Tide-specific mapper. VERIFIED (learning-batch-004, L-06).
69
+ **Role mapper fallback (defensive)**: The Tide role/claim mappers in the shipped realm templates use **stock Keycloak mapper types** — `oidc-usermodel-attribute-mapper` (for `tideUserKey`/`vuid`) and `oidc-hardcoded-claim-mapper` (for `t.uho`). There is no distinct `tide-roles-mapper` provider in the extension repos on `main`; a mapper by that instance name only appears in some external app realm.json files. A prior note claimed a `tide-roles-mapper` protocolMapper provider exists on the prod image but is silently dropped on the staging image this could not be confirmed against source and image contents were not inspected. Treat it as unverified. As a defensive measure, include standard Keycloak role mappers (`realm roles`, `client roles`) in the template so token role claims survive regardless of image.
70
70
 
71
71
  Use `tidecloak-dev` for standard development. It has built-in ORK/threshold defaults and does not need `KC_HOSTNAME`, `SYSTEM_HOME_ORK`, `USER_HOME_ORK`, `THRESHOLD_T`, `THRESHOLD_N`, or `PAYER_PUBLIC`. Use `tidecloak-stg-dev` only when testing against pre-release Tide features.
72
72
 
@@ -140,13 +140,13 @@ Order matters. Do not skip or reorder steps.
140
140
  | 4 | Get master admin token | `POST /realms/master/protocol/openid-connect/token` | Bearer token |
141
141
  | 5 | Create realm | `POST /admin/realms` with realm.json template | Realm + client |
142
142
  | 6 | Initialize Tide + enable IGA | `POST /admin/realms/{realm}/vendorResources/setUpTideRealm` then `POST /admin/realms/{realm}/tide-admin/toggle-iga` | License activated, VRK generated, IGA enabled |
143
- | 7 | Approve client + role change requests | `sign` + `commit` on `/tide-admin/change-set/clients/requests` AND `/roles/requests`. **Roles must be approved here** — `setUpTideRealm` creates `tide-realm-admin` as a draft when IGA is enabled. Without approval, role lookups in Step 8c fail. | Client config + roles committed |
144
- | 8 | Create admin user | `POST /admin/realms/{realm}/users` | User creation change request created |
145
- | 8b | Approve user creation | `sign` + `commit` on `/tide-admin/change-set/users/requests` | User queryable. **Must happen before role assignment.** |
143
+ | 7 | Approve client + role change requests | `authorize` + `commit` on `/iga/change-requests` (bulk-authorize then commit ready CRs; see `canon/iga-change-requests-api.md`). **Roles must be approved here** — with IGA enabled, change requests are created in `PENDING` status (there is no `DRAFT` status token); `setUpTideRealm` creates `tide-realm-admin` as a draft. `tide-realm-admin` is a client role on `realm-management`; it is granted to the first admin via firstAdmin auto-commit during the login-closure converge, after which a `REGEN_ADMIN_POLICY` change request flips the authorizer from `firstAdmin` to `multiAdmin`. Without approving the pending role CRs here, role lookups in Step 8c fail. | Client config + roles committed |
144
+ | 8 | Create admin user | `POST /admin/realms/{realm}/users` (returns 202 → user-creation change request) | User creation change request created |
145
+ | 8b | Approve user creation | `authorize` + `commit` on `/iga/change-requests` | User queryable. **Must happen before role assignment.** |
146
146
  | 8c | Assign tide-realm-admin role | Assign `tide-realm-admin` client role on `realm-management`. Validate role lookup returns a role, not an error. | Admin user has role |
147
147
  | 9 | Generate invite link | `POST /tideAdminResources/get-required-action-link` with `["link-tide-account-action"]` | URL for admin to link Tide account |
148
148
  | 10 | Wait for account linking | Poll user `tideUserKey` attribute until non-empty | Admin has Tide identity |
149
- | 11 | Approve role assignment change requests | `sign` + `commit` on `/tide-admin/change-set/users/requests` and `/roles/requests` | Admin roles committed |
149
+ | 11 | Approve role assignment change requests | `authorize` + `commit` on `/iga/change-requests` | Admin roles committed |
150
150
  | 12 | Update CustomAdminUIDomain | `PUT /identity-provider/instances/tide` then `POST /vendorResources/sign-idp-settings` | Enclave approval popups work from app origin |
151
151
  | 13 | Export adapter JSON | `GET /vendorResources/get-installations-provider?clientId={uuid}&providerId=keycloak-oidc-keycloak-json` | `tidecloak.json` with `jwk`, `vendorId`, `homeOrkUrl` |
152
152
 
@@ -163,7 +163,7 @@ Order matters. Do not skip or reorder steps.
163
163
 
164
164
  **Anti-pattern:** Using `/tide-admin/setUpTideRealm`. Wrong path. Returns 404. VERIFIED.
165
165
 
166
- **Anti-pattern:** Sending JSON to this endpoint. Causes "Could not create tide realm" (`email is null or empty`). VERIFIED.
166
+ **Anti-pattern:** Sending JSON to this endpoint (instead of `x-www-form-urlencoded`). Causes the setup to fail with "Tide realm setup failed" / "Could not set up the Tide realm". VERIFIED.
167
167
 
168
168
  ### toggle-iga
169
169
 
@@ -173,16 +173,21 @@ Order matters. Do not skip or reorder steps.
173
173
 
174
174
  ### Approve/commit change requests
175
175
 
176
- - Sign: `POST /admin/realms/{realm}/tide-admin/change-set/sign`
177
- - Commit: `POST /admin/realms/{realm}/tide-admin/change-set/commit`
178
- - Both accept JSON: `{ "changeSetId": "...", "changeSetType": "...", "actionType": "..." }`
179
- - For batch: use `/sign/batch` and `/commit/batch` with `{ "changeSets": [...] }`
176
+ Current `/iga/change-requests/...` surface (replaces legacy `/tide-admin/change-set/...`, GAP-065). **Full spec + bootstrap loop: `canon/iga-change-requests-api.md`.**
177
+ - List pending: `GET /admin/realms/{realm}/iga/change-requests?status=PENDING` (objects keyed by `id`)
178
+ - Authorize (sign): `POST /admin/realms/{realm}/iga/change-requests/{id}/authorize` (body `{}` optional; records an approval, does **not** commit; simple firstAdmin/Tideless lane — refuses multiAdmin CRs)
179
+ - Batch authorize (bootstrap): `POST /admin/realms/{realm}/iga/change-requests/bulk-authorize` with `{ "actionTypeIn": ["CREATE","DELETE"], "limit": 100 }`
180
+ - Approve (multiAdmin): `POST /admin/realms/{realm}/iga/change-requests/{id}/approve` (enclave lane; approves AND auto-commits once quorum met — the console "Authorize" button)
181
+ - Commit: `POST /admin/realms/{realm}/iga/change-requests/{id}/commit` (explicit apply-only; 412 if under threshold / unmet dependency)
182
+ - FirstAdmin/Tideless: authorize signs server-side. Tide MultiAdmin: use the `{id}/approval-model` enclave exchange, then `approve`.
183
+
184
+ > **Legacy-bootstrap note.** The bootstrap init script (`init_tidecloak.sh`) on the keycloak-IGA fork still drives the **legacy** `/tide-admin/change-set/*` surface (`sign`/`commit`, and `/sign/batch` + `/commit/batch` with `{ "changeSets": [...] }`). That surface is stale — it is kept here only to explain what the existing script calls; reach for `/iga/change-requests/*` in new integration code.
180
185
 
181
186
  ### Adapter config export
182
187
 
183
188
  - Path: `GET /admin/realms/{realm}/vendorResources/get-installations-provider`
184
189
  - Query: `clientId={uuid}&providerId=keycloak-oidc-keycloak-json`
185
- - The `jwk` field is only present when IGA is enabled.
190
+ - The `jwk` field is injected only when a `tide-vendor-key` component and its EdDSA signing key exist on the realm. `setUpTideRealm` creates both, so `jwk` appears once licensing has run. It is **not** gated on IGA. If `jwk` is missing, the remedy is to ensure `setUpTideRealm` ran successfully (not to enable IGA).
186
191
 
187
192
  ---
188
193
 
@@ -249,7 +254,7 @@ When bootstrapping realms programmatically on a hosted TideCloak instance (e.g.,
249
254
 
250
255
  ### Two-Phase Provisioning
251
256
 
252
- Role assignment MUST happen after the admin user links their Tide account. IGA change requests require a Tide doken to be signable. Without a linked identity, the sign endpoint returns 200 but the change-set remains in DRAFT permanently.
257
+ Role assignment MUST happen after the admin user links their Tide account. IGA change requests require a Tide doken to be signable. Without a linked identity, the authorize endpoint returns 200 but the change request remains unsigned/uncommitted permanently.
253
258
 
254
259
  **Phase A** (automated, before user interaction):
255
260
  Steps 1–9 from the standard sequence, EXCEPT step 8c (role assignment). Generate the invite link at the end.
@@ -35,21 +35,16 @@ GET /realms/{realm}/public/get-tide-config?clientId={clientId}
35
35
 
36
36
  Public endpoint. Returns `vendorId`, `homeOrkUrl`, client auth origins, and branding URLs for a client. No auth required.
37
37
 
38
- ### Admin Policy (for E2EE)
38
+ ### Role / Encryption Policies
39
39
 
40
- ```
41
- GET /realms/{realm}/tide-policy-resources/admin-policy
42
- ```
43
-
44
- Public endpoint. Returns the base64-encoded admin policy needed for policy-based encryption operations. Also available as `/admin-policy/bytes` (raw) and `/admin-policy/display` (human-readable).
45
-
46
- ### Role Policy
40
+ Signed role/encryption policies are managed through the admin IGA surface:
47
41
 
48
42
  ```
49
- GET /realms/{realm}/tide-policy-resources/policy?roleId={id}&clientId={id}
43
+ GET /admin/realms/{realm}/iga/role-policies
44
+ GET /admin/realms/{realm}/iga/role-policies/name/{name}
50
45
  ```
51
46
 
52
- Public endpoint. Returns the Midgard policy for a specific role on a client.
47
+ Each role-policy record carries the signed `policy` bytes and `policySig` used by policy-based encryption/decryption. The older public `tide-policy-resources/admin-policy` and `.../policy` endpoints are not present on current main (`tidecloak-iga-extensions`); see `canon/custom-contracts.md`.
53
48
 
54
49
  ### Vouchers
55
50
 
@@ -73,20 +68,18 @@ GET /realms/{realm}/tide-idp-resources/clientIframe/iss/{iss}/aud/{aud}/tide_dpo
73
68
 
74
69
  Serves the DPoP authentication iframe HTML. `iss` and `aud` are hex-encoded. The SDK uses this internally.
75
70
 
76
- ### Token Exchange (Delegation)
71
+ ### Token Exchange (RFC 8693)
77
72
 
78
73
  ```
79
74
  POST /realms/{realm}/protocol/openid-connect/token
80
75
  ```
81
76
 
82
- Standard OIDC token endpoint used for delegation token exchange:
77
+ Standard OIDC token endpoint used for RFC 8693 token exchange (server-to-server, e.g. a backend calling a downstream protected service on behalf of the user):
83
78
  - `grant_type=urn:ietf:params:oauth:grant-type:token-exchange`
84
79
  - `subject_token=<user access token>`
85
- - `actor_token=<browser-signed delegation request>`
86
- - `dpop_approval=<session key approval>`
87
- - `DPoP` header: server's DPoP proof
80
+ - `actor_token=<acting-party token>` (optional)
88
81
 
89
- See `canon/delegation.md` for full flow.
82
+ For the shipped .NET wiring, see `playbooks/protect-aspnet-core-asgard.md` (Step 5). Browser-driven server-side delegation (the asgard-tide Node SDK, the same one keylessh uses) is unmerged and intentionally not documented here.
90
83
 
91
84
  ---
92
85
 
@@ -134,113 +127,82 @@ Generates a link for the admin user to link their Tide account. Time-limited (de
134
127
 
135
128
  ---
136
129
 
137
- ## IGA Change-Set Endpoints
130
+ ## IGA Change-Request Endpoints
138
131
 
139
- Used for multi-admin governance approval flows.
132
+ Used for multi-admin governance approval flows. **Current surface: `/iga/change-requests/...`** (iga-core), confirmed by Tide 2026-07-07 (GAP-065). This **replaces** the legacy `/tide-admin/change-set/...` surface. Full spec, payloads, status codes, and the bootstrap approve/commit loop: **`canon/iga-change-requests-api.md`**.
140
133
 
141
- ### List Pending Changes
134
+ ### List / get change requests
142
135
 
143
136
  ```
144
- GET /admin/realms/{realm}/tide-admin/change-set/users/requests
145
- GET /admin/realms/{realm}/tide-admin/change-set/roles/requests
146
- GET /admin/realms/{realm}/tide-admin/change-set/clients/requests
147
- GET /admin/realms/{realm}/tide-admin/change-set/groups/requests
148
- GET /admin/realms/{realm}/tide-admin/change-set/all/requests
149
- GET /admin/realms/{realm}/tide-admin/change-set/counts
137
+ GET /admin/realms/{realm}/iga/change-requests?status=PENDING # PENDING|APPROVED|DENIED|CANCELLED
138
+ GET /admin/realms/{realm}/iga/change-requests/{id}
150
139
  ```
151
140
 
152
- ### Sign and Commit
141
+ Each CR object is keyed by `id` (replaces legacy `draftRecordId`), with `entityType`, `actionType`, `status`, `readyToCommit`, `threshold`, `authorizers[]`, `dependsOn`/`blocked`.
153
142
 
154
- ```
155
- POST /admin/realms/{realm}/tide-admin/change-set/sign
156
- Body: { "changeSetId": "...", "changeSetType": "...", "actionType": "..." }
143
+ ### Authorize (sign) and Commit
157
144
 
158
- POST /admin/realms/{realm}/tide-admin/change-set/commit
159
- Body: { "changeSetId": "...", "changeSetType": "...", "actionType": "..." }
145
+ ```
146
+ POST /admin/realms/{realm}/iga/change-requests/{id}/authorize Body: {} (optional) → 200/403/409
147
+ POST /admin/realms/{realm}/iga/change-requests/{id}/commit → 200/412
160
148
  ```
161
149
 
162
- Batch variants: `/sign/batch` and `/commit/batch` with `{ "changeSets": [...] }`.
150
+ Batch approve (bootstrap): `POST /admin/realms/{realm}/iga/change-requests/bulk-authorize` with `{ "actionTypeIn": ["CREATE","DELETE"], "limit": 100 }` (429 if a bulk run is already in progress). Bulk authorizes but does not commit — commit the CRs that become `readyToCommit`.
163
151
 
164
- ### Cancel
152
+ ### Deny
165
153
 
166
154
  ```
167
- POST /admin/realms/{realm}/tide-admin/change-set/cancel
168
- Body: { "changeSetId": "...", "changeSetType": "...", "actionType": "..." }
155
+ POST /admin/realms/{realm}/iga/change-requests/{id}/deny → 204
169
156
  ```
170
157
 
171
- ### Approve/Reject (multi-admin)
158
+ ### Two-phase approval (Tide MultiAdmin, enclave)
172
159
 
173
160
  ```
174
- POST /admin/realms/{realm}/tideAdminResources/add-review
175
- Content-Type: multipart/form-data
176
- Fields: changeSetId, changeSetType, actionType, requests
177
-
178
- POST /admin/realms/{realm}/tide-admin/add-rejection
179
- Content-Type: multipart/form-data
180
- Fields: changeSetId, actionType, changeSetType
161
+ GET /admin/realms/{realm}/iga/change-requests/{id}/approval-model → { changeRequestId, requestModel(base64) }
162
+ POST /admin/realms/{realm}/iga/change-requests/{id}/approval-model Body: { "requestModel": "<base64 doken>" }
163
+ { recorded, authCount, threshold }
181
164
  ```
182
165
 
183
- Note: `add-review` and `add-rejection` use `multipart/form-data`. All other change-set endpoints use JSON.
184
-
185
- ### Activity and Comments
166
+ ### Comments and ADOPT
186
167
 
187
168
  ```
188
- GET /admin/realms/{realm}/tide-admin/change-set/{id}/activity
189
- POST /admin/realms/{realm}/tide-admin/change-set/{id}/comments
190
- Body: { "comment": "..." }
169
+ GET/POST /admin/realms/{realm}/iga/change-requests/{id}/comments Body: { "comment": "..." } (≤2000)
170
+ PUT/DELETE /admin/realms/{realm}/iga/change-requests/{id}/comments/{commentId}
171
+ POST /admin/realms/{realm}/iga/adopt Body: { "entityType": "USER", "entityId": "..." } → 201/409
172
+ GET /admin/realms/{realm}/iga/change-requests/{id}/diagnostic-bundle
191
173
  ```
192
174
 
175
+ **Legacy → current:** `change-set/{type}/requests` → `iga/change-requests?status=PENDING`; `change-set/sign[/batch]` → `iga/change-requests/{id}/authorize` (+ `bulk-authorize`); `change-set/commit[/batch]` → `iga/change-requests/{id}/commit`; `change-set/cancel` → `.../{id}/deny`; `tideAdminResources/add-review` → `.../{id}/approval-model`; field `draftRecordId` → `id`. Enabling IGA is unchanged: `POST /admin/realms/{realm}/tide-admin/toggle-iga`.
176
+
193
177
  ---
194
178
 
195
179
  ## Policy and Contract Endpoints
196
180
 
197
- ### Policy Templates
181
+ Served under `@Path("iga")`. (The older `tide-admin/policy-templates`, `tide-admin/ssh-policies`, `tide-admin/realm-policy`, and `role-policy/{id}/init-cert` endpoints no longer exist on main.)
198
182
 
199
- ```
200
- POST /admin/realms/{realm}/tide-admin/policy-templates
201
- GET /admin/realms/{realm}/tide-admin/policy-templates
202
- PUT /admin/realms/{realm}/tide-admin/policy-templates/{id}
203
- DELETE /admin/realms/{realm}/tide-admin/policy-templates/{id}
204
- ```
205
-
206
- Body: `{ name, description, contractCode, modelId, parameters }`
207
-
208
- ### Realm Policy
183
+ ### Role Policies
209
184
 
210
185
  ```
211
- GET /admin/realms/{realm}/tide-admin/realm-policy
212
- POST /admin/realms/{realm}/tide-admin/realm-policy/pending
213
- POST /admin/realms/{realm}/tide-admin/realm-policy/commit
214
- POST /admin/realms/{realm}/tide-admin/realm-policy/request-delete
215
- POST /admin/realms/{realm}/tide-admin/realm-policy/commit-delete
216
- DELETE /admin/realms/{realm}/tide-admin/realm-policy
186
+ GET /admin/realms/{realm}/iga/role-policies
187
+ GET /admin/realms/{realm}/iga/role-policies/{id}
188
+ GET /admin/realms/{realm}/iga/role-policies/name/{name}
189
+ POST /admin/realms/{realm}/iga/role-policies
190
+ DELETE /admin/realms/{realm}/iga/role-policies/{id}
191
+ DELETE /admin/realms/{realm}/iga/role-policies/name/{name}
217
192
  ```
218
193
 
219
- ### SSH Policies (per-role)
220
-
221
- ```
222
- PUT /admin/realms/{realm}/tide-admin/ssh-policies
223
- GET /admin/realms/{realm}/tide-admin/ssh-policies
224
- DELETE /admin/realms/{realm}/tide-admin/ssh-policies?roleId={id}
225
- ```
226
-
227
- Body: `{ roleId, contractCode, approvalType, executionType, threshold, policyData }`
194
+ POST body (`IgaRolePolicyRepresentation`): `{ name, policy, policySig, contractId, approvalType, executionType, threshold, policyData }`. `name`, `policy`, and `policySig` are required (`policySig` ≤ 512 chars).
228
195
 
229
196
  ### Forseti Contracts
230
197
 
231
198
  ```
232
- PUT /admin/realms/{realm}/tide-admin/forseti-contracts
233
- GET /admin/realms/{realm}/tide-admin/forseti-contracts
199
+ GET /admin/realms/{realm}/iga/forseti-contracts
200
+ GET /admin/realms/{realm}/iga/forseti-contracts/{id}
201
+ POST /admin/realms/{realm}/iga/forseti-contracts
202
+ DELETE /admin/realms/{realm}/iga/forseti-contracts/{id}
234
203
  ```
235
204
 
236
- Body: `{ contractCode, name }`
237
-
238
- ### Role Init Cert
239
-
240
- ```
241
- POST /admin/realms/{realm}/tide-admin/role-policy/{roleId}/init-cert
242
- Body: { initCert, initCertSig }
243
- ```
205
+ POST body (`IgaForsetiContractRepresentation`): `{ contractCode, name }`. Returns the stored contract with its `contractHash` (SHA-512 of the source).
244
206
 
245
207
  ---
246
208
 
@@ -250,34 +212,34 @@ Infrastructure-level. Used during initial setup and license rotation.
250
212
 
251
213
  ```
252
214
  POST /admin/realms/{realm}/vendorResources/generate-initial-key
253
- POST /admin/realms/{realm}/vendorResources/generate-initial-vrk-v1
254
- POST /admin/realms/{realm}/vendorResources/confirm-initial-vrk
255
- POST /admin/realms/{realm}/vendorResources/rotate-vrk
256
215
  POST /admin/realms/{realm}/vendorResources/switch-vrk
257
- POST /admin/realms/{realm}/vendorResources/requestFreeTierLicense
258
216
  GET /admin/realms/{realm}/vendorResources/isPendingLicenseActive
259
217
  GET /admin/realms/{realm}/vendorResources/getLicenseDetails
260
218
  GET /admin/realms/{realm}/vendorResources/licenseHistory
261
219
  ```
262
220
 
221
+ Initial license issuance is driven by `setUpTideRealm` (free-tier); ongoing license drafts/trigger/history are handled by the IGA licensing endpoints (`/iga/licensing/*`).
222
+
263
223
  ---
264
224
 
265
225
  ## Utility Endpoints
266
226
 
267
227
  ```
268
- POST /admin/realms/{realm}/vendorResources/sign-message
269
- FormParam: data
270
-
271
228
  GET /admin/realms/{realm}/vendorResources/get-tide-jwk
229
+ ```
272
230
 
273
- POST /admin/realms/{realm}/tide-admin/generate-default-user-context
274
- Body: [clientId1, clientId2, ...]
231
+ ---
232
+
233
+ ## Other IGA Endpoints
275
234
 
276
- POST /admin/realms/{realm}/tide-admin/what-if-token
277
- Body: { changeSetType, actionType, userId, clientId }
235
+ Also served under `@Path("iga")` / `@Path("iga-tve")` (plus a `tide-admin` user-context read):
278
236
 
279
- GET /admin/realms/{realm}/tide-admin/user-context/{userId}/{clientId}
280
- GET /admin/realms/{realm}/tide-admin/change-set/{changeSetId}/user-context
237
+ ```
238
+ GET|POST|DELETE /admin/realms/{realm}/iga/authorizers[/{id}] # realm authorizer (firstAdmin/multiAdmin) config
239
+ GET|POST /admin/realms/{realm}/iga/server-certs[...] # server-identity / mTLS cert issuance (request/issue/revoke/active/instance)
240
+ GET|POST /admin/realms/{realm}/iga/licensing/[...] # license drafts / trigger / history
241
+ POST /admin/realms/{realm}/iga-tve/tve-bundle # TVE attestation-unit bundle export (CBOR/JSON)
242
+ GET /admin/realms/{realm}/tide-admin/user-context/{userId}/{clientId} # effective user context (roles/claims) for a client
281
243
  ```
282
244
 
283
245
  ---