Perflo hosted origins
Live mode pins the reviewed app, Perfolio, fiat, cards, and agent origins in the API. The hosted application origin,https://app.perflo.ai, is also shared by the browser URL
guard and edge security headers. A launching bank cannot point the deployment at another
Perflo tenant through configuration alone: startup validation rejects every unreviewed
service origin.
This page documents the current state. Parameterizing the origins across deployments is
future work — see Adapters.
Where the origin is pinned
1. The startup security boundary
apps/api/src/neobank/config.py defines the canonical origin as a module constant, and
the settings validator rejects any deployment that configures a different one:
apps/api/src/neobank/config.py
apps/api/src/neobank/config.py
2. The edge security headers
apps/web/security-headers.conf is the Caddy/nginx header layer served in front of the
web app. Two directives name app.perflo.ai explicitly:
Content-Security-Policyframe-src https://app.perflo.ai— the only origin allowed to be framed by the customer app. The connection-approval iframe depends on this; hosted KYC and card reveal do not run in a frame.Permissions-Policypublickey-credentials-createandpublickey-credentials-get— allow the framed Perflo page to perform WebAuthn ceremonies (passkey enrollment and assertion) on the bank’s origin. Without both entries, the hosted Perflo WebAuthn UI cannot run inside the iframe.
3. The shared browser URL guard
apps/web/src/api/client.ts defines the browser constant and one exact-origin validator.
The connection dialog, KYC navigation, approval tracker, and card reveal flow reuse it:
apps/web/src/api/client.ts
allow attribute uses the same constant to delegate the two WebAuthn
permissions to the framed origin:
apps/web/src/routes/dashboard.tsx
4. Hosted card reveal
Card detail reveal returns a hosted Perflo URL. One shared predicate decides whether a provider-minted URL is on the trusted origin;perflo_app_url is itself pinned to
PERFLO_APP_ORIGIN by the startup validator:
apps/api/src/neobank/hosted_urls.py
apps/api/src/neobank/api.py
opener is nulled before navigation. The CSP frame-src and WebAuthn
Permissions-Policy entries govern only the connection iframe.
Why the origin is pinned
Perflo runs the money rail. Hosted KYC, hosted connection approval, and hosted card reveal are Perflo-controlled surfaces. The bank frames connection approval and opens KYC and card reveal in new tabs — top-level browsing contexts on Perflo’s own origin, opened withopener severed so the hosted page cannot script the banking tab. Three guarantees
must hold simultaneously for the connection frame to work and remain constrained:
- The framed origin is the one Perflo reviewed and serves (
PERFLO_APP_ORIGIN). - The CSP allows that exact origin to be framed (
frame-src). - The
Permissions-Policyallows that exact origin to run WebAuthn (publickey-credentials-create,publickey-credentials-get).
Related
- Branding & theming — the brand identity is editable; the Perflo origin is not.
- Adapters — adapter selection and the provider origin boundary.