@tideorg/mcp 1.4.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 (76) hide show
  1. package/GAP_REGISTER.md +117 -0
  2. package/README.md +38 -0
  3. package/adapters/AGENTS.md +241 -0
  4. package/adapters/CLAUDE.md +146 -0
  5. package/adapters/replit.md +224 -0
  6. package/canon/anti-patterns.md +2133 -0
  7. package/canon/concepts.md +816 -0
  8. package/canon/custom-contracts.md +533 -0
  9. package/canon/delegation.md +195 -0
  10. package/canon/feature-mapping.md +637 -0
  11. package/canon/framework-matrix.md +1125 -0
  12. package/canon/invariants.md +851 -0
  13. package/canon/redirect-handler.md +254 -0
  14. package/canon/tidecloak-bootstrap.md +287 -0
  15. package/canon/tidecloak-endpoints.md +294 -0
  16. package/canon/troubleshooting.md +1494 -0
  17. package/canon/version-policy.md +89 -0
  18. package/mcp-server/dist/index.d.ts +2 -0
  19. package/mcp-server/dist/index.js +605 -0
  20. package/package.json +45 -0
  21. package/playbooks/add-auth-nextjs-existing.md +799 -0
  22. package/playbooks/add-auth-nextjs-fresh.md +654 -0
  23. package/playbooks/add-rbac-nextjs.md +190 -0
  24. package/playbooks/bootstrap-realm-from-template.md +170 -0
  25. package/playbooks/configure-e2ee-roles-and-policies.md +196 -0
  26. package/playbooks/deploy-tidecloak-docker.md +792 -0
  27. package/playbooks/diagnose-broken-login.md +234 -0
  28. package/playbooks/diagnose-missing-roles-or-claims.md +253 -0
  29. package/playbooks/initialize-admin-and-link-account.md +235 -0
  30. package/playbooks/migrate-from-existing-auth.md +198 -0
  31. package/playbooks/protect-api-nextjs.md +451 -0
  32. package/playbooks/protect-routes-nextjs.md +544 -0
  33. package/playbooks/setup-forseti-e2ee.md +756 -0
  34. package/playbooks/setup-iga-admin-panel.md +344 -0
  35. package/playbooks/setup-server-delegation.md +142 -0
  36. package/playbooks/start-tidecloak-dev.md +130 -0
  37. package/playbooks/verify-jwt-server-side.md +439 -0
  38. package/prompts/add-admin-approval-flow.md +50 -0
  39. package/prompts/build-private-customer-portal.md +85 -0
  40. package/prompts/migrate-generic-auth-to-tide.md +67 -0
  41. package/prompts/secure-existing-app.md +80 -0
  42. package/reference-apps/INDEX.md +87 -0
  43. package/reference-apps/encrypted-communication/anti-patterns.md +123 -0
  44. package/reference-apps/encrypted-communication/bootstrap-sequence.md +152 -0
  45. package/reference-apps/encrypted-communication/manifest.yaml +100 -0
  46. package/reference-apps/encrypted-communication/role-policy-matrix.md +80 -0
  47. package/reference-apps/encrypted-communication/scenario.md +134 -0
  48. package/reference-apps/git-pr-signing-service/anti-patterns.md +61 -0
  49. package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +157 -0
  50. package/reference-apps/git-pr-signing-service/manifest.yaml +80 -0
  51. package/reference-apps/git-pr-signing-service/role-policy-matrix.md +99 -0
  52. package/reference-apps/git-pr-signing-service/scenario.md +169 -0
  53. package/reference-apps/iga-admin-governance/anti-patterns.md +133 -0
  54. package/reference-apps/iga-admin-governance/bootstrap-sequence.md +153 -0
  55. package/reference-apps/iga-admin-governance/manifest.yaml +87 -0
  56. package/reference-apps/iga-admin-governance/role-policy-matrix.md +67 -0
  57. package/reference-apps/iga-admin-governance/scenario.md +126 -0
  58. package/reference-apps/organisation-password-manager/anti-patterns.md +71 -0
  59. package/reference-apps/organisation-password-manager/bootstrap-sequence.md +127 -0
  60. package/reference-apps/organisation-password-manager/manifest.yaml +86 -0
  61. package/reference-apps/organisation-password-manager/role-policy-matrix.md +59 -0
  62. package/reference-apps/organisation-password-manager/scenario.md +107 -0
  63. package/reference-apps/policy-governed-signing/anti-patterns.md +67 -0
  64. package/reference-apps/policy-governed-signing/bootstrap-sequence.md +128 -0
  65. package/reference-apps/policy-governed-signing/manifest.yaml +78 -0
  66. package/reference-apps/policy-governed-signing/role-policy-matrix.md +62 -0
  67. package/reference-apps/policy-governed-signing/scenario.md +113 -0
  68. package/skills/tide-diagnostics/SKILL.md +99 -0
  69. package/skills/tide-integration/SKILL.md +136 -0
  70. package/skills/tide-learning-capture/SKILL.md +174 -0
  71. package/skills/tide-rbac-and-e2ee/SKILL.md +214 -0
  72. package/skills/tide-reviewer/SKILL.md +133 -0
  73. package/skills/tide-route-and-api-protection/SKILL.md +201 -0
  74. package/skills/tide-scenario-resolver/SKILL.md +81 -0
  75. package/skills/tide-setup/SKILL.md +218 -0
  76. package/skills/tide-solutions-architect/SKILL.md +130 -0
@@ -0,0 +1,134 @@
1
+ # Encrypted Real-Time Communication
2
+
3
+ ## What this is
4
+
5
+ A real-time communication app (chat, video calls, or both) where all user content is end-to-end encrypted. TideCloak handles authentication and protects long-lived private key material at rest via self-encryption. A separate client-side crypto library handles runtime E2E operations (message encryption, media frame encryption, key exchange). The server stores only ciphertext and never sees plaintext content or private keys.
6
+
7
+ This is a **hybrid encryption** pattern:
8
+ - **Layer 1 (Tide)**: Self-encryption protects the user's private key stored in the database.
9
+ - **Layer 2 (external crypto)**: A client-side library (e.g., libsodium, WebCrypto) uses the decrypted private key for runtime E2E operations.
10
+
11
+ ---
12
+
13
+ ## When to use this scenario
14
+
15
+ Use when the app needs:
16
+ - Real-time encrypted messaging (1:1 or group)
17
+ - Encrypted voice/video calls
18
+ - Zero-knowledge server (server stores ciphertext only)
19
+ - Per-user asymmetric key pairs with Tide-protected private key storage
20
+ - Forward secrecy via key rotation when group membership changes
21
+ - Runtime encryption that Tide's built-in `doEncrypt`/`doDecrypt` does not cover (e.g., streaming media frame encryption, libsodium `crypto_secretbox`, WebCrypto `AES-GCM`)
22
+
23
+ Do NOT use when:
24
+ - Data is encrypted at rest and does not need real-time streaming decryption — use `organisation-password-manager` instead
25
+ - Only the encrypting user needs to decrypt — use standard self-encryption via `tide-rbac-and-e2ee`
26
+ - The app needs cryptographic signing, not encryption — use `policy-governed-signing`
27
+ - The app only needs authentication with no encryption — use standard auth playbooks
28
+
29
+ ---
30
+
31
+ ## Core Tide capabilities used
32
+
33
+ | Capability | Role in this scenario |
34
+ |-----------|----------------------|
35
+ | TideCloak SSO (OIDC) | Core: user authentication, JWT-based identity |
36
+ | Self-encryption (`doEncrypt`/`doDecrypt`) | Core: encrypt/decrypt user's private key for storage |
37
+ | `_tide_<tag>.selfencrypt` / `_tide_<tag>.selfdecrypt` roles | Core: gate access to self-encryption operations |
38
+ | `_tide_enabled` role | Core: required for all Tide operations |
39
+ | Server-side JWT verification | Required: protect API endpoints that store/serve encrypted key material |
40
+ | DPoP (optional but recommended) | Recommended: bind tokens to client for API protection |
41
+ | IGA | Required: approve role assignments before E2E works |
42
+
43
+ **Not used in the default self-encryption path**: Policy-governed encryption, Forseti contracts, VVK shared encryption, doken-based signing.
44
+
45
+ **Shared encryption variant**: If the app needs Tide-native shared encryption (recipients decrypt ciphertext via `IAMService.doDecrypt` with policy bytes), add `setup-forseti-e2ee` to the playbook sequence. This requires a Forseti contract, admin signing ceremony, and additional roles. See `role-policy-matrix.md` for details. VERIFIED (LEARNINGS-batch-009 L-06).
46
+
47
+ ---
48
+
49
+ ## What must exist before first user access
50
+
51
+ 1. TideCloak running with a licensed, IGA-enabled realm
52
+ 2. Realm roles: `_tide_enabled`, `_tide_<tag>.selfencrypt`, `_tide_<tag>.selfdecrypt` in the default composite
53
+ 3. At least one admin user with `tide-realm-admin` linked and initial change requests committed
54
+ 4. Adapter JSON exported with `jwk` field
55
+ 5. App deployed with TideCloak provider and server-side JWT verification
56
+ 6. Database table for storing encrypted key material (public key + Tide-encrypted private key per user)
57
+
58
+ ---
59
+
60
+ ## Bootstrap-only steps (admin)
61
+
62
+ 1. Start TideCloak container
63
+ 2. Create realm from template with self-encryption roles
64
+ 3. License realm (`setUpTideRealm`)
65
+ 4. Enable IGA (`toggle-iga`)
66
+ 5. Create admin user, assign `tide-realm-admin`, generate invite link
67
+ 6. Admin completes Tide account linking
68
+ 7. Approve and commit initial change requests (client, user, role)
69
+ 8. Sign IDP settings (`sign-idp-settings`)
70
+ 9. Export adapter JSON to app
71
+
72
+ ---
73
+
74
+ ## Runtime user flow
75
+
76
+ On **first login** for a new user:
77
+ 1. User authenticates via TideCloak SSO
78
+ 2. App checks database for existing encrypted key material
79
+ 3. No keys found → app generates a new asymmetric keypair client-side (e.g., `crypto_box_keypair()`)
80
+ 4. App encrypts the private key using `doEncrypt(privateKeyBytes, tag)` (Tide self-encryption)
81
+ 5. App stores the encrypted private key and the public key in the database via a protected API endpoint
82
+ 6. Private key is now available in memory for runtime E2E operations
83
+
84
+ On **returning login**:
85
+ 1. User authenticates via TideCloak SSO
86
+ 2. App fetches encrypted key material from database
87
+ 3. App decrypts the private key using `doDecrypt(encryptedPrivateKey, tag)` (Tide self-encryption)
88
+ 4. Private key is now available in memory for runtime E2E operations
89
+
90
+ **Runtime E2E operations** (not Tide — handled by external crypto library):
91
+ - Key exchange: use the decrypted private key + recipient's public key to derive shared secrets
92
+ - Message encryption: symmetric encryption (e.g., `crypto_secretbox`) with conversation keys
93
+ - Media frame encryption: per-frame symmetric encryption for WebRTC Insertable Streams
94
+ - Key rotation: generate new conversation key when group membership changes, re-encrypt for all members
95
+
96
+ ---
97
+
98
+ ## Default playbook sequence
99
+
100
+ 1. `start-tidecloak-dev`
101
+ 2. `bootstrap-realm-from-template`
102
+ 3. `initialize-admin-and-link-account`
103
+ 4. `configure-e2ee-roles-and-policies`
104
+ 5. `add-auth-nextjs-fresh` (adapt for React/Vite if not Next.js)
105
+ 6. `protect-routes-nextjs`
106
+ 7. `protect-api-nextjs`
107
+ 8. `verify-jwt-server-side`
108
+ 9. `add-rbac-nextjs`
109
+
110
+ ---
111
+
112
+ ## Key diagnostics
113
+
114
+ | Symptom | Likely cause | Fix |
115
+ |---------|-------------|-----|
116
+ | `doEncrypt` fails with "User has not been given any access to 'tag'" | Missing `_tide_<tag>.selfencrypt` role | Assign role, approve via IGA, re-login |
117
+ | `doDecrypt` fails with same error | Missing `_tide_<tag>.selfdecrypt` role | Assign role, approve via IGA, re-login |
118
+ | `doDecrypt` fails on stored ciphertext | User does not own this ciphertext (self-encryption is identity-bound) | Verify the same user encrypted it |
119
+ | Key material API returns 401 | JWT verification failing | Check token, DPoP proof, adapter `jwk` |
120
+ | Private key decrypts but E2E still fails | Runtime crypto library error, not Tide | Debug the external crypto separately |
121
+ | New user cannot decrypt old group messages | Expected: forward secrecy means pre-join messages are inaccessible | Store key history per version if historical access is needed |
122
+ | Login completes but encrypt/decrypt unavailable | Roles not in token yet (120s delay after IGA commit) | Re-login or wait for token refresh |
123
+
124
+ ---
125
+
126
+ ## Intentionally configurable
127
+
128
+ - **Crypto library**: libsodium, WebCrypto, tweetnacl, or any client-side crypto library. Tide does not constrain this choice.
129
+ - **Key type**: Ed25519/X25519 (libsodium), ECDH P-256 (WebCrypto), or any asymmetric scheme. The private key is just bytes to `doEncrypt`.
130
+ - **Encryption tag**: Any short alphanumeric string (e.g., `keys`, `self`, `chat`). Must match the `_tide_<tag>` roles in the realm.
131
+ - **Key storage schema**: The database schema for storing encrypted keys is app-defined. Minimum: user ID, public key, encrypted private key.
132
+ - **Key rotation strategy**: App-defined. Common: rotate conversation key on member join/leave for forward secrecy.
133
+ - **Transport**: WebSocket, WebRTC, HTTP polling, or any transport. Tide does not constrain this.
134
+ - **Message format**: App-defined. Tide only touches the private key storage, not the message wire format.
@@ -0,0 +1,61 @@
1
+ # Anti-Patterns — Git PR Signing Service
2
+
3
+ Scenario-specific mistakes. Each defeats a security property or causes a setup failure. Inherits all anti-patterns from `policy-governed-signing` (AP-PS01 through AP-PS16).
4
+
5
+ ## AP-GS01: Signing commits without webhook signature verification
6
+
7
+ The service receives GitHub webhooks to trigger the signing flow. If it does not verify the HMAC-SHA256 webhook signature, an attacker can forge webhook payloads to trigger signing of arbitrary commits. Verify every webhook payload against the shared secret before processing.
8
+
9
+ ## AP-GS02: Using cosign or gitsign as intermediary
10
+
11
+ cosign is for OCI container image signing. gitsign is for local developer-side signing via Fulcio/Rekor. Neither maps to a server-side PR-based signing service. Use the GitHub Git Data API directly to create signed commits.
12
+
13
+ ## AP-GS03: Signing without quorum — single admin approval
14
+
15
+ The entire point of threshold signing with Forseti governance is that no single individual can authorize a signature. If the Forseti contract threshold is 1, any single compromised admin account can sign arbitrary code. Set threshold >= 2 for production.
16
+
17
+ ## AP-GS04: Trusting GitHub PR metadata without re-validating
18
+
19
+ The webhook payload includes PR metadata (author, branch, reviewers, labels). The Forseti contract's `ValidateData` must parse and validate the actual commit bytes, not trust metadata from the webhook. Webhook payloads are GitHub's representation; the commit bytes are the cryptographic truth.
20
+
21
+ ## AP-GS05: Storing the threshold Ed25519 private key
22
+
23
+ The threshold private key never exists in complete form anywhere. The service receives only the raw 64-byte signature from ORKs, never the key. If you find yourself storing, generating, or assembling a private key, you have broken I-01 (never-whole-key).
24
+
25
+ ## AP-GS06: Skipping SSH wire format wrapping
26
+
27
+ GitHub requires SSH signature format (`BEGIN SSH SIGNATURE` block) for commit verification. Submitting the raw 64-byte Ed25519 signature to the Git Data API will not produce a "Verified" badge. The wrapping is a non-cryptographic transformation that must happen after ORK signing and before the GitHub API call.
28
+
29
+ ## AP-GS07: Using master admin credentials in the service
30
+
31
+ The service must use admin OIDC tokens acquired via browser login for Tide operations (approval collection, signing requests). It must use GitHub App installation tokens for GitHub API calls. Do not embed `admin`/`password` or GitHub personal access tokens in the service. See AP-41.
32
+
33
+ ## AP-GS08: Allowing merge without signing service status check
34
+
35
+ If branch protection does not require the signing service's status check, developers can merge PRs directly — bypassing threshold signing entirely. The branch protection rule is the enforcement point that routes all merges through the service.
36
+
37
+ ## AP-GS09: Signing merge commits for the wrong branch
38
+
39
+ The Forseti contract should validate that the target branch matches the signing role's scope (e.g., `git-sign:main` should only sign merges to `main`). Without this check, a `git-sign:main` approval could be used to sign a merge to any branch.
40
+
41
+ ## AP-GS10: Not verifying GitHub App token permissions at startup
42
+
43
+ The service authenticates to GitHub as a GitHub App installation. If the App's permissions are insufficient (missing `checks:write`, `contents:write`, or `pull_requests:read`), operations fail at runtime with opaque 403 errors. Verify permissions at service startup.
44
+
45
+ ## AP-GS11: Assuming signed commits appear instantly
46
+
47
+ After creating a signed commit via the GitHub Git Data API and updating the branch ref, GitHub may take a few seconds to process the signature verification. Do not assert "Verified" badge presence immediately after the API call. The check run should be updated to success after the ref update, not after badge verification.
48
+
49
+ ## AP-GS12: Re-using admin dokens across PRs
50
+
51
+ Each signing operation should use fresh admin approvals (dokens) specific to that PR's commit bytes. Re-using dokens from a previous PR approval would sign different data than what the admins reviewed. The Forseti contract's `ValidateData` should include PR-specific context to prevent replay.
52
+
53
+ ## AP-GS13: Using GitHub merge API for tree computation
54
+
55
+ `POST /repos/{owner}/{repo}/merges` creates a **real merge commit** and advances the base branch. It is NOT a dry-run. Using it to compute the merge tree SHA causes PRs to be merged without threshold signatures — defeating the entire purpose of the signing service.
56
+
57
+ **Correct**: Use `GET /repos/{owner}/{repo}/git/commits/{headSha}` to read the head commit's tree SHA (no side effects). Build the merge commit payload from the tree SHA and both parent SHAs.
58
+
59
+ **Wrong**: `POST /repos/{owner}/{repo}/merges` with `base` and `head` — this performs a real merge.
60
+
61
+ VERIFIED (LEARNINGS-ratidefy-batch-001 L-14).
@@ -0,0 +1,157 @@
1
+ # Bootstrap Sequence — Git PR Signing Service
2
+
3
+ All steps must complete before the service can sign merge commits. Order matters.
4
+
5
+ ## Phase 1: TideCloak infrastructure
6
+
7
+ 1. **Start TideCloak container**
8
+ - Use `tidecloak-dev` (development) or `tidecloak-stg-dev` (staging with ORK env vars).
9
+ - Staging requires `SYSTEM_HOME_ORK`, `USER_HOME_ORK`, `THRESHOLD_T`, `THRESHOLD_N`.
10
+ - Wait for readiness: `curl http://localhost:8080/health/ready` returns 200.
11
+
12
+ 2. **Obtain bootstrap admin token**
13
+ - `POST /realms/master/protocol/openid-connect/token` with `KC_BOOTSTRAP_ADMIN_USERNAME` / `KC_BOOTSTRAP_ADMIN_PASSWORD`.
14
+
15
+ ## Phase 2: Realm and client setup
16
+
17
+ 3. **Import realm template**
18
+ - `POST /admin/realms` with `realm.json`.
19
+ - Template must declare: realm name, OIDC client for the signing service, `_tide_enabled` role, default-roles composite, protocol mappers.
20
+ - Template should include redirect URIs for the signing service's web UI.
21
+
22
+ 4. **License the realm**
23
+ - `POST /admin/realms/{realm}/vendorResources/setUpTideRealm` with admin email.
24
+ - Content-Type: `application/x-www-form-urlencoded`. Body: `email=admin@example.com`.
25
+
26
+ 5. **Enable IGA**
27
+ - `POST /admin/realms/{realm}/tide-admin/toggle-iga` with form-urlencoded `isIGAEnabled=true`.
28
+ - Must happen after licensing.
29
+
30
+ ## Phase 3: Approve initial change-sets
31
+
32
+ 6. **Approve client change-sets**
33
+ - List: `GET /admin/realms/{realm}/tide-admin/change-set/clients/requests`.
34
+ - For each: sign (`POST .../change-set/sign`) then commit (`POST .../change-set/commit`).
35
+
36
+ ## Phase 4: Admin user setup
37
+
38
+ 7. **Create admin user(s)**
39
+ - `POST /admin/realms/{realm}/users` with username, email, enabled=true.
40
+ - Create at least as many admins as your signing policy threshold requires (e.g., 2 for threshold=2).
41
+
42
+ 8. **Assign `tide-realm-admin` role**
43
+ - Get role ID: `GET /admin/realms/{realm}/clients/{realm-mgmt-client-id}/roles` and find `tide-realm-admin`.
44
+ - Assign: `POST /admin/realms/{realm}/users/{userId}/role-mappings/clients/{realm-mgmt-client-id}`.
45
+
46
+ 9. **Generate account-linking invites**
47
+ - For each admin: `POST /admin/realms/{realm}/tideAdminResources/get-required-action-link?userId={userId}&lifespan=43200` with body `["link-tide-account-action"]`.
48
+ - Each admin must complete linking in a browser.
49
+
50
+ 10. **Wait for account linking**
51
+ - Poll `GET /admin/realms/{realm}/users/{userId}` until `attributes.tideUserKey` and `attributes.vuid` are present for each admin.
52
+
53
+ ## Phase 5: Approve user change-sets
54
+
55
+ 11. **Approve user change-sets**
56
+ - List: `GET /admin/realms/{realm}/tide-admin/change-set/users/requests`.
57
+ - For each: sign then commit.
58
+
59
+ ## Phase 6: IDP settings and adapter export
60
+
61
+ 12. **Sign IDP settings**
62
+ - `POST /admin/realms/{realm}/vendorResources/sign-idp-settings`.
63
+ - Required after any IDP config change.
64
+
65
+ 13. **Export adapter JSON**
66
+ - `GET /admin/realms/{realm}/vendorResources/get-installations-provider?clientId={clientUUID}&providerId=keycloak-oidc-keycloak-json`.
67
+ - Save to `data/tidecloak.json`.
68
+ - Verify `jwk`, `vendorId`, `homeOrkUrl`, `client-origin-auth-*` fields are present.
69
+
70
+ ## Phase 7: Signing role and policy setup
71
+
72
+ 14. **Create signing roles**
73
+ - Create client roles: `git-sign:main`, `git-sign:develop`, etc. as needed.
74
+ - Create service executor role: `signing-service`.
75
+ - Role creation goes through IGA change-sets. Approve and commit.
76
+
77
+ 15. **Assign signing roles**
78
+ - Assign `git-sign:<branch>` roles to admin users who will approve PRs for those branches.
79
+ - Assign `signing-service` role to the service's own identity (if using a service account pattern) or to the admin who triggers the signing execution.
80
+ - Role assignments go through IGA change-sets. Approve and commit.
81
+
82
+ 16. **Create and approve signing policies**
83
+ - Create a PolicySignRequest with:
84
+ - Forseti contract: `GitCommitSigningPolicy` (see role-policy-matrix.md)
85
+ - Parameters: `Role` = `git-sign:main`, `Resource` = client ID, `threshold` = 2
86
+ - Model ID: `BasicCustom<GitSign>:BasicCustom<1>`
87
+ - Contract transport: source + entry type
88
+ - PolicyApproval must be signed and committed so `signed_policy_data` is populated.
89
+ - Repeat for each branch that needs a separate signing policy.
90
+
91
+ ## Phase 8: GitHub App setup
92
+
93
+ 17. **Register GitHub App**
94
+ - Create a GitHub App (or bot account) in the target organization.
95
+ - Permissions required: `checks: write`, `contents: write`, `pull_requests: read`.
96
+ - Subscribe to webhook events: `pull_request`.
97
+ - Set webhook URL to the signing service's webhook endpoint (e.g., `https://signing-service.example.com/api/webhook/github`).
98
+ - Set webhook secret. Store it securely in the service's environment.
99
+
100
+ 18. **Upload threshold public key and configure committer identity**
101
+ - Export the threshold Ed25519 public key from the adapter JSON's `jwk.keys[0].x` field (base64url-encoded 32-byte key).
102
+ - Convert to SSH format: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA<base64> <comment>`.
103
+ - Add it to a GitHub account as a **Signing Key** (Settings → SSH and GPG Keys → type: Signing Key).
104
+ - **Critical**: The commit's `committer.email` must match this GitHub account's email. Mismatch produces `unknown_signature_type` instead of "Verified". VERIFIED (LEARNINGS-ratidefy-batch-001 L-26).
105
+ - **GitHub App limitation**: Bot accounts (e.g., `myapp[bot]`) cannot have SSH keys. Use a dedicated machine user account.
106
+ - GitHub will verify commit signatures against this key.
107
+
108
+ 19. **Install GitHub App on repositories**
109
+ - Install the App on the target repositories (or organization-wide).
110
+ - Verify webhook delivery: push a test PR and confirm the service receives the event.
111
+
112
+ 20. **Configure branch protection**
113
+ - On each target branch, enable branch protection rules:
114
+ - Require status checks to pass: add the signing service's check name.
115
+ - Optionally: require reviews (GitHub-native reviews in addition to Tide-governed approval).
116
+ - This ensures PRs cannot be merged without the signing service's approval.
117
+
118
+ ## Phase 9: Service readiness
119
+
120
+ 21. **Place adapter JSON in service**
121
+ - `data/tidecloak.json` for Next.js service.
122
+
123
+ 22. **Configure environment**
124
+ - `GITHUB_APP_ID` — GitHub App ID
125
+ - `GITHUB_PRIVATE_KEY` — GitHub App private key (for API authentication)
126
+ - `GITHUB_WEBHOOK_SECRET` — webhook HMAC secret
127
+ - `CLIENT_ADAPTER` — (optional) adapter JSON for containerized deployment
128
+
129
+ 23. **Configure CSP**
130
+ - `frame-src '*'` for ORK enclave iframe.
131
+
132
+ 24. **Start service**
133
+ - Service must reach TideCloak at `auth-server-url`.
134
+ - Service must reach GitHub API (`api.github.com`).
135
+ - Service must serve the redirect handler at configured `redirectUri` path.
136
+
137
+ ## Pre-use checklist
138
+
139
+ Before the signing service is operational, verify:
140
+
141
+ - [ ] TideCloak healthy (`/health/ready` returns 200)
142
+ - [ ] Realm licensed and IGA enabled
143
+ - [ ] At least N admins with linked Tide accounts (where N >= signing threshold)
144
+ - [ ] All pending change-sets approved and committed
145
+ - [ ] Signing roles created and assigned (`git-sign:<branch>`, `signing-service`)
146
+ - [ ] Signing PolicyApproval committed with non-empty `signed_policy_data` for each role
147
+ - [ ] Adapter JSON exported with `jwk`, `vendorId`, `homeOrkUrl`
148
+ - [ ] `sign-idp-settings` called
149
+ - [ ] GitHub App registered with correct permissions and webhook events
150
+ - [ ] Threshold Ed25519 public key uploaded to GitHub App/bot as SSH signing key
151
+ - [ ] GitHub App installed on target repositories
152
+ - [ ] Branch protection rules require signing service status check
153
+ - [ ] Webhook delivery confirmed (test PR triggers service)
154
+ - [ ] Service running with JWT + DPoP verification on admin endpoints
155
+ - [ ] Webhook signature verification active (HMAC-SHA256)
156
+ - [ ] Redirect handler present at `redirectUri` path
157
+ - [ ] CSP includes `frame-src '*'`
@@ -0,0 +1,80 @@
1
+ # Git PR Signing Service — Scenario Manifest
2
+
3
+ id: git-pr-signing-service
4
+ title: Git PR Signing Service
5
+ category: threshold-signing
6
+ description: Enterprise PR-based commit signing service. GitHub webhook triggers admin review in web UI; Forseti-governed threshold signing produces signed merge commits with GitHub "Verified" badge. No developer holds a signing key.
7
+
8
+ frameworks:
9
+ - nextjs
10
+
11
+ core_patterns:
12
+ - forseti
13
+ - doken
14
+ - dpop
15
+ - iga
16
+ - oidc
17
+ - threshold-signing
18
+ - policy-signing
19
+ - basic-custom-request
20
+ - policy-sign-request
21
+ - client-roles
22
+ - signing
23
+ - github-webhook
24
+ - github-api
25
+ - ssh-signature-format
26
+
27
+ match_keywords:
28
+ - git signing
29
+ - git commit signing
30
+ - commit signing
31
+ - signed commits
32
+ - verified commits
33
+ - github signing
34
+ - github verified
35
+ - pr signing
36
+ - merge signing
37
+ - sign commits
38
+ - sign merges
39
+ - code signing service
40
+ - enterprise commit signing
41
+ - verified badge
42
+ - git tag signing
43
+ - sign git tags
44
+ - sign pull requests
45
+
46
+ bootstrap_required: true
47
+ requires_admin_preapproval: true
48
+ requires_iga: true
49
+ requires_e2ee: false
50
+ signing_mode: policy-governed
51
+
52
+ default_roles:
53
+ - _tide_enabled
54
+
55
+ policy_roles: []
56
+
57
+ signing_role_pattern: "git-sign:<branch>"
58
+
59
+ default_playbooks:
60
+ - start-tidecloak-dev
61
+ - bootstrap-realm-from-template
62
+ - initialize-admin-and-link-account
63
+ - add-auth-nextjs-fresh
64
+ - protect-routes-nextjs
65
+ - protect-api-nextjs
66
+ - verify-jwt-server-side
67
+ - add-rbac-nextjs
68
+ - setup-github-app-signing
69
+
70
+ discriminating_question: Does the app sign git commits or merges via a server-side service with admin approval? If the app signs SSH challenges, documents, or transactions directly, use `policy-governed-signing` instead.
71
+ valid_only_when: App is a server-side service that receives GitHub webhooks, collects admin approvals, and threshold-signs merge commits via the GitHub API.
72
+
73
+ notes: >
74
+ This is a specialization of policy-governed-signing for the git/GitHub domain.
75
+ The signing service is a Next.js web app — admins authenticate via browser OIDC.
76
+ Developers never interact with Tide directly; they push code and open PRs as normal.
77
+ The service signs merge commits via GitHub Git Data API after Forseti-governed admin approval.
78
+ Ed25519 threshold signatures must be wrapped in SSH wire format for GitHub verification.
79
+ Requires a GitHub App (or bot account) with the threshold Ed25519 public key registered as an SSH signing key.
80
+ cosign and gitsign are NOT used — they target container images and local git signing respectively.
@@ -0,0 +1,99 @@
1
+ # Role-Policy Matrix — Git PR Signing Service
2
+
3
+ ## Tide bootstrap roles
4
+
5
+ | Role | Purpose | Attached policy | Created by | Approved by | Required before first use | Default / optional | Notes |
6
+ |------|---------|----------------|------------|-------------|--------------------------|-------------------|-------|
7
+ | `_tide_enabled` | Enables Tide operations for user | None (gate role) | Realm template | N/A (declared in realm.json) | Yes | Default | Must be in realm.json. Not auto-created. |
8
+ | `tide-realm-admin` | Full realm administration | N/A | Bootstrap script | N/A (first admin) | Yes (at least one) | Required | Client role on `realm-management`, not a realm role. |
9
+
10
+ ## Signing roles (Forseti contract-backed)
11
+
12
+ | Role | Purpose | Attached policy | Created by | Approved by | Required before first use | Default / optional | Notes |
13
+ |------|---------|----------------|------------|-------------|--------------------------|-------------------|-------|
14
+ | `git-sign:<branch>` (e.g., `git-sign:main`) | Authorizes signing merge commits to a specific branch | Forseti contract with `signed_policy_data`; validates commit content, approvers, executor | Admin via service UI | Tide realm admin(s) via IGA | Yes (for that branch) | Per-branch | PolicyApproval must be committed before signing works. |
15
+ | `git-sign:*` (optional) | Authorizes signing merge commits to any branch | Same Forseti contract, wildcard match | Admin via service UI | Tide realm admin(s) via IGA | No | Optional | Use for admins who can approve merges to any branch. |
16
+
17
+ ## Service identity roles
18
+
19
+ | Role | Purpose | Created by | Notes |
20
+ |------|---------|------------|-------|
21
+ | `signing-service` (client role) | Identifies the service as the signing executor | Admin during bootstrap | The Forseti contract's `ValidateExecutor` checks for this role. Ensures only the service (not individual admins) can execute the signing request. |
22
+
23
+ ## Key rules
24
+
25
+ 1. `_tide_enabled` must be declared in `realm.json`. Not auto-created by `setUpTideRealm`.
26
+ 2. Signing roles are client roles on the service's OIDC client, not realm roles.
27
+ 3. Each signing role requires a committed PolicyApproval with `signed_policy_data` before ORKs authorize signing.
28
+ 4. Role assignment changes go through IGA change-sets. Roles appear in JWT/doken after next token refresh (up to 120s delay).
29
+ 5. `tide-realm-admin` is a client role on `realm-management`, not a realm role.
30
+ 6. Admins who review and approve PRs need both `tide-realm-admin` and the relevant `git-sign:<branch>` role.
31
+ 7. The service itself needs a service identity with the `signing-service` role for `ValidateExecutor`.
32
+
33
+ ## Forseti contract for git commit signing
34
+
35
+ ```csharp
36
+ using Ork.Forseti.Sdk;
37
+
38
+ public class GitCommitSigningPolicy : IAccessPolicy
39
+ {
40
+ [PolicyParam] public string Role { get; set; } // e.g. "git-sign:main"
41
+ [PolicyParam] public string Resource { get; set; } // client ID
42
+ [PolicyParam] public int threshold { get; set; } // e.g. 2 admins
43
+
44
+ public PolicyDecision ValidateData(DataContext ctx)
45
+ {
46
+ // Parse the git commit payload
47
+ // Validate:
48
+ // - target branch matches the role suffix
49
+ // - author identity is present
50
+ // - commit message is non-empty
51
+ // Optional checks:
52
+ // - reject commits modifying protected paths (e.g., .github/workflows/)
53
+ // - require commit message format (conventional commits, ticket reference)
54
+ // - enforce repo allow-list
55
+ // - validate PR metadata (reviewers, labels, checks passed)
56
+ return PolicyDecision.Allow();
57
+ }
58
+
59
+ public PolicyDecision ValidateApprovers(ApproversContext ctx)
60
+ {
61
+ var approvers = DokenDto.WrapAll(ctx.Dokens);
62
+ return Decision.RequireAnyWithRole(approvers, Role, Resource, threshold);
63
+ }
64
+
65
+ public PolicyDecision ValidateExecutor(ExecutorContext ctx)
66
+ {
67
+ var executor = new DokenDto(ctx.Doken);
68
+ return Decision.RequireNotExpired(executor)
69
+ .And(Decision.RequireRole(executor, "signing-service", Resource));
70
+ }
71
+ }
72
+ ```
73
+
74
+ **Contract notes**:
75
+ - `ValidateData` receives the commit bytes that will be signed. Parse them to extract branch, author, message, and file changes.
76
+ - `ValidateApprovers` checks that enough admins with the `git-sign:<branch>` role approved the PR.
77
+ - `ValidateExecutor` checks the service's identity, not an individual admin. The service is the entity that submits the signing request after collecting approvals.
78
+ - Use `BasicCustomRequest` (model ID: `BasicCustom<GitSign>:BasicCustom<1>`). The commit content is known at request creation time and does not change.
79
+
80
+ ## Policy parameters
81
+
82
+ | Parameter | Type | Required | Example | Purpose |
83
+ |-----------|------|----------|---------|---------|
84
+ | `Role` | string | Yes | `git-sign:main` | The client role required for approvers |
85
+ | `Resource` | string | Yes | `git-pr-signing-service` | The client ID for role lookup |
86
+ | `threshold` | number | Yes | `2` | Minimum number of admin approvals before signing |
87
+
88
+ ## Approval flow
89
+
90
+ 1. PR opened on GitHub
91
+ 2. Service receives webhook, creates check run (pending)
92
+ 3. Admins log in to service via TideCloak OIDC
93
+ 4. Each admin reviews code in service UI, clicks "Approve" (collects doken as approval)
94
+ 5. When `threshold` approvals collected, service creates `BasicCustomRequest` with commit bytes
95
+ 6. Service attaches admin dokens as approvers and its own doken as executor
96
+ 7. ORKs execute Forseti contract: validate commit, check approver roles, verify service identity
97
+ 8. If contract allows, ORKs produce Ed25519 signature
98
+ 9. Service wraps signature in SSH format, creates signed commit via GitHub API
99
+ 10. Service updates check run to success, merges PR