Skip to main content

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.
Changing any reviewed origin without coordinating with Perflo breaks connection approval, hosted KYC, or hosted card reveal. The dashboard frames only connection approval. Hosted KYC and card detail reveal each open in a new tab with the opener severed, so Perflo’s own origin governs the camera and the hosted page cannot script the banking tab. The connection iframe requires the Content-Security-Policy frame-src directive and the WebAuthn Permissions-Policy origins to match the origin Perflo actually serves. Every hosted flow that leaves the dashboard — a top-level navigation or a new tab, which is itself a top-level browsing context — depends on the same exact-origin validation, but not on frame-src or delegated WebAuthn permissions.
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
The settings validator compares all five configured origins with the reviewed constants for every live workload, including the scheduler:
apps/api/src/neobank/config.py
Every live process refuses to start if any configured provider origin differs. This guard prevents provider credentials from being sent to an unreviewed host.

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-Policy frame-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-Policy publickey-credentials-create and publickey-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
The iframe’s 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
The API refuses to return an untrusted URL to the client:
apps/api/src/neobank/api.py
The same validator guards the connection action URL, the live adapter uses the same predicate to replace an untrusted minted reveal URL with the trusted Perflo cards page (PFR-013), and the worker applies it to mandate approval URLs. All surfaces use the trusted Perflo origin, but the KYC and card reveal pages each open in a new tab whose 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 with opener severed so the hosted page cannot script the banking tab. Three guarantees must hold simultaneously for the connection frame to work and remain constrained:
  1. The framed origin is the one Perflo reviewed and serves (PERFLO_APP_ORIGIN).
  2. The CSP allows that exact origin to be framed (frame-src).
  3. The Permissions-Policy allows that exact origin to run WebAuthn (publickey-credentials-create, publickey-credentials-get).
If any one of these points at a different origin than the others, either the connection frame or its WebAuthn ceremony is blocked. The top-level flows still require the API’s hosted-URL validation to agree with the same trusted origin. Coordinating a change across the API constants, shared browser guard, edge policies, and Perflo’s own hosting is the only supported path.
  • Branding & theming — the brand identity is editable; the Perflo origin is not.
  • Adapters — adapter selection and the provider origin boundary.