ADR-003: Veltro as a standalone app + the suite identity authority
Source of truth:
docs/adr/003-veltro-standalone-identity-authority.md(synced at build time — edit the original, not this copy). Status: Accepted (2026-07-03). Owner confirmed Option A (Decision 3): Veltro is a deployment role of the shared platform codebase. Supersedes ADR-001 Decision 2 (VectorFlow as the identity authority); keeps ADR-001 Decision 1 (separate repos + thin platform repo) and the ADR-002 module contract, amended so the delegated issuer is Veltro, not VectorFlow.
ADR-010 partial supersession (2026-07-30): Veltro remains the suite identity authority, but Decision 3’s shared VectorFlow source, schema, database, image, version, and
VELTRO_ROLE=identityrealization is historical implementation rather than the approved target. #202 extracts an independent lightweight application fixed to public realmdefault.
Amended by ADR-004 (2026-07-04): Decision 4’s three
*_DELEGATED_AUTHflags are consolidated into oneVELTRO_INTEGRATEDper product, andVF_SESSION_SECRETis renamedVELTRO_SESSION_SECRET. The identity mechanism (cookie,VELTRO_ROLE, DB-sharing) is unchanged.
Context
Section titled “Context”Today “Veltro” is not an application — it is:
- this platform repo (compose profiles, single-origin nginx, env, ADRs), and
- an nginx alias
/veltro/*→vectorflow:3000/vectorflow/veltro/*, i.e. the VectorFlow container serves the identity surface.
Recent upstream work sharpened the seams:
- VF #529 consolidated identity/org/suite-audit settings under a cohesive
src/app/(dashboard)/veltro/settings/*subtree (SSO, SCIM, users, roles, teams, organization, suite-audit), retitled VF’s own hub “VectorFlow Settings”, and made every Veltro entry point a clean-origin<a href="/veltro/…">. - CHAD #281 added
suiteOwnedhandoff (identity tiles hidden in delegated mode) +VELTRO_BASE=/veltro; #282 (this effort) matched CHAD’s settings hub to VF 1:1. - The design system ships on npm (
@veltrosecurity/{tokens@0.1.1, ui@0.1.1,shell@0.2.1}), already consumed by both apps — the shared-UI half of “suite layer” is solved and repo-independent.
What remains is the inversion the goal asks for: Veltro must become its own deployable application (own image, container, compose, database) that IS the suite identity/SSO authority, with VectorFlow and CHAD reduced to delegating peers — while each product still runs fully standalone with its own auth when deployed on its own compose.
ADR-001 deliberately made VF the authority and rejected “a fourth stateful
service.” This ADR accepts that operational cost on purpose: a suite that adds
Warden/Prowl/… needs identity to live in a layer no single product owns
(the Zscaler ZIdentity model). Naming the tradeoff once: +1 stateful service
(Veltro app + its Postgres) in the full deployment.
Decision 1 — Veltro is its own app and the suite identity authority
Section titled “Decision 1 — Veltro is its own app and the suite identity authority”Introduce a Veltro application: own ghcr image
(ghcr.io/veltrosecurity/veltro-*), own container, own Postgres, own compose
profile. Veltro owns, for the suite:
- Login (credentials + TOTP, WebAuthn, OIDC, SAML) — the suite sign-in.
- Identity settings — SSO/OIDC, SAML, SCIM, users, roles, teams.
- The launcher (
/) + the AppSwitcher app registry (already in@veltrosecurity/shell). - Shared suite services — the cross-app audit feed, and the S2S token plane that fans out to products.
Under Veltro’s compose, VF and CHAD are delegators: they trust Veltro’s session and redirect sign-in / identity-management to Veltro. On their own composes they are unchanged — own login, own SSO/SCIM/local users.
Decision 2 — Session mechanism is unchanged; only the issuer moves
Section titled “Decision 2 — Session mechanism is unchanged; only the issuer moves”Keep ADR-001’s proven mechanism: a single-origin, host-only Auth.js v5 JWE
session cookie, single-tenant (org_id="default"), signed/encrypted with the
shared NEXTAUTH_SECRET (HKDF-SHA256 → dir/A256CBC-HS512). The only change is
who mints it: Veltro, not VF.
- CHAD already decodes it generically (
backend/app/core/vf_session.pydoes not checkiss). Repoint by pointingVF_SESSION_SECRETat Veltro’s secret — config-only. (A follow-up may rename the env toVELTRO_SESSION_SECRETwith a back-compat alias.) - VF in delegated mode reads the cookie natively: VF is Auth.js with the
same shared secret in single-tenant mode, so a Veltro-minted cookie is already
a valid VF session (
getTokenwithNEXTAUTH_SECRET). VF delegated mode is therefore mostly gating, not new crypto (see Decision 4).
This is why the mechanism is low-risk: the cookie bytes are identical to today; only the process that sets them changes.
Decision 3 — Realization: Veltro is a deployment role of the shared platform codebase
Section titled “Decision 3 — Realization: Veltro is a deployment role of the shared platform codebase”Decided — Option A (owner-confirmed 2026-07-03). Build Veltro from the shared platform codebase in an identity/authority role rather than as a clean-room rewrite. This makes Veltro a separate deployable app (own image/container/DB/ compose/authority), while sharing source with VF; it satisfies “its own app” at the deployment boundary, not as a separate codebase (owner accepted this tradeoff):
- The identity stack (auth.ts, saml, scim, webauthn, jwt-key, the
veltro/settings/*pages, the launcher,@veltrosecurity/shell) already exists and is verified. Veltro runs that stack; VF runs the same source in its product role. - A build/run flag (
VELTRO_ROLE=identity) makes the app serve only the identity surface (/,/login,/veltro/**,/api/auth/**,/api/scim/**,/api/auth/saml/**, launcher) and hide product features (pipelines/fleet/dashboards). Ship as imageveltro-server. - Because Veltro and VF share the identity code and secret, the cookie contract is identical by construction — CHAD/VF decode unchanged.
Rationale: maximal reuse of proven, verified auth + crypto; identical cookie; fully buildable/verifiable here (a role flag, not a new IdP). “Separate app” is satisfied at the deployment boundary (own image/container/DB/compose/authority), the way a monorepo builds multiple apps from shared packages.
Alternative (Option C), rejected for now. A brand-new clean-room Veltro app (own codebase + minimal single-tenant schema) reusing only the npm design system
- cookie format. Truest “separate codebase,” but a large net-new IdP whose
OIDC/SAML/SCIM/TOTP correctness cannot be verified without a live stack + real
IdPs — high risk of shipping unverified auth. Resumable later by extracting the
identity stack into an
@veltrosecurity/identitypackage consumed by a thin Veltro app (and VF), which is the clean long-term end state.
Owner decision (2026-07-03): Option A. Only the Veltro app’s internals differ between A and C; everything else in this ADR is identical either way.
DB-sharing corollary (Option A). Veltro and the suite VectorFlow are the same app + Prisma schema, so in the
fullprofile they share ONE database (vf-postgres) — Veltro migrates it first, and a user Veltro provisions is visible to the VF product app (no cross-DB JIT needed for VF). CHAD keeps its own DB and JIT-provisions from the cookie claims (backend/app/services/ suite_auth.py). A FUTURE product joining the suite for identity must therefore either (a) share this VF-schema identity DB, or (b) JIT-provision from the session claims like CHAD — prefer (b) when it has its own schema.
Decision 4 — VF and CHAD standalone integrity is inviolable
Section titled “Decision 4 — VF and CHAD standalone integrity is inviolable”- Own compose = unchanged.
vectorflow/docker/server/docker-compose.ymlandchad/docker-compose.ymlkeep their own login/SSO/SCIM/local users, no Veltro dependency. The delegated flags default off (CHAD_DELEGATED_AUTH=false; a newVF_DELEGATED_AUTH=false). - VF delegated mode (new, suite only). When
VF_DELEGATED_AUTH=true: VF stops offering its own sign-in (redirect/login→ Veltro), redirects/veltro/**identity management to Veltro, and trusts the incoming Veltro-minted session (native, shared secret). VF keeps its own encryption-at- rest key (VF_ENCRYPTION_KEY_V2) for its instance secrets — that is a VF concern, independent of who mints sessions. - Secret ownership.
NEXTAUTH_SECRETbecomes Veltro’s crown-jewel (mints suite sessions + signs S2S tokens). Compose fans it to VF and CHAD as the session-verification secret. Rotating it logs everyone out of all three.
Decision 5 — Topology (full profile)
Section titled “Decision 5 — Topology (full profile)”veltro-nginx, single origin:
/→ Veltro launcher (was302 /vectorflow/launcher)./login,/veltro/**,/api/auth/**,/api/scim/**→ Veltro./vectorflow/**→ VectorFlow (delegator; basePath image)./chad/**→ CHAD (delegator). Cookie stays host-only on the single origin (unchanged requirement).
Consequences
Section titled “Consequences”- +1 stateful service: the Veltro app container (the
vectorflow-serverimage in identity role). It SHARESvf-postgreswith the suite VectorFlow (Decision 3 corollary) — no separate identity DB, andversions.envneeds no new pin (Veltro ridesVF_VERSION). - Veltro (identity role) SKIPS VF’s product singleton schedulers (delivery retry,
auto-rollback, staged rollout, fleet/anomaly/cost/metrics, git-sync, backups):
they must run on exactly one writer against the shared DB, so the co-located
product instance runs them (
src/instrumentation.node.tsgates onisVeltroIdentityRole(); verified — the identity container starts none of them). Edge: a future Veltro-only deploy (no VF product sibling) would not runauth-challenge-gc; split identity vs product singletons if that shape ships. - ADR-002 module contract amended: the delegated issuer is Veltro; new modules
point
*_DELEGATED_AUTH+ session secret at Veltro. Everything else stands. - Because Veltro shares
vf-postgres, existing VF-as-authority suite deploys need NO user/org migration — Veltro reads the same identity tables. (A later move to a dedicated Veltro DB would need a migration; not required for Option A.) - Standalone VF/CHAD are untouched — their own compose profiles exclude Veltro and
the delegated flags default off (verified via
docker compose config).
Phased plan
Section titled “Phased plan”- P1 (done): CHAD settings hub visual parity (#282).
- P2: This ADR + owner confirmation of Decision 3 (A vs C).
- P3: Veltro app — identity role (Option A):
VELTRO_ROLEgating, image, launcher/login/identity at/+/veltro. Build + unit/route tests. - P4: VF delegated mode (
VF_DELEGATED_AUTH) — login/identity redirect + native session trust; standalone unchanged. Unit tests both modes. - P5: Repoint CHAD delegation VF→Veltro (config; optional env rename).
- P6: Veltro compose profile +
veltro-postgres+ nginx re-topology +.env/versions.envcontract. - P7: Verification — extend
scripts/verify-sso.shfor the Veltro issuer; confirm VF-only and CHAD-only composes still boot standalone; update README/ADR-001/ADR-002 cross-refs + a migration runbook.
Acceptance mirrors scripts/verify-sso.sh: / routing, Veltro login →
/vectorflow and /chad/ authed with no second login, single logout, and both
products still boot standalone with their own login.