chore(ci): add fork-side SSO audit script + workflow#9
Conversation
Adds a per-fork deterministic audit that catches regressions of the cross-app SSO contract in Pressingly/twenty BEFORE they reach foss-server-bundle-devstack. Mirrors the pattern landed in Pressingly/plane#32. Rows covered (per awais786/sso-rules-moneta:openspec/specs/proxy-auth- middleware/spec.md): Row 14 — SPA logout (packages/twenty-front/src/modules/auth/hooks/ useAuth.ts) MUST NOT call /oauth2/sign_out. Per-app Logout is navigation-only via buildPortalUrl. Row 20 — JwtAuthGuard (packages/twenty-server/src/engine/guards/ jwt-auth.guard.ts) MUST call response.clearCookie('tokenPair', ...) on identity mismatch, gated on AUTH_TYPE === 'SSO'. SECURITY-CRITICAL. Row 21 — Email-shape detection MUST NOT use polynomial-backtracking regex. Twenty uses indexOf-based detection in normalizeProxyEmail (post-PR #8); this row is a regression guard. Workflow (.github/workflows/sso-audit.yml): - Runs on PRs touching auth code or the audit itself - Pushes to foss-main, weekly cron, manual dispatch - Posts sticky PR comment via marocchino/sticky-pull-request-comment@v2 - Skips comment on fork PRs (read-only token), continue-on-error guard - Exits 1 on security-critical violations → merge blocked Local dry-run on foss-main: row 20 ❌ (jwt-auth.guard.ts lacks the clearCookie call). Exit 1. Confirms the gate works. Will go green once #8 (or the equivalent fix) merges. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Twenty SSO Fork AuditCross-app contract: https://github.com/awais786/sso-rules-moneta/blob/main/openspec/specs/proxy-auth-middleware/spec.md
1 violations. Security-critical (row 20): 1. |
There was a problem hiding this comment.
Pull request overview
Adds a fork-side CI audit to enforce a cross-app SSO contract for Twenty, aiming to catch SSO regressions (especially stale-session identity leaks) before they reach downstream bundles.
Changes:
- Add
scripts/sso-audit.shto deterministically audit specific SSO contract rows (14/20/21) in the codebase. - Add
.github/workflows/sso-audit.ymlto run the audit on relevant PRs, onfoss-mainpushes, and on a weekly schedule; publish results to job summary and (for same-repo PRs) a sticky PR comment. - Block merges when the audit exits non-zero (intended for security-critical failures).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
scripts/sso-audit.sh |
Implements the deterministic SSO contract checks and emits a markdown report + exit code policy. |
.github/workflows/sso-audit.yml |
Runs the audit in GitHub Actions, posts summaries/comments, and enforces failures for merge gating. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| local has_clear_cookie | ||
| has_clear_cookie=$(grep -cE "clearCookie\(\s*['\"]tokenPair['\"]" "$JWT_GUARD" || true) | ||
|
|
||
| local has_auth_type | ||
| has_auth_type=$(grep -cE "AUTH_TYPE" "$JWT_GUARD" || true) | ||
|
|
||
| if [[ "$has_clear_cookie" -gt 0 && "$has_auth_type" -gt 0 ]]; then | ||
| record 1 "✅" "$JWT_GUARD contains \`clearCookie('tokenPair'\` gated on AUTH_TYPE — Rule 2 mismatch flush in place" | ||
| return |
There was a problem hiding this comment.
Addressed across commits dd2be34, 1ae5de3, c874245 — Row 20 now uses an awk-based proximity check requiring all four signals (clearCookie('tokenPair', AUTH_TYPE === 'SSO', X-Auth-Request-Email header read, and data.user.email comparison) to co-occur within a configurable line window. Unrelated AUTH_TYPE references elsewhere in the file can no longer false-pass.
| push: | ||
| branches: [foss-main] | ||
| schedule: | ||
| # 09:00 UTC every Monday — pre-week sanity check | ||
| - cron: '0 9 * * 1' |
There was a problem hiding this comment.
@copilot apply changes based on this feedback — drop the push: foss-main and weekly schedule triggers until JwtAuthGuard lands on foss-main with the Rule 2 fix. PR runs (current behaviour) are sufficient to gate fork-audit regressions until then.
There was a problem hiding this comment.
Applied — push: foss-main and weekly schedule are removed, leaving PR-triggered and manual runs for now. Addressed in dd2be34e (and present in current head).
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
|
|
There was a problem hiding this comment.
Addressed — current workflow has contents: read at workflow level and pull-requests: write scoped to the sticky-comment job only.
|
@copilot apply changes based on the comments in this thread |
Agent-Logs-Url: https://github.com/Pressingly/twenty/sessions/c800f651-f223-422d-be1b-32d95363495e Co-authored-by: awais786 <445320+awais786@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Pressingly/twenty/sessions/c800f651-f223-422d-be1b-32d95363495e Co-authored-by: awais786 <445320+awais786@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Pressingly/twenty/sessions/c800f651-f223-422d-be1b-32d95363495e Co-authored-by: awais786 <445320+awais786@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Pressingly/twenty/sessions/c800f651-f223-422d-be1b-32d95363495e Co-authored-by: awais786 <445320+awais786@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Pressingly/twenty/sessions/c800f651-f223-422d-be1b-32d95363495e Co-authored-by: awais786 <445320+awais786@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Pressingly/twenty/sessions/c800f651-f223-422d-be1b-32d95363495e Co-authored-by: awais786 <445320+awais786@users.noreply.github.com>
Implemented the thread feedback in this branch. Key updates include stricter row-20 matching (colocated SSO gate + mismatch operands + clearCookie), least-privilege workflow split for PR commenting, and disabling |
|
Closing: fork-side audit tooling not needed on this fork. The audit is being maintained in sso-rules-moneta instead, which is the single source of truth across all forks. |
Summary
Per-fork SSO contract audit in CI. Mirrors Pressingly/plane#32. Runs
scripts/sso-audit.shon PRs, blocks merges on security-critical violations. Catches regressions before they reachfoss-server-bundle-devstack.What it checks
packages/twenty-front/src/modules/auth/hooks/useAuth.ts/oauth2/sign_outpackages/twenty-server/src/engine/guards/jwt-auth.guard.tsresponse.clearCookie('tokenPair', ...)on identity mismatch, gated onAUTH_TYPE === 'SSO'Spec source:
sso-rules-moneta/openspec/specs/proxy-auth-middleware/spec.md.Current state
Local dry-run on
foss-main: row 20 ❌ —jwt-auth.guard.tsdoesn't have theclearCookie('tokenPair')call yet. The audit correctly blocks merges. Once PR #8 merges, the audit will go green onfoss-main.To unblock this PR, rebase onto
fix/proxy-auth-stale-session-on-user-switch(or wait for #8).Output
sso-fork-auditheader)🤖 Generated with Claude Code