Skip to content

chore(ci): add fork-side SSO audit script + workflow#9

Closed
awais786 wants to merge 7 commits into
foss-mainfrom
chore/sso-audit-ci
Closed

chore(ci): add fork-side SSO audit script + workflow#9
awais786 wants to merge 7 commits into
foss-mainfrom
chore/sso-audit-ci

Conversation

@awais786
Copy link
Copy Markdown
Collaborator

Summary

Per-fork SSO contract audit in CI. Mirrors Pressingly/plane#32. Runs scripts/sso-audit.sh on PRs, blocks merges on security-critical violations. Catches regressions before they reach foss-server-bundle-devstack.

What it checks

Row File Invariant Severity
14 packages/twenty-front/src/modules/auth/hooks/useAuth.ts SPA logout MUST NOT call /oauth2/sign_out informational
20 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
21 same No polynomial-backtracking email-shape regex regression guard

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.ts doesn't have the clearCookie('tokenPair') call yet. The audit correctly blocks merges. Once PR #8 merges, the audit will go green on foss-main.

To unblock this PR, rebase onto fix/proxy-auth-stale-session-on-user-switch (or wait for #8).

Output

  • Sticky PR comment (sso-fork-audit header)
  • GitHub job summary
  • Exit code 1 on security-critical → merge blocked

🤖 Generated with Claude Code

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>
@github-actions
Copy link
Copy Markdown

Twenty SSO Fork Audit

Cross-app contract: https://github.com/awais786/sso-rules-moneta/blob/main/openspec/specs/proxy-auth-middleware/spec.md
Row numbers match the 21-row table at https://github.com/awais786/sso-rules-moneta/blob/main/skills/app-rules/SKILL.md#5-report

Row Invariant Status Notes
14 logout shape: SPA logout does not call /oauth2/sign_out packages/twenty-front/src/modules/auth/hooks/useAuth.ts does not invoke /oauth2/sign_out (this row verifies only that the SPA doesn't try to clear the upstream proxy cookie itself; that's the portal's job)
20 session-identity reconciliation present (Rule 2 mismatch flush) packages/twenty-server/src/engine/guards/jwt-auth.guard.ts does NOT call response.clearCookie('tokenPair', ...). The cross-app spec (proxy-auth-middleware Rule 2) requires the guard to clear the tokenPair cookie when oauth2-proxy asserts a different identity than the JWT user. Without it, the stale-session-on-user-switch leak returns. Fix: add a comparison of X-Auth-Request-Email vs data.user.email after validateTokenByRequest, and on mismatch call response.clearCookie('tokenPair', { path: '/' }) then return false. Gate the check on AUTH_TYPE === 'SSO'.
21 email-shape detection uses indexOf, not polynomial regex No polynomial-backtracking email-shape regex in packages/twenty-server/src/engine/guards/jwt-auth.guard.ts; using indexOf-based detection

1 violations. Security-critical (row 20): 1.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.sh to deterministically audit specific SSO contract rows (14/20/21) in the codebase.
  • Add .github/workflows/sso-audit.yml to run the audit on relevant PRs, on foss-main pushes, 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.

Comment thread scripts/sso-audit.sh Outdated
Comment on lines +118 to +126
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
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread .github/workflows/sso-audit.yml Outdated
Comment on lines +22 to +26
push:
branches: [foss-main]
schedule:
# 09:00 UTC every Monday — pre-week sanity check
- cron: '0 9 * * 1'
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment on lines +29 to +32
permissions:
contents: read
pull-requests: write

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed — current workflow has contents: read at workflow level and pull-requests: write scoped to the sticky-comment job only.

@awais786
Copy link
Copy Markdown
Collaborator Author

@copilot apply changes based on the comments in this thread

Copilot AI and others added 6 commits May 16, 2026 11:40
Copy link
Copy Markdown

Copilot AI commented May 16, 2026

@copilot apply changes based on the comments in this thread

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 push/schedule until the guard invariant is present. Latest commit: 3ef61b84.

@awais786
Copy link
Copy Markdown
Collaborator Author

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.

@awais786 awais786 closed this May 18, 2026
@awais786 awais786 deleted the chore/sso-audit-ci branch May 18, 2026 08:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants