Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .agents/skills/authentication/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,24 @@ Each hosted `*.agent-native.com` app has its **own user store**, so "sign in onc
- **Invariant (do not break):** identity rows are only ever **added** — never modified, renamed, or deleted. Enabling SSO logs users out, but they always log back into the **same email-matched account with data intact**. Email is the only thing that crosses the trust boundary; the app never trusts a user id, role, or org from the wire.
- **Canary rollout:** deploy with the env unset everywhere (no-op) → set it on **one** app (mail) only → verify (logout → SSO → Dispatch → back to the same pre-existing account, data intact, direct logins still work) → expand app-by-app → rollback = unset the env on that app's deploy (instant, no data change).

### Packaged Desktop workspace SSO

For the signed Desktop app's canonical first-party hosted registry, Electron
owns a dedicated persistent Dispatch identity partition. Each target app still
uses the hosted federation flow above to mint its own normal local session;
Electron transfers only that target app's session cookie into its isolated
`persist:app-<id>` partition. Never distribute a Dispatch cookie, an identity
JWT, or one app's session to another app, and never expose those values through
renderer IPC.

Custom, user-added, and third-party apps are excluded. Preserve direct web
sign-in and the per-app `AGENT_NATIVE_IDENTITY_HUB_URL` opt-in/canary behavior.
The `desktop-sso.json` broker is only a loopback, non-production local
development compatibility path, not packaged hosted SSO. Desktop workspace
sign-out clears the central identity session and canonical app sessions;
standalone web logout remains app-local. Builder Connect and Builder credentials
are unrelated to human identity and must not join this flow.

Full runbook + flow detail: [Cross-App SSO doc](/docs/cross-app-sso).

## Builder Browser Access
Expand Down
8 changes: 8 additions & 0 deletions .changeset/calm-desktops-federate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@agent-native/core": minor
"@agent-native/dispatch": patch
---

Add an authenticated, nonce-only completion route for packaged Desktop clients orchestrating cross-app identity federation.

Ensure Dispatch installs identity federation routes on its primary auth guard so concurrent Nitro plugin startup cannot pre-empt them with a 401.
114 changes: 114 additions & 0 deletions .github/workflows/desktop-canary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Desktop SSO Canary

on:
push:
branches:
- codex/desktop-workspace-sso

permissions:
contents: read

jobs:
build-signed-macos-canary:
if: github.ref == 'refs/heads/codex/desktop-workspace-sso'
runs-on: macos-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Set canary version
working-directory: packages/desktop-app
env:
CANARY_SUFFIX: ${{ github.run_number }}
run: |
BASE=$(node -p "require('./package.json').version")
npm version "${BASE}-desktop-sso-canary.${CANARY_SUFFIX}" --no-git-tag-version

- name: Import signing certificate
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
set -euo pipefail
CERTIFICATE_PATH="$RUNNER_TEMP/desktop-canary-certificate.p12"
KEYCHAIN_PATH="$RUNNER_TEMP/desktop-canary.keychain-db"
echo "$APPLE_CERTIFICATE" | base64 --decode > "$CERTIFICATE_PATH"
security create-keychain -p "" "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
security unlock-keychain -p "" "$KEYCHAIN_PATH"
security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" "$KEYCHAIN_PATH"
rm "$CERTIFICATE_PATH"

- name: Build signed and notarized canary
working-directory: packages/desktop-app
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: W3PMF2T3MW
SENTRY_DESKTOP_DSN: ${{ secrets.SENTRY_DESKTOP_DSN || secrets.SENTRY_ELECTRON_DSN || secrets.SENTRY_CLIENT_DSN || secrets.SENTRY_DSN }}
SENTRY_DESKTOP_CLIENT_KEY: ${{ secrets.SENTRY_DESKTOP_CLIENT_KEY || secrets.SENTRY_CLIENT_KEY }}
SENTRY_DESKTOP_PROJECT_ID: ${{ secrets.SENTRY_DESKTOP_PROJECT_ID || secrets.SENTRY_PROJECT_ID }}
SENTRY_DESKTOP_INGEST_HOST: ${{ secrets.SENTRY_DESKTOP_INGEST_HOST || secrets.SENTRY_INGEST_HOST }}
SENTRY_DESKTOP_ENVIRONMENT: canary
run: |
node ../../scripts/build-branding-assets.mjs
pnpm build:native:mac
pnpm build:chrome-extension
pnpm build
npx electron-builder --mac --config --publish never

