@tideorg/mcp 1.4.1 → 1.9.0

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 (91) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/GAP_REGISTER.md +19 -9
  3. package/PRIVACY.md +41 -0
  4. package/README.md +204 -120
  5. package/adapters/AGENTS.md +3 -2
  6. package/adapters/CLAUDE.md +1 -1
  7. package/adapters/replit.md +0 -0
  8. package/canon/anti-patterns.md +55 -62
  9. package/canon/concepts.md +46 -34
  10. package/canon/custom-contracts.md +12 -8
  11. package/canon/feature-mapping.md +27 -75
  12. package/canon/framework-matrix.md +69 -22
  13. package/canon/hosting-options.md +111 -0
  14. package/canon/iga-change-requests-api.md +211 -0
  15. package/canon/invariants.md +33 -35
  16. package/canon/redirect-handler.md +0 -0
  17. package/canon/security-gap-mapping.md +506 -0
  18. package/canon/security-runtime-probes.md +177 -0
  19. package/canon/tidecloak-bootstrap.md +84 -15
  20. package/canon/tidecloak-endpoints.md +60 -98
  21. package/canon/troubleshooting.md +201 -53
  22. package/canon/version-policy.md +8 -12
  23. package/mcp-server/dist/http.d.ts +2 -0
  24. package/mcp-server/dist/http.js +46 -0
  25. package/mcp-server/dist/index.d.ts +0 -0
  26. package/mcp-server/dist/index.js +2 -601
  27. package/mcp-server/dist/server.d.ts +2 -0
  28. package/mcp-server/dist/server.js +617 -0
  29. package/package.json +48 -45
  30. package/playbooks/add-auth-nextjs-existing.md +33 -17
  31. package/playbooks/add-auth-nextjs-fresh.md +1 -1
  32. package/playbooks/add-rbac-nextjs.md +7 -2
  33. package/playbooks/bootstrap-realm-from-template.md +33 -18
  34. package/playbooks/configure-e2ee-roles-and-policies.md +0 -0
  35. package/playbooks/deploy-tidecloak-docker.md +31 -28
  36. package/playbooks/diagnose-broken-login.md +0 -0
  37. package/playbooks/diagnose-missing-roles-or-claims.md +2 -2
  38. package/playbooks/initialize-admin-and-link-account.md +22 -19
  39. package/playbooks/migrate-from-existing-auth.md +0 -0
  40. package/playbooks/protect-api-nextjs.md +40 -1
  41. package/playbooks/protect-aspnet-core-asgard.md +313 -0
  42. package/playbooks/protect-routes-nextjs.md +24 -10
  43. package/playbooks/provision-tidecloak-skycloak.md +213 -0
  44. package/playbooks/setup-forseti-e2ee.md +32 -50
  45. package/playbooks/setup-iga-admin-panel.md +113 -171
  46. package/playbooks/start-tidecloak-dev.md +0 -0
  47. package/playbooks/verify-jwt-server-side.md +20 -1
  48. package/prompts/add-admin-approval-flow.md +0 -0
  49. package/prompts/build-private-customer-portal.md +1 -1
  50. package/prompts/migrate-generic-auth-to-tide.md +0 -0
  51. package/prompts/secure-existing-app.md +0 -0
  52. package/prompts/security-gap-analysis.md +55 -0
  53. package/reference-apps/INDEX.md +0 -0
  54. package/reference-apps/encrypted-communication/anti-patterns.md +3 -3
  55. package/reference-apps/encrypted-communication/bootstrap-sequence.md +2 -2
  56. package/reference-apps/encrypted-communication/manifest.yaml +0 -0
  57. package/reference-apps/encrypted-communication/role-policy-matrix.md +0 -0
  58. package/reference-apps/encrypted-communication/scenario.md +2 -2
  59. package/reference-apps/git-pr-signing-service/anti-patterns.md +0 -0
  60. package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +8 -8
  61. package/reference-apps/git-pr-signing-service/manifest.yaml +0 -0
  62. package/reference-apps/git-pr-signing-service/role-policy-matrix.md +0 -0
  63. package/reference-apps/git-pr-signing-service/scenario.md +0 -0
  64. package/reference-apps/iga-admin-governance/anti-patterns.md +18 -16
  65. package/reference-apps/iga-admin-governance/bootstrap-sequence.md +10 -5
  66. package/reference-apps/iga-admin-governance/manifest.yaml +0 -0
  67. package/reference-apps/iga-admin-governance/role-policy-matrix.md +12 -13
  68. package/reference-apps/iga-admin-governance/scenario.md +15 -13
  69. package/reference-apps/organisation-password-manager/anti-patterns.md +0 -0
  70. package/reference-apps/organisation-password-manager/bootstrap-sequence.md +5 -6
  71. package/reference-apps/organisation-password-manager/manifest.yaml +0 -0
  72. package/reference-apps/organisation-password-manager/role-policy-matrix.md +0 -0
  73. package/reference-apps/organisation-password-manager/scenario.md +0 -0
  74. package/reference-apps/policy-governed-signing/anti-patterns.md +0 -0
  75. package/reference-apps/policy-governed-signing/bootstrap-sequence.md +9 -9
  76. package/reference-apps/policy-governed-signing/manifest.yaml +0 -0
  77. package/reference-apps/policy-governed-signing/role-policy-matrix.md +0 -0
  78. package/reference-apps/policy-governed-signing/scenario.md +0 -0
  79. package/skills/tide-diagnostics/SKILL.md +1 -1
  80. package/skills/tide-integration/SKILL.md +9 -18
  81. package/skills/tide-learning-capture/SKILL.md +0 -0
  82. package/skills/tide-mcp-qa/SKILL.md +139 -0
  83. package/skills/tide-rbac-and-e2ee/SKILL.md +5 -5
  84. package/skills/tide-reviewer/SKILL.md +1 -1
  85. package/skills/tide-route-and-api-protection/SKILL.md +0 -0
  86. package/skills/tide-scenario-resolver/SKILL.md +0 -0
  87. package/skills/tide-security-analyst/SKILL.md +182 -0
  88. package/skills/tide-setup/SKILL.md +1 -1
  89. package/skills/tide-solutions-architect/SKILL.md +0 -0
  90. package/canon/delegation.md +0 -195
  91. package/playbooks/setup-server-delegation.md +0 -142
@@ -0,0 +1,506 @@
1
+ # Security Gap Mapping
2
+
3
+ Maps weaknesses commonly found in existing systems to the Tide capability that removes them.
4
+
5
+ This file is the lookup table for the `tide-security-analyst` skill. Each gap entry has:
6
+ - **Detect** — how an agent finds the gap in a real codebase or deployment
7
+ - **Trust concentration** — the single party/artifact you must trust for the current control to hold
8
+ - **Tide replacement** — what Tide provides, with sourcing status
9
+ - **Remediation path** — the playbook sequence
10
+ - **Honesty note** — what the Tide replacement does NOT cover
11
+
12
+ **Critical rule**: A gap analysis that cannot name the trust concentration is not a finding. "Uses passwords" is not a gap. "Password verification collapses to trust in one database and one server process" is.
13
+
14
+ **Severity rubric** (aligned with `canon/invariants.md` failure severities):
15
+
16
+ | Severity | Meaning |
17
+ |----------|---------|
18
+ | **CRITICAL** | One compromised party forges identity, mints authority, or reads all sensitive data |
19
+ | **HIGH** | One compromised party escalates privilege or replays/extends stolen authority |
20
+ | **MEDIUM** | Weakness is exploitable but requires an additional foothold |
21
+ | **INFO** | Architectural trust concentration with no immediate exploit path |
22
+
23
+ Detection commands below are operator guidance — **ASSUMED** unless tagged otherwise. Tide capability claims cite `canon/feature-mapping.md` and `canon/invariants.md`, which carry their own VERIFIED sourcing.
24
+
25
+ ---
26
+
27
+ ## SG-01: Central password verification (stored hashes)
28
+
29
+ **What it looks like**: Password hashes (bcrypt/argon2/scrypt/PBKDF2) in the application database, or a single IdP that verifies passwords against its own store.
30
+
31
+ **Detect**:
32
+ ```bash
33
+ grep -rn "bcrypt\|argon2\|scrypt\|pbkdf2\|password_hash\|hashedPassword" --include="*.ts" --include="*.js" --include="*.py" --include="*.cs" --include="*.java" -i .
34
+ # Any hit in a login/registration path = central verification
35
+ grep -rn "compare\(.*password\|verifyPassword\|checkPassword" -i src/ server/ app/ 2>/dev/null
36
+ # Schema check: look for password/hash columns in migrations or ORM models
37
+ ```
38
+
39
+ **Trust concentration**: The database and every process that can read it. Dump the table → offline cracking of the entire user base. The verifying server sees the plaintext password on every login.
40
+
41
+ **Severity**: CRITICAL (for the credential store), even when hashing is strong — hashing changes attack cost, not attack surface.
42
+
43
+ **Tide replacement**: Threshold password verification (PRISM) across T+ ORKs. No password hash stored anywhere. Each ORK verifies the password challenge independently. An attacker compromising the TideCloak server cannot learn passwords. **VERIFIED** (`canon/feature-mapping.md` — Login/SSO; I-02, I-09).
44
+
45
+ **Remediation path**: `migrate-from-existing-auth` → `add-auth-nextjs-existing` (or framework equivalent via `canon/framework-matrix.md`).
46
+
47
+ **Honesty note**: Tide removes the stored-credential single point. It does not stop a user from choosing a weak password or being phished into a fake login page on a domain you don't control.
48
+
49
+ ---
50
+
51
+ ## SG-02: Token-signing key held whole in one place
52
+
53
+ **What it looks like**: JWT/session signing secret in an env var, config file, KMS entry, or IdP database (`JWT_SECRET`, `jwt.sign(payload, secret)`, HS256 shared secrets, a single RS256 private key, Keycloak/Auth0/Cognito realm keys).
54
+
55
+ **Detect**:
56
+ ```bash
57
+ grep -rn "JWT_SECRET\|SIGNING_KEY\|PRIVATE_KEY\|jwt.sign\|SignedCookie\|session_secret" -i . --include="*.env*" --include="*.ts" --include="*.js" --include="*.yaml" --include="*.json"
58
+ grep -rn "HS256\|HS384\|HS512" -i src/ server/ lib/ 2>/dev/null
59
+ # HS* = symmetric: every verifier can also mint. Even worse concentration.
60
+ ```
61
+
62
+ **Trust concentration**: Whoever holds the key mints unlimited valid identity for every user and role. Server compromise, leaked env, CI logs, or a malicious insider = silent, undetectable token forgery.
63
+
64
+ **Severity**: CRITICAL.
65
+
66
+ **Tide replacement**: JWTs are threshold-signed by the VVK across T+ ORKs; each VVK ORK independently verifies claims before partial-signing; no complete signing key exists anywhere at any point (I-01 Never-Whole-Key, I-02). Compromising TideCloak itself does not allow token forgery (I-09). **VERIFIED** (`canon/invariants.md` I-01/I-02/I-09; `canon/feature-mapping.md` — JWT Verification).
67
+
68
+ **Remediation path**: `deploy-tidecloak-docker` → `bootstrap-realm-from-template` → `initialize-admin-and-link-account` → `verify-jwt-server-side`.
69
+
70
+ **Honesty note**: The relying application must still verify tokens correctly (SG-04, SG-10). Threshold signing does not help if the API never checks the signature.
71
+
72
+ ---
73
+
74
+ ## SG-03: Bearer tokens with no proof-of-possession
75
+
76
+ **What it looks like**: `Authorization: Bearer <token>` accepted as-is. A stolen token (XSS, logs, proxies, browser extensions) is replayable until expiry from any machine.
77
+
78
+ **Detect**:
79
+ ```bash
80
+ grep -rn "Authorization.*Bearer" -i server/ src/ app/ api/ 2>/dev/null
81
+ grep -rn "dpop\|cnf.jkt\|mtls\|token_binding" -i server/ src/ 2>/dev/null
82
+ # Bearer present + no DPoP/mTLS/cnf handling = replayable tokens
83
+ # Also check token lifetime: long-lived bearer tokens amplify this gap
84
+ ```
85
+
86
+ **Trust concentration**: Every channel and store the token transits — browser storage, log pipelines, proxies. Any one leak = full session takeover for the token lifetime.
87
+
88
+ **Severity**: HIGH.
89
+
90
+ **Tide replacement**: DPoP (RFC 9449) token binding. The SDK generates an ephemeral key pair; every request carries a fresh proof; server verifies method, URL, freshness, `jti` replay cache, and `cnf.jkt` thumbprint. A stolen access token is useless without the private key. DPoP is required for Tide's full security guarantees. **VERIFIED** (`canon/feature-mapping.md` — DPoP; I-12).
91
+
92
+ **Remediation path**: `protect-api-nextjs` → `verify-jwt-server-side` (DPoP verification steps included), client config per I-12 bidirectional lockstep.
93
+
94
+ **Honesty note**: DPoP binds the token to the browser's key. It does not protect against an attacker who fully controls the victim's live browser session (they can drive the real key).
95
+
96
+ ---
97
+
98
+ ## SG-04: Client-side-only authorization
99
+
100
+ **What it looks like**: Role checks, route guards, or middleware redirects in frontend code, with APIs that trust the client already checked. The most common gap in AI-generated and rapid-prototype apps.
101
+
102
+ **Detect**:
103
+ ```bash
104
+ # Find client-side gating
105
+ grep -rn "hasRole\|isAdmin\|user.role\|roles.includes" src/ app/ components/ --include="*.tsx" --include="*.jsx" 2>/dev/null
106
+ # Then check whether APIs verify independently
107
+ grep -rLn "jwtVerify\|verifyJWT\|verifyToken" app/api/ pages/api/ server/routes/ 2>/dev/null
108
+ # Any API file in the second list that handles sensitive data = finding
109
+ # Next.js middleware-only protection counts: middleware redirects are UX, not enforcement
110
+ ```
111
+
112
+ **Trust concentration**: The attacker's own browser. There is no server-side control at all — curl bypasses everything.
113
+
114
+ **Severity**: CRITICAL.
115
+
116
+ **Tide replacement**: Server-side verification of threshold-signed JWTs with embedded JWKS, plus role checks after signature verification (I-03, I-04, I-08). UI gating stays — as UX only. **VERIFIED** (`canon/invariants.md` I-03/I-08; `canon/feature-mapping.md` — Protected Routes vs Protected APIs).
117
+
118
+ **Remediation path**: `protect-api-nextjs` → `verify-jwt-server-side` → `add-rbac-nextjs`.
119
+
120
+ **Honesty note**: This gap is fixable with any competent server-side auth. What Tide adds on top: the verified signature is threshold-produced (SG-02), so fixing SG-04 with Tide also removes the signing-key single point instead of relocating it.
121
+
122
+ ---
123
+
124
+ ## SG-05: Unprotected or partially protected APIs
125
+
126
+ **What it looks like**: API endpoints with no authentication at all, or a mix where some routes verify and others were forgotten. Common after incremental growth: the first routes got auth, the last five didn't.
127
+
128
+ **Detect**:
129
+ ```bash
130
+ # Enumerate all API routes, then diff against routes that verify
131
+ find app/api pages/api server/routes -name "*.ts" -o -name "*.js" 2>/dev/null
132
+ grep -rln "jwtVerify\|verifyJWT\|authenticate\|requireAuth" app/api/ pages/api/ server/routes/ 2>/dev/null
133
+ # The set difference is the finding. Confirm each unprotected route's data sensitivity before reporting.
134
+ # Runtime check (no repo access needed):
135
+ curl -s -o /dev/null -w "%{http_code}" https://target/api/<suspected-route>
136
+ # 200 without credentials on a sensitive route = confirmed
137
+ ```
138
+
139
+ **Trust concentration**: None exists — that is the gap. Obscurity of the route name is the only control.
140
+
141
+ **Severity**: CRITICAL for sensitive data/mutations, MEDIUM for public-by-design routes misclassified during triage.
142
+
143
+ **Tide replacement**: Same enforcement layer as SG-04. Every protected route verifies the threshold-signed JWT server-side before acting (I-03).
144
+
145
+ **Remediation path**: `protect-api-nextjs` → `verify-jwt-server-side`, applied per-route with the `tide-route-and-api-protection` skill's diagnostic table.
146
+
147
+ **Honesty note**: Route-by-route coverage is an audit discipline, not a product feature. The analysis must enumerate routes exhaustively; a sampled audit gives false assurance.
148
+
149
+ ---
150
+
151
+ ## SG-06: Server-readable sensitive data (no E2EE, or server-held encryption keys)
152
+
153
+ **What it looks like**: Sensitive fields (SSNs, health data, documents, secrets) in plaintext at rest; or "encrypted at rest" where the app server holds the decryption key (KMS envelope encryption, `ENCRYPTION_KEY` env var, TDE only).
154
+
155
+ **Detect**:
156
+ ```bash
157
+ grep -rn "ENCRYPTION_KEY\|createCipheriv\|createDecipheriv\|AES\|kms.decrypt" -i src/ server/ lib/ 2>/dev/null
158
+ # Encryption code + server-resident key = server can read everything (relocated trust, not removed)
159
+ # Schema review: identify sensitive columns, check whether any encryption exists at all
160
+ # Ask: who can read this data? If the answer includes "the server" or "the DBA", it is a finding
161
+ ```
162
+
163
+ **Trust concentration**: The application server, its key store, and every admin of either. DB dump + key = full disclosure. "Encryption at rest" against a live server compromise is close to no encryption.
164
+
165
+ **Severity**: HIGH (CRITICAL for regulated data classes).
166
+
167
+ **Tide replacement**: Hermetic E2EE. Session keys are threshold-encrypted via CVK; decryption requires live Fabric participation by T+ ORKs; plaintext never exists on the server, admin console, or any ORK; access is enforced cryptographically against roles in the threshold-signed JWT. **VERIFIED** (`canon/feature-mapping.md` — E2EE; I-11). Two models — self-encryption vs policy-governed shared encryption — and they are different architectures (I-17): resolve which one before building.
168
+
169
+ **Remediation path**: `configure-e2ee-roles-and-policies`; `setup-forseti-e2ee` if multiple users must decrypt the same ciphertext.
170
+
171
+ **Honesty note**: E2EE requires online Fabric access — no offline decryption (I-11). Server-side batch processing of plaintext (search, analytics) stops working by design; flag this as a product decision, not a footnote. Data already exfiltrated before migration stays exposed.
172
+
173
+ ---
174
+
175
+ ## SG-07: Unilateral admin power
176
+
177
+ **What it looks like**: One admin account (or any member of an `admins` group) can create users, grant roles, change clients/config with no second approval. Includes the IdP root account and "break-glass" accounts that see daily use.
178
+
179
+ **Detect**:
180
+ ```bash
181
+ # IdP config: does any change flow require a second approver? Usually no.
182
+ # App-level: look for admin mutation endpoints with single-role checks
183
+ grep -rn "role.*admin\|isAdmin" server/ app/api/ 2>/dev/null | grep -i "create\|grant\|assign\|delete\|update"
184
+ # Process check (ask the operator): can one person grant themselves a role in production?
185
+ ```
186
+
187
+ **Trust concentration**: Each individual admin account. One phished admin = backdoor accounts, silent privilege grants, audit-log tampering. Procedural review (tickets, PR-style approval) is bypassable by whoever operates the console.
188
+
189
+ **Severity**: HIGH (CRITICAL when the same admin also controls the token-signing IdP — combine with SG-02).
190
+
191
+ **Tide replacement**: IGA / QEA (Quorum Enforced Authorization). Privileged admin writes are captured as **change requests** (the write returns HTTP 202 + a CR id and nothing applies) that must be **authorized (signed) then committed (replayed)** before they take effect. Same admin re-signing is rejected (four-eyes / 409); commit fails with 412 until the approval count meets the threshold. **The strength of this depends on the realm's mode** (`iga.attestor`):
192
+ - **Tide mode** (`iga.attestor=tide`, licensed realm): approvals are cryptographic — sealed VRK→Midgard→ORK network; no single admin, server, or vendor can bypass. This is the mode that delivers the "no single point of bypass" property (I-09/I-10). multiAdmin threshold = `max(1, floor(0.7 × active tide-realm-admins))`.
193
+ - **Tideless mode** (`iga.attestor=simple` or unset — the default on a non-licensed realm): the "signature" is the admin's recorded **username**; the quorum gate (distinct-signature count ≥ threshold + approver-role check) is enforced by TideCloak's own server logic. **No cryptography, no ORK, no threshold sealing.** It stops a lone honest admin and gives four-eyes, but a compromised TideCloak server or DB admin CAN bypass or forge it — it is a procedural control with a server-enforced gate, not a cryptographic one.
194
+
195
+ **VERIFIED** (mode split, CR lifecycle, thresholds: internal `tide-iga` QA skill + `tidecloak-iga-extensions/docs/qea-iga-api.md`. The change-request API surface is now `/iga/change-requests/...` across the pack — full spec in `canon/iga-change-requests-api.md`; see also the IGA-model note at the end of this file).
196
+
197
+ **Remediation path**: `setup-iga-admin-panel` (IGA is also part of standard bootstrap: `bootstrap-realm-from-template`). To get the cryptographic guarantee, the realm must be **Tide-licensed** (`iga.attestor=tide`), not Tideless.
198
+
199
+ **Honesty note**: Two limits, both load-bearing. (1) **Mode**: only Tide mode is cryptographic — do not claim "no single bypass" for a Tideless realm. Confirm `iga.attestor=tide` before making the strong claim. (2) **Collusion**: even in Tide mode, quorum protects against a compromised *minority* of admins; a colluding or simultaneously-phished quorum still commits. Size the admin set accordingly.
200
+
201
+ ---
202
+
203
+ ## SG-08: Standing privileged credentials in application code
204
+
205
+ **What it looks like**: Master admin username/password, service-account client secrets, or long-lived admin API keys in app config so the backend can call the IdP admin API.
206
+
207
+ **Detect**:
208
+ ```bash
209
+ grep -rn "admin.*password\|client_secret\|service.account\|ADMIN_TOKEN\|master" -i .env* config/ server/ docker-compose* 2>/dev/null
210
+ grep -rn "grant_type=password\|grant_type=client_credentials" -i server/ src/ 2>/dev/null
211
+ ```
212
+
213
+ **Trust concentration**: The credential itself. It is valid 24/7, scoped to everything, and leaks through env dumps, logs, and repo history.
214
+
215
+ **Severity**: CRITICAL.
216
+
217
+ **Tide replacement**: Server-side delegation. The server calls admin APIs on behalf of an authenticated user via short-lived (max 600s) threshold-signed delegation tokens, DPoP-bound through a two-hop chain of trust — no master admin credentials in the app, and delegation can be role-scoped via `requested_roles`. **VERIFIED** (`canon/feature-mapping.md` — Server-Side Delegation; `canon/delegation.md`).
218
+
219
+ **Remediation path**: `setup-server-delegation`.
220
+
221
+ **Honesty note**: Delegation covers admin-API calls driven by a real user's session. Fully autonomous background jobs that need admin power are a different problem — do not present delegation as covering them without checking `canon/delegation.md` for the supported patterns.
222
+
223
+ ---
224
+
225
+ ## SG-09: Procedural-only policy enforcement on high-value operations
226
+
227
+ **What it looks like**: Signing, payments, releases, or data-access decisions enforced by application `if`-statements or workflow tools. Whoever controls the server controls the policy.
228
+
229
+ **Detect**:
230
+ ```bash
231
+ # Find the authorization decision point for the highest-value operation in the system.
232
+ # Ask: if this one process is compromised, does the policy still hold? If no — finding.
233
+ grep -rn "approve\|authorize\|policy" -i server/ src/ | grep -v test | head -40
234
+ ```
235
+
236
+ **Trust concentration**: The single process (or single codebase) evaluating the policy.
237
+
238
+ **Severity**: HIGH for signing/financial operations, MEDIUM otherwise.
239
+
240
+ **Tide replacement**: Forseti policy contracts — real C# executed independently in every ORK's five-layer sandbox; majority of ORKs must approve; a compromised ORK (or your own compromised server) cannot override the policy. **VERIFIED** (`canon/feature-mapping.md` — Forseti; I-15).
241
+
242
+ **Remediation path**: `setup-forseti-e2ee` / `configure-e2ee-roles-and-policies`; scenario match first (`tide_choose_scenario`) — signing scenarios like policy-governed signing have dedicated reference apps.
243
+
244
+ **Honesty note**: Forseti governs operations that flow through Tide (signing, decryption). It does not retro-govern arbitrary business logic that never touches the Fabric. Scope claims precisely.
245
+
246
+ ---
247
+
248
+ ## SG-10: Remote key distribution for token verification
249
+
250
+ **What it looks like**: `createRemoteJWKSet`, fetching `/.well-known/jwks.json` or `/protocol/openid-connect/certs` at runtime to verify tokens.
251
+
252
+ **Detect**:
253
+ ```bash
254
+ grep -rn "createRemoteJWKSet\|jwks_uri\|well-known/jwks\|openid-connect/certs" src/ server/ lib/ 2>/dev/null
255
+ ```
256
+
257
+ **Trust concentration**: The network path and DNS between verifier and key endpoint at every cold start.
258
+
259
+ **Severity**: MEDIUM (an interception requires additional footholds, but silently substitutes the trust root).
260
+
261
+ **Tide replacement**: Embedded JWKS from the adapter JSON, verified local-only via `createLocalJWKSet(config.jwk)`. Missing `jwk` is a bootstrap failure to fix at the source, never a reason to fall back to remote fetch. **VERIFIED** (I-04).
262
+
263
+ **Remediation path**: `verify-jwt-server-side`.
264
+
265
+ **Honesty note**: For non-Tide IdPs, remote JWKS over TLS is standard practice; report this as a hardening delta of the Tide model, not as a vulnerability in the existing system, unless TLS validation is also broken.
266
+
267
+ ---
268
+
269
+ ## SG-11: Homegrown auth or crypto
270
+
271
+ **What it looks like**: Custom session-token formats, hand-rolled password reset tokens, custom crypto (XOR "encryption", homemade JWT parsing, `Math.random()` secrets).
272
+
273
+ **Detect**:
274
+ ```bash
275
+ grep -rn "Math.random\|Date.now().*token\|md5\|sha1(" -i src/ server/ lib/ 2>/dev/null | grep -iv "test\|cache\|etag"
276
+ grep -rn "atob\|btoa\|base64" -i src/ server/ | grep -i "token\|auth\|session" 2>/dev/null
277
+ # Custom JWT parse without signature verification:
278
+ grep -rn "split('.')\|split(\"\.\")" src/ server/ | grep -i "token\|jwt" 2>/dev/null
279
+ ```
280
+
281
+ **Trust concentration**: The author's cryptographic correctness. Usually multiple independent breaks.
282
+
283
+ **Severity**: CRITICAL until proven otherwise.
284
+
285
+ **Tide replacement**: Full replacement of the auth layer with TideCloak (standard OIDC surface, threshold-backed internals), not incremental patching. **VERIFIED** flow: `canon/feature-mapping.md` — Login/SSO.
286
+
287
+ **Remediation path**: `migrate-from-existing-auth` → standard sequence (`add-auth-nextjs-existing` → `protect-routes-nextjs` → `protect-api-nextjs` → `verify-jwt-server-side` → `add-rbac-nextjs`).
288
+
289
+ **Honesty note**: Migration must preserve existing user accounts and behavior — follow the migration playbook's inventory steps; do not rip and replace in one pass.
290
+
291
+ ---
292
+
293
+ ## SG-12: IdP vendor lock-in with no cryptographic exit
294
+
295
+ **What it looks like**: All identity and keys live inside a hosted IdP; leaving means re-enrolling every user; the vendor (or its compromise) is a permanent trust dependency.
296
+
297
+ **Detect**: Architecture question, not a grep: "If you had to leave your IdP in 90 days, what breaks?" Also: who besides you can reset your tenant's admin?
298
+
299
+ **Trust concentration**: The IdP vendor — commercially and cryptographically.
300
+
301
+ **Severity**: INFO (architectural; becomes HIGH during an actual vendor-compromise event).
302
+
303
+ **Tide replacement**: Ragnarok realm offboarding — quorum-approved, one-way key reconstruction that lets a realm exit to standalone Keycloak with zero ORK calls (the sole exception to I-01). **VERIFIED** (I-01 exception; internal QA exemplar exercises the full Backup ON → Trigger → Commit lifecycle).
304
+
305
+ **Remediation path**: No dedicated playbook in this pack yet — see `GAP_REGISTER.md`. Raise with the operator; do not improvise offboarding steps.
306
+
307
+ **Honesty note**: Offboarding surrenders threshold properties by design (keys become whole). It is an exit ramp, not a normal operating mode.
308
+
309
+ ---
310
+
311
+ ## SG-13: JWT algorithm confusion / unverified signature algorithm
312
+
313
+ **What it looks like**: Token verification that trusts the `alg` header, accepts `alg: none`, or verifies an RS256/ES256 token with a key loaded as an HMAC secret (public key used as HS256 secret → attacker forges tokens). Also: decoding a JWT without verifying it at all (`jwt.decode` / `atob` on the payload, then trusting claims).
314
+
315
+ **Detect**:
316
+ ```bash
317
+ grep -rn "algorithms:\s*\[.*none\|alg.*none\|verify.*false\|jwt.decode\|jsonwebtoken.*decode" -i src/ server/ lib/ 2>/dev/null
318
+ # jwt.verify without an explicit algorithms allowlist = accepts whatever the header claims
319
+ grep -rn "jwt.verify\|jwtVerify" -i src/ server/ lib/ 2>/dev/null
320
+ # then confirm each call pins algorithms; a missing allowlist is the finding
321
+ # HS/RS confusion: same key material used for both signing and verifying (symmetric assumption)
322
+ grep -rn "verify.*process.env\|verify.*publicKey.*HS" -i src/ server/ 2>/dev/null
323
+ ```
324
+
325
+ **Trust concentration**: The token's own header. If the verifier lets the token pick the algorithm, the attacker picks it too — `alg: none` or key-confusion means anyone mints valid tokens. This collapses even a well-guarded signing key (SG-02) to nothing.
326
+
327
+ **Severity**: CRITICAL when `alg: none` or unpinned algorithms are accepted; HIGH when tokens are decoded-without-verify on a non-authoritative path.
328
+
329
+ **Tide replacement**: Verification uses the embedded JWKS (`createLocalJWKSet(config.jwk)`) with the algorithm fixed by the key type (EdDSA), no header-driven algorithm selection and no remote key substitution. The signature itself is threshold-produced (SG-02), so a forged header has nothing valid to bind to. **VERIFIED** (`canon/invariants.md` I-04; `canon/feature-mapping.md` — JWT Verification).
330
+
331
+ **Remediation path**: `verify-jwt-server-side` (pins the algorithm and issuer, embedded JWKS only).
332
+
333
+ **Honesty note**: Pinning the algorithm and rejecting `alg: none` is standard-library hygiene that any correct verifier does — report the *current* state as the gap. Tide's contribution is that the verified signature is also threshold-backed, so fixing SG-13 the Tide way removes SG-02 at the same time rather than leaving a whole key behind the now-correct check.
334
+
335
+ ---
336
+
337
+ ## SG-14: Tamperable audit trail on privileged actions
338
+
339
+ **What it looks like**: Admin/security-relevant actions (role grants, config changes, user creation, permission edits) recorded only in application logs or a DB table that the same admins can edit or delete. No cryptographic integrity; the actor who performs an action can also erase the evidence.
340
+
341
+ **Detect**:
342
+ ```bash
343
+ # Look for an audit table/log and ask: who can write/delete it?
344
+ grep -rn "audit\|activity_log\|event_log\|auditLog" -i src/ server/ migrations/ db/ 2>/dev/null
345
+ # If audit rows are written by the app with the same DB role that serves requests,
346
+ # and admins have DB or console access, the trail is tamperable.
347
+ # Process check (ask operator): can an admin delete the record of their own action?
348
+ ```
349
+
350
+ **Trust concentration**: Whoever can write to the log store — application DB role, DBA, or any admin with console access. A compromised admin grants themselves a role and deletes the audit row; the tamper is invisible.
351
+
352
+ **Severity**: HIGH (governance/compliance-critical; CRITICAL when combined with SG-07 unilateral admin — one admin acts *and* covers the trail).
353
+
354
+ **Tide replacement — MODE-DEPENDENT, do not overclaim**: IGA / QEA captures privileged changes as change requests that carry a persisted authorization (signature) record. The tamper-evidence property depends on `iga.attestor`:
355
+ - **Tide mode** (`iga.attestor=tide`, licensed): the authorization is sealed cryptographically (VRK→Midgard→ORK network) and future JWT claims are verified against these proofs — no single admin, server, or DBA can forge or silently retract the record. This is the mode that actually makes the trail tamper-*evident*.
356
+ - **Tideless mode** (`iga.attestor=simple`/unset, default): the record is the approving admin's username stored in TideCloak's DB. It provides a four-eyes approval trail but has **no cryptographic integrity** — a DBA or a compromised TideCloak can alter or delete it just like any other audit table. Against the SG-14 threat (a privileged insider tampering with the store) Tideless mode is **not** a real improvement.
357
+
358
+ **VERIFIED** (mode split: internal `tide-iga` QA skill / `tidecloak-iga-extensions/docs`, 2026-06 staging). Also note: in current staging the Tide-mode `sign()` is still a SHA-256 stub (a known in-flight artifact) — the cryptographic sealing is the intended/target state, so tag a live deployment's actual guarantee against its build.
359
+
360
+ **Remediation path**: `setup-iga-admin-panel` (IGA is part of standard bootstrap: `bootstrap-realm-from-template`). Tamper-evidence requires a **Tide-licensed** realm.
361
+
362
+ **Honesty note**: Two limits. (1) **Mode**: the tamper-evidence claim holds only for Tide mode — for a Tideless realm, report SG-14 as *not meaningfully addressed* and point to standard append-only/audit controls. (2) **Scope**: even in Tide mode, IGA makes tamper-evident only the *authorization decisions it governs* — it is not a general-purpose immutable log for arbitrary application events, which still need their own controls (out of scope, below).
363
+
364
+ ---
365
+
366
+ ## SG-15: Weak session lifecycle (fixation, no rotation, long-lived sessions)
367
+
368
+ **What it looks like**: Session identifiers not rotated after login or privilege change (fixation); very long or non-expiring sessions; refresh tokens that never rotate; logout that doesn't invalidate server-side. Attacker who plants or captures a session id keeps access indefinitely.
369
+
370
+ **Detect**:
371
+ ```bash
372
+ grep -rn "session\|maxAge\|expiresIn\|cookie" -i src/ server/ lib/ 2>/dev/null | grep -iv test | head -40
373
+ # Look for: no regenerate-on-login, maxAge measured in weeks/months, refresh tokens with no rotation
374
+ grep -rn "regenerate\|rotateToken\|session.regenerate" -i src/ server/ 2>/dev/null
375
+ # Absence of session regeneration on the login path = fixation exposure
376
+ ```
377
+
378
+ **Trust concentration**: The lifetime of a single session artifact. One capture (or one pre-set id) = access for the whole (often unbounded) window.
379
+
380
+ **Severity**: MEDIUM (HIGH when sessions are effectively non-expiring or bearer tokens are long-lived — compounds SG-03).
381
+
382
+ **Tide replacement**: Standard OIDC authorization-code flow via TideCloak issues fresh threshold-signed tokens per authentication with bounded lifetimes (`accessTokenLifespan: 600`, `ssoSessionIdleTimeout: 1800`, `ssoSessionMaxLifespan: 36000` in the Tide realm defaults), and DPoP binds each token to a per-session key so a captured token is not replayable (SG-03). **VERIFIED** (`canon/feature-mapping.md` — Realm Initialization, DPoP; I-12).
383
+
384
+ **Remediation path**: `add-auth-nextjs-existing` (or `migrate-from-existing-auth`) to move onto the OIDC flow; `verify-jwt-server-side` for DPoP binding and expiry checks.
385
+
386
+ **Honesty note**: Adopting OIDC token lifetimes fixes fixation and unbounded sessions, but the relying app must still enforce `exp`/`iat` server-side (I-03) and not re-introduce its own long-lived session cookie alongside. Moving to Tide without dropping the legacy session store leaves the gap open.
387
+
388
+ ---
389
+
390
+ ## SG-16: No step-up / second approval on high-value operations
391
+
392
+ **What it looks like**: Irreversible or high-value actions (funds transfer, key export, bulk delete, production release, signing) authorized by the same single session that authorized reading a dashboard. No second factor, no second approver, no policy gate proportional to the stakes.
393
+
394
+ **Detect**:
395
+ ```bash
396
+ # Identify the highest-value operations, then check the authority required to invoke them.
397
+ grep -rn "transfer\|payout\|withdraw\|delete.*all\|export.*key\|sign\|release\|deploy" -i server/ src/ 2>/dev/null | grep -iv test | head -40
398
+ # If the authorization for these is identical to any authenticated request, that is the gap.
399
+ ```
400
+
401
+ **Trust concentration**: The single authenticated session. One phished session or one over-privileged token performs the highest-stakes action with no additional barrier.
402
+
403
+ **Severity**: HIGH for financial/signing/irreversible operations, MEDIUM otherwise.
404
+
405
+ **Tide replacement**: Two complementary mechanisms — Forseti policy contracts require a majority of ORKs to approve an operation against programmable rules (thresholds, executor checks, time windows) before it proceeds; IGA / QEA quorum requires multi-admin approval (change request → authorize → commit) for governed administrative changes. Forseti is always cryptographic (ORK-executed). IGA quorum is cryptographic **only in Tide mode** (`iga.attestor=tide`); in Tideless mode the quorum is username-based and server-enforced (see SG-07). Both make high-value authority multi-party rather than single-session. **VERIFIED** (`canon/feature-mapping.md` — Forseti; IGA mode split from internal `tide-iga` QA skill; I-10, I-15).
406
+
407
+ **Remediation path**: Scenario match first (`tide_choose_scenario` — signing/approval scenarios have dedicated reference apps); then `setup-forseti-e2ee` / `configure-e2ee-roles-and-policies` for operation policy, `setup-iga-admin-panel` for admin-change quorum.
408
+
409
+ **Honesty note**: Two scoping limits. (1) Forseti governs operations that flow through the Fabric (signing, decryption, policy-gated actions) — purely internal business operations that never call Tide are not automatically gated; the operation must be routed through a Tide-authorized flow for the policy to bind. Do not claim blanket step-up over all app actions. (2) For the IGA half, the "cryptographic, can't-be-bypassed" strength applies only to Tide mode — a Tideless realm's quorum is a server-enforced procedural gate.
410
+
411
+ ---
412
+
413
+ ## SG-17: User-held secrets stored server-readable
414
+
415
+ **What it looks like**: Secrets that belong to *users* — stored third-party API tokens, connected-account credentials, personal notes/vault entries, recovery codes — kept in plaintext or under a server-held key, so the application (and anyone who compromises it) can read every user's secrets.
416
+
417
+ **Detect**:
418
+ ```bash
419
+ # Distinguish USER secrets (per-user, user-owned) from INFRA secrets (DB creds, service keys).
420
+ grep -rn "api_key\|access_token\|secret\|credential\|vault\|recovery_code" -i migrations/ db/ src/models server/models 2>/dev/null | grep -iv "process.env\|config"
421
+ # Per-user secret columns stored plaintext or app-decryptable = finding.
422
+ # INFRA secrets in env are SG-14-adjacent ops hygiene, NOT this gap — see honesty note.
423
+ ```
424
+
425
+ **Trust concentration**: The application server and its key store. A DB dump plus the server key (or plaintext) discloses every user's stored secrets at once.
426
+
427
+ **Severity**: MEDIUM (HIGH for high-value stored credentials like connected financial/cloud accounts).
428
+
429
+ **Tide replacement**: Hermetic E2EE. Per-user secrets are encrypted such that decryption requires live Fabric threshold participation and the plaintext never exists on the server or any ORK; access is enforced against roles in the threshold-signed JWT. Use self-encryption when only the owning user decrypts, or policy-governed VVK encryption when defined others must (resolve which per I-17). **VERIFIED** (`canon/feature-mapping.md` — E2EE; I-11). This is the same mechanism as SG-06, applied specifically to secret material.
430
+
431
+ **Remediation path**: `configure-e2ee-roles-and-policies`; `setup-forseti-e2ee` if the secret must be shared/recoverable by others under policy.
432
+
433
+ **Honesty note**: This covers *user-owned* secrets. **Infrastructure secrets** — your own DB passwords, service-account keys, TLS private keys — are NOT this gap and are not Tide's domain; they belong to a secrets manager (out of scope, below). Also: E2EE requires online Fabric (I-11), so any server-side job that needs to *use* a stored user secret autonomously (e.g. a nightly sync using a user's API token) breaks by design — flag this as a product decision, not a footnote.
434
+
435
+ ---
436
+
437
+ ## SG-18: Machine/service identity via shared static secrets
438
+
439
+ **What it looks like**: Service-to-service calls authenticated with long-lived shared secrets, static API keys, or a service account whose credential sits in config on both sides. The same secret is valid indefinitely and grants a fixed, often broad, scope.
440
+
441
+ **Detect**:
442
+ ```bash
443
+ grep -rn "api.key\|service.account\|shared.secret\|X-API-Key\|Bearer.*static\|client_credentials" -i server/ src/ config/ .env* 2>/dev/null
444
+ # Long-lived shared secret between services = the finding
445
+ ```
446
+
447
+ **Trust concentration**: The shared secret. It leaks through config, logs, images, and CI on either side; anyone holding it impersonates the service for the credential's (usually unbounded) life.
448
+
449
+ **Severity**: HIGH.
450
+
451
+ **Tide replacement — PARTIAL, read carefully**: For service calls that act **on behalf of an authenticated user**, server-side delegation replaces the standing shared secret with short-lived (max 600s), DPoP-bound, threshold-signed delegation tokens scoped via `requested_roles` — no static secret in either service. **VERIFIED** (`canon/feature-mapping.md` — Server-Side Delegation; `canon/delegation.md`).
452
+
453
+ **Remediation path**: `setup-server-delegation` — **only** for user-driven service calls.
454
+
455
+ **Honesty note**: This is the most over-claimable mapping in the file — do not oversell it. **Autonomous machine-to-machine identity** with no user in the loop (a cron job, a webhook receiver, a data pipeline authenticating as itself) is NOT covered by delegation and has no clean Tide replacement in this pack today. For those, report the shared-secret gap and point to a secrets manager / workload-identity solution (out of scope, below), and log it against `GAP_REGISTER.md`. Claiming delegation covers headless service identity is a false finding.
456
+
457
+ ---
458
+
459
+ ## Out of scope: gaps Tide does NOT fix
460
+
461
+ Report these when found — classify as `NOT_ADDRESSED_BY_TIDE`, point to standard remediation, and never fold them into the Tide pitch:
462
+
463
+ | Gap class | Why Tide doesn't cover it |
464
+ |-----------|--------------------------|
465
+ | SQL/NoSQL/command injection | Application input handling; orthogonal to identity/keys |
466
+ | XSS / CSRF | Frontend/output encoding and same-site controls (note: DPoP limits what stolen tokens are worth, but XSS in a live session still acts as the user) |
467
+ | SSRF, path traversal | Application-layer input handling |
468
+ | Vulnerable dependencies | Supply chain hygiene (audit tooling, update policy) |
469
+ | Missing rate limiting / brute-force controls | Infra/middleware concern (TideCloak inherits Keycloak's brute-force settings for login, but your APIs need their own) |
470
+ | Infrastructure hardening (open ports, default creds on other services) | Ops concern |
471
+ | Logging/monitoring/alerting gaps | Ops concern |
472
+ | Business-logic flaws (IDOR beyond role checks, workflow abuse) | Tide enforces who; your code still decides what an authorized user may touch |
473
+
474
+ **Anti-pattern (AP-SEC-1)**: Presenting Tide as fixing a gap in this table. It destroys the credibility of the legitimate findings. The analysis is stronger when the out-of-scope list is visibly honest.
475
+
476
+ **Anti-pattern (AP-SEC-2)**: Reporting a trust concentration without evidence (file path, config value, or runtime observation). Every SG finding needs at least one concrete artifact, tagged VERIFIED (observed directly), INFERRED (strongly implied by observed code), or ASSUMED (operator statement, unconfirmed).
477
+
478
+ ---
479
+
480
+ ## IGA-model note (Tide vs Tideless, and a canon divergence)
481
+
482
+ Several findings (SG-07, SG-14, SG-16) depend on IGA / QEA (Quorum Enforced Authorization). Two things an analyst must hold:
483
+
484
+ 1. **Mode determines whether IGA is a cryptographic control.** The realm attribute `iga.attestor` decides it:
485
+ - `iga.attestor=tide` (licensed realm) — approvals sealed VRK→Midgard→ORK. Cryptographic; delivers "no single point of bypass." (Current staging note: the Tide-mode `sign()` is a SHA-256 stub in flight — verify a live deployment's actual build before asserting the full crypto guarantee.)
486
+ - `iga.attestor=simple` or unset (Tideless, the default) — the "signature" is the approving admin's **username**, quorum enforced by TideCloak server logic. Four-eyes and approver-role gating, but **no cryptography**; a compromised server or DBA can bypass/forge. For SG-14 (tamper-evidence) this means Tideless mode is *not* a real fix.
487
+
488
+ **Always confirm `iga.attestor` before making a cryptographic claim.** Detect: realm attribute (`GET /admin/realms/{realm}` → attributes) or presence of a `tide-vendor-key` component (Tide mode) vs its absence (Tideless).
489
+
490
+ 2. **API-surface migration (direction CONFIRMED by Tide 2026-07-07).** The QEA model above (change requests captured as HTTP 202; per-id `authorize` → `commit`; four-eyes 409; quorum-unmet 412; base path `/admin/realms/{realm}/iga/change-requests/...`; batch `POST /iga/bulk-authorize`; enable via `isIGAEnabled="true"` / `POST /tide-admin/toggle-iga`; Phase-6 ADOPT scan) is iga-core's surface and **replaces** the legacy `/admin/realms/{realm}/tide-admin/change-set/sign|commit/batch`. This resolves the earlier "which surface is canonical" question (GAP-065): the new `/iga/change-requests/...` surface is authoritative. Reconciliation is done: the full spec is in `canon/iga-change-requests-api.md`, and canon, playbooks, bootstrap scripts, and reference-apps now use the new surface (the bootstrap approve/commit loop is VERIFIED-against-spec / REQUIRES_RUNTIME_VALIDATION). For a **security analysis** (capability mapping) the mode-split model above is authoritative. For **building/bootstrapping**, follow `canon/iga-change-requests-api.md`. Tracked in `GAP_REGISTER.md` GAP-065.
491
+
492
+ ---
493
+
494
+ ## Verification
495
+
496
+ To check this file is being used correctly by an analysis:
497
+ 1. Every finding cites an SG id from this file (or `NOT_ADDRESSED_BY_TIDE`).
498
+ 2. Every SG finding includes evidence with a confidence tag.
499
+ 3. Every remediation names a playbook that exists in `playbooks/` (run `tide_list playbooks` to confirm).
500
+ 4. The report contains a non-empty out-of-scope section, or an explicit statement that none were found.
501
+
502
+ ## Status Legend
503
+
504
+ - **VERIFIED** — claim sourced from `canon/` files that carry their own verified sourcing, or from operational exemplars
505
+ - **INFERRED** — strongly implied by source material
506
+ - **ASSUMED** — operator guidance (all detection commands unless tagged otherwise)