- name: Verify candidate provenance and trust
working-directory: packages/desktop-app
env:
EXPECTED_COMMIT: ${{ github.sha }}
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$EXPECTED_COMMIT"
VERSION=$(node -p "require('./package.json').version")
test -n "$VERSION"

APP_COUNT=0
while IFS= read -r -d '' APP_PATH; do
APP_COUNT=$((APP_COUNT + 1))
INFO="$APP_PATH/Contents/Info.plist"
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
xcrun stapler validate "$APP_PATH"
spctl --assess --type execute --verbose=4 "$APP_PATH"
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$INFO" | grep -qx 'com.agentnative.desktop'
/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$INFO" | grep -Fqx "$VERSION"
done < <(find dist -type d -name 'Agent Native.app' -print0)
test "$APP_COUNT" -eq 2

{
echo "workflow_run=${GITHUB_RUN_ID}"
echo "commit=${EXPECTED_COMMIT}"
echo "version=${VERSION}"
echo "bundle_id=com.agentnative.desktop"
} > dist/CANARY-MANIFEST.txt
find dist -maxdepth 1 -type f \( -name '*.dmg' -o -name '*.zip' \) -print0 \
| sort -z \
| xargs -0 shasum -a 256 >> dist/CANARY-MANIFEST.txt
grep -q '\.dmg$' < <(find dist -maxdepth 1 -type f -name '*.dmg')
grep -q '\.zip$' < <(find dist -maxdepth 1 -type f -name '*.zip')

- name: Upload short-lived canary artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: desktop-sso-canary-${{ github.sha }}
path: |
packages/desktop-app/dist/*.dmg
packages/desktop-app/dist/*.zip
packages/desktop-app/dist/CANARY-MANIFEST.txt
if-no-files-found: error
retention-days: 3
23 changes: 23 additions & 0 deletions packages/core/docs/content/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,29 @@ back to the shared `an_session` name. To intentionally share one auth database
across subdomains, set `AGENT_NATIVE_SHARE_COOKIE_DOMAIN=1` alongside
`COOKIE_DOMAIN`.

## Packaged Desktop Workspace SSO {#desktop-workspace-sso}

The signed Agent Native Desktop app gives canonical first-party hosted apps one
workspace sign-in without turning their sessions into a shared cookie. Desktop
keeps the Dispatch identity session in its own persistent Electron partition,
then each app completes the normal [Cross-App SSO](/docs/cross-app-sso)
federation and mints its **own** local session. Desktop transfers only that
target app's session cookie into its `persist:app-<id>` partition.

This applies only to the canonical first-party app registry packaged with
Desktop. Custom, user-added, and third-party apps are excluded. Direct web
sign-in remains available for recovery and standalone browser use, and every
hosted app still opts into federation independently with
`AGENT_NATIVE_IDENTITY_HUB_URL`; the existing canary and rollback procedure is
unchanged.

The local `desktop-sso.json` broker is a separate compatibility mechanism for
loopback, non-production Electron development. It is not a hosted SSO
credential source and must not be broadened to production. Desktop workspace
sign-out clears the central Dispatch identity session and the canonical app
sessions in Desktop; standalone-browser sessions remain app-local. Builder
Connect and Builder credentials are unrelated to this identity flow.

## QA Accounts {#qa-accounts}

Local development and tests skip signup email verification by default, so you
Expand Down
22 changes: 22 additions & 0 deletions packages/core/docs/content/cross-app-sso.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,28 @@ The just-in-time link is a single decision keyed entirely on the verified email:

</Diagram>

## Packaged Desktop workspace {#desktop-workspace}

The signed Agent Native Desktop app composes this hosted federation into one
workspace sign-in for its canonical first-party hosted app registry. Electron
keeps the Dispatch authority session in a dedicated persistent identity
partition. For each target app, it runs this app-local federation flow and
copies only the completed target app's normal session cookie into that app's
isolated `persist:app-<id>` partition. Each app still owns its session,
database, authorization, and data isolation; Desktop does not distribute a
Dispatch cookie or a shared app token.

This boundary deliberately excludes custom, user-added, and third-party apps.
The local `desktop-sso.json` broker remains a loopback, non-production
development compatibility path; it is not used for packaged hosted SSO.
Workspace-wide Desktop sign-out clears the central identity session and the
canonical app sessions in Desktop, while standalone web sign-out remains
app-local. Builder Connect and Builder credentials do not participate.

Desktop does not change federation eligibility: each hosted app must still set
`AGENT_NATIVE_IDENTITY_HUB_URL`, direct app login remains available, and the
one-app-at-a-time canary and env-unset rollback below still apply.

## Self-hosting {#self-hosting}

Any Dispatch deployment can serve as the identity hub — you are not limited to `dispatch.agent-native.com`. Set `AGENT_NATIVE_IDENTITY_HUB_URL` on each client app to point at your Dispatch instance:
Expand Down
4 changes: 4 additions & 0 deletions packages/core/docs/content/locales/ar-SA/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ description: "تكامل أفضل للمصادقة مع البريد الإلك
عبر النطاقات الفرعية، قم بتعيين `AGENT_NATIVE_SHARE_COOKIE_DOMAIN=1` جنبًا إلى جنب مع
`COOKIE_DOMAIN`.

## SSO لمساحة عمل Desktop {#desktop-workspace-sso}

يستخدم تطبيق Agent Native Desktop الموقّع قسم هوية Dispatch دائمًا ومخصصًا للتطبيقات المستضافة الأولى الأساسية. ينفذ كل تطبيق اتحاد [Cross-App SSO](/docs/cross-app-sso) المعتاد وينشئ جلسته المحلية الخاصة؛ وينقل Desktop ملف تعريف ارتباط جلسة التطبيق الهدف فقط إلى `persist:app-<id>`. تستبعد التطبيقات المخصصة والمضافة من المستخدم والطرف الثالث. لا يتغير تسجيل الدخول المباشر للويب ولا الاشتراك التدريجي وcanary والتراجع لكل تطبيق عبر `AGENT_NATIVE_IDENTITY_HUB_URL`. وسيط `desktop-sso.json` المحلي مخصص فقط لتطوير Electron عبر loopback وخارج الإنتاج. يسجل خروج Desktop بمسح الهوية المركزية وجلسات التطبيقات الأساسية، لا جلسات المتصفح؛ ولا علاقة لـ Builder Connect بذلك.

## حسابات ضمان الجودة {#qa-accounts}

تتخطى عمليات التطوير والاختبارات المحلية التحقق من البريد الإلكتروني للتسجيل بشكل افتراضي، لذا
Expand Down
4 changes: 4 additions & 0 deletions packages/core/docs/content/locales/ar-SA/cross-app-sso.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ AGENT_NATIVE_IDENTITY_HUB_URL=https://dispatch.agent-native.com

</Diagram>

## مساحة عمل Desktop المعبأة {#desktop-workspace}

يؤلف Desktop الموقّع هذا الاتحاد في تسجيل دخول لمساحة العمل لسجله الأساسي: يبقى قسم هوية Dispatch الدائم والمخصص مركزيًا، لكن كل تطبيق هدف ينشئ جلسته المحلية ولا ينقل إلا ملف تعريف ارتباطها إلى `persist:app-<id>`. لا تتم مشاركة ملفات تعريف ارتباط Dispatch أو رموز التطبيقات. تستبعد التطبيقات المخصصة والمضافة من المستخدم والطرف الثالث. يظل `desktop-sso.json` وسيطًا محليًا للـ loopback خارج الإنتاج. يسجل خروج Desktop بمسح الهوية المركزية وجلسات التطبيقات الأساسية، ويبقى خروج المتصفح محليًا لكل تطبيق. Builder Connect غير ذي صلة. يستمر تسجيل الدخول المباشر والاشتراك التدريجي وcanary والتراجع بمتغير البيئة لكل تطبيق.

## الاستضافة الذاتية {#self-hosting}

يمكن أن يكون أي نشر لـ Dispatch بمثابة مركز الهوية - فأنت لست مقيدًا بـ `dispatch.agent-native.com`. قم بتعيين `AGENT_NATIVE_IDENTITY_HUB_URL` على كل تطبيق عميل للإشارة إلى مثيل Dispatch الخاص بك:
Expand Down
4 changes: 4 additions & 0 deletions packages/core/docs/content/locales/de-DE/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ zurück zum gemeinsamen `an_session`-Namen. Absichtlich eine Authentifizierungsd
über Subdomains hinweg `AGENT_NATIVE_SHARE_COOKIE_DOMAIN=1` daneben setzen
`COOKIE_DOMAIN`.

## Workspace-SSO in der Desktop-App {#desktop-workspace-sso}

Die signierte Agent Native Desktop-App verwendet für kanonische gehostete First-Party-Apps eine eigene dauerhafte Dispatch-Identitätspartition. Jede App führt die normale [Cross-App SSO](/docs/cross-app-sso)-Föderation aus und prägt ihre eigene lokale Sitzung; Desktop überträgt nur das Sitzungscookie der Ziel-App nach `persist:app-<id>`. Benutzerdefinierte, hinzugefügte und Drittanbieter-Apps sind ausgeschlossen. Direkte Web-Anmeldung sowie das Opt-in, Canary und Rollback pro App mit `AGENT_NATIVE_IDENTITY_HUB_URL` bleiben unverändert. Der lokale Broker `desktop-sso.json` gilt nur für Loopback- und Nicht-Produktions-Entwicklung. Desktop-Abmeldung löscht die zentrale Identität und kanonische App-Sitzungen, nicht Browser-Sitzungen; Builder Connect ist unabhängig.

## QA-Konten {#qa-accounts}

Lokale Entwicklung und Tests überspringen standardmäßig die Überprüfung der Anmelde-E-Mail, damit Sie
Expand Down
4 changes: 4 additions & 0 deletions packages/core/docs/content/locales/de-DE/cross-app-sso.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ Der Just-in-Time-Link ist eine einzelne Entscheidung, die vollständig auf der v

</Diagram>

## Paketierter Desktop-Arbeitsbereich {#desktop-workspace}

Die signierte Desktop-App verbindet diese Föderation für ihre kanonische First-Party-Registry zu einer Arbeitsbereich-Anmeldung: Eine dedizierte dauerhafte Dispatch-Identitätspartition bleibt zentral, jede Ziel-App prägt aber ihre eigene lokale Sitzung und nur deren Cookie wird nach `persist:app-<id>` übertragen. Dispatch-Cookies und App-Tokens werden nicht geteilt. Benutzerdefinierte, hinzugefügte und Drittanbieter-Apps sind ausgeschlossen. `desktop-sso.json` bleibt ein lokaler Loopback-Nichtproduktions-Broker. Desktop-Abmeldung löscht zentrale Identität und kanonische App-Sitzungen, Browser-Abmeldung bleibt app-lokal; Builder Connect ist unabhängig. Direktlogin sowie Opt-in, Canary und Env-Unset-Rollback pro App bleiben bestehen.

## Selbsthosting {#self-hosting}

Jede Dispatch-Bereitstellung kann als Identitäts-Hub dienen – Sie sind nicht auf `dispatch.agent-native.com` beschränkt. Legen Sie `AGENT_NATIVE_IDENTITY_HUB_URL` in jeder Client-App so fest, dass es auf Ihre Dispatch-Instanz verweist:
Expand Down
4 changes: 4 additions & 0 deletions packages/core/docs/content/locales/es-ES/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ volver al nombre compartido `an_session`. Compartir intencionalmente una base de
en todos los subdominios, establezca `AGENT_NATIVE_SHARE_COOKIE_DOMAIN=1` al lado
`COOKIE_DOMAIN`.

## SSO del espacio de trabajo de Desktop {#desktop-workspace-sso}

La aplicación Desktop firmada mantiene una partición persistente de identidad de Dispatch para las apps alojadas propias canónicas. Cada app realiza la federación normal de [Cross-App SSO](/docs/cross-app-sso) y crea su propia sesión local; Desktop transfiere solamente la cookie de sesión de la app destino a `persist:app-<id>`. Se excluyen apps personalizadas, añadidas por usuarios y de terceros. El inicio de sesión web directo y el opt-in, canary y reversión por app con `AGENT_NATIVE_IDENTITY_HUB_URL` no cambian. El broker local `desktop-sso.json` es solo para desarrollo Electron en loopback y no producción. Cerrar sesión en Desktop borra la identidad central y sesiones de apps canónicas, no sesiones del navegador; Builder Connect no está relacionado.

## Cuentas de control de calidad {#qa-accounts}

Las pruebas y el desarrollo local omiten la verificación del correo electrónico de registro de forma predeterminada, por lo que
Expand Down
4 changes: 4 additions & 0 deletions packages/core/docs/content/locales/es-ES/cross-app-sso.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ El enlace justo a tiempo es una decisión única basada completamente en el corr

</Diagram>

## Espacio de trabajo Desktop empaquetado {#desktop-workspace}

La aplicación Desktop firmada compone esta federación en un inicio de sesión de espacio de trabajo para su registro propio canónico: una partición persistente de identidad de Dispatch permanece central, pero cada app destino crea su propia sesión local y solo su cookie se transfiere a `persist:app-<id>`. No se comparten cookies de Dispatch ni tokens de apps. Se excluyen apps personalizadas, añadidas por usuarios y de terceros. `desktop-sso.json` sigue siendo un broker local de loopback no productivo. Cerrar sesión en Desktop borra la identidad central y sesiones canónicas; en navegador sigue siendo local a cada app. Builder Connect no está relacionado. Se conservan el login directo y el opt-in, canary y rollback por variable de entorno por app.

## Autohospedaje {#self-hosting}

Cualquier implementación de Dispatch puede servir como centro de identidad; no está limitado a `dispatch.agent-native.com`. Configure `AGENT_NATIVE_IDENTITY_HUB_URL` en cada aplicación cliente para que apunte a su instancia de Dispatch:
Expand Down
4 changes: 4 additions & 0 deletions packages/core/docs/content/locales/fr-FR/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ retour au nom `an_session` partagé. Pour partager intentionnellement une base d
dans les sous-domaines, définissez `AGENT_NATIVE_SHARE_COOKIE_DOMAIN=1` à côté
`COOKIE_DOMAIN`.

## SSO de l’espace de travail Desktop {#desktop-workspace-sso}

L’application Desktop signée conserve une partition d’identité Dispatch persistante pour les applications hébergées propriétaires canoniques. Chaque application exécute la fédération [Cross-App SSO](/docs/cross-app-sso) normale et crée sa propre session locale ; Desktop ne transfère que le cookie de session de l’application cible vers `persist:app-<id>`. Les applications personnalisées, ajoutées par l’utilisateur et tierces sont exclues. La connexion web directe ainsi que l’opt-in, le canari et le retour arrière par application avec `AGENT_NATIVE_IDENTITY_HUB_URL` restent inchangés. Le broker local `desktop-sso.json` est réservé au développement Electron en loopback hors production. La déconnexion Desktop efface l’identité centrale et les sessions canoniques, pas celles du navigateur ; Builder Connect est sans rapport.

## Comptes d'assurance qualité {#qa-accounts}

Le développement et les tests locaux ignorent la vérification de l'e-mail d'inscription par défaut, vous pouvez donc
Expand Down
Loading
Loading