Nexus

Sites Overview

Sites let you (or an AI agent) build fully integrated internal web apps on top of your data that run inside Nexus, in your own Azure tenant, with no external hosting and no separate stack to operate.

A Nexus Site is a custom web app — HTML, JavaScript, CSS, and images — that Nexus hosts and serves directly inside your own Azure tenant at /sites/{slug}/. Each page calls the same Nexus REST API as the signed-in user, so a Site is a tailored UI over that user's own permission-bounded data: live bridges, saved reports, and pipelines. This page is for teams (and AI agents acting on their behalf) that want internal dashboards and operational tools without standing up a separate front-end stack, and for the security reviewer who needs to understand exactly what a hosted page can and cannot do.

What a Site is

Sites turn Nexus into an application platform, not just a query engine. Instead of exporting data to a spreadsheet or building a separate web app with its own servers, auth, and deployment pipeline, you author static assets and Nexus serves them from the same Container App that already runs your tenant. There is nothing new to host, secure, or pay for separately — a Site lives and runs where your data already lives, and it inherits the tenant's isolation posture (per-tenant Postgres, per-tenant Key Vault, per-tenant VNet) for free.

The defining property is that a hosted page authenticates the viewing user and talks to /api/* as them. Everything a Site can see or do is bounded by that user's role-based access control (RBAC). Two people opening the same Site see different data because each request runs under the caller's own grants — the page has no ambient authority of its own. A Site is therefore best understood as a thin, branded client over the exact same API surface the desktop app uses, not as a privileged server-side app.

A Site has two kinds of content:

  • Config — JSON metadata: name, description, status (draft or published), the default_document (usually index.html) served for the bare /sites/{slug} path, and an optional connect_src_extra break-glass list (see below). Config is stored as tenant metadata and is not version-controlled.
  • Assets — the files you serve: index.html, app.js, style.css, images, and fonts. Text assets are version-controlled; binary/blob assets are not.

What you can build

Because a page can list bridges, run any saved report, and start any pipeline the user is allowed to, Sites fit a wide range of internal tooling:

  • Operational dashboards — KPI cards and tables backed by saved reports that refresh live as the viewer loads the page.
  • Ops and admin tools — buttons that kick off pipelines (refreshes, syncs, exports) and show the result.
  • Departmental data views — a curated, branded view of one team's data, scoped automatically to who is signed in.
  • Self-service report runners — let non-technical users run parameterized reports without touching the underlying query.
  • Status boards — a wall-mounted or shared page that surfaces pipeline run health or a rolled-up metric, refreshed on an interval.

What a Site is not: it is not a place to run arbitrary server-side code, reach third-party APIs directly from the browser, or hold long-lived secrets. Those belong in a pipeline or a connector, which a page then calls through Nexus. The boundary is deliberate — it is what keeps a Site safe to hand to an AI agent or a non-admin author.

How it runs and stays secure

Sites are designed around one hard guarantee: a hosted page can never obtain or leak a bearer token, and can never exceed the viewing user's permissions. Two render paths deliver that guarantee.

  • In the desktop app (primary). The Nexus desktop client renders a Site in a dedicated, locked-down top-level WebView and brokers every /api/* call through its local loopback proxy (https://localhost:8443), which injects the user's keyring-resident credentials server-side. The page itself makes plain relative fetches with no Authorization header and never receives a token. The WebView is IPC-restricted so page code cannot reach desktop-native bridges.
  • In a browser (fallback). The first-party SDK signs the user in with Microsoft Entra ID (Azure AD) using PKCE (S256) and holds the access token in a module-private closure in memory only — never in localStorage or sessionStorage. A refresh-stable, HttpOnly session cookie keeps the user signed in across reloads without exposing the token to page JavaScript.

The Sites Content-Security-Policy

Every /sites/* response carries a strict, per-response Content-Security-Policy set by the Sites router itself (not the platform-wide middleware). It is the security crux of the feature:

default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
connect-src 'self' https://login.microsoftonline.com;
object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'

What each rule buys you:

  • script-src 'self' — no inline <script>, no eval, no third-party CDNs. All JavaScript must be an external .js asset you serve from the same origin. This removes the most common XSS and supply-chain vectors outright.
  • connect-src 'self' https://login.microsoftonline.com — the page can reach only the Nexus API origin and the Microsoft sign-in endpoint. This is the anti-exfiltration guarantee: even if a token somehow sat in page code, there is no network channel to send it — or any of the data the page reads — anywhere else. On Azure Government tenants the login host flips to login.microsoftonline.us to follow the active cloud.
  • img-src 'self' data: — images are same-origin assets or inline data: URIs; no remote hotlinking.
  • frame-ancestors 'none' (reinforced by X-Frame-Options: DENY) — the page renders top-level only and cannot be embedded for clickjacking.
  • object-src 'none' / base-uri 'none' — no plugins, no <base> rewriting.

This strict CSP is specific to Sites (and the server-hosted /app shell). It is a real, deployed control on those surfaces — it is not a claim about the rest of the platform.

For the rare, vetted case where a page genuinely must reach an additional origin, an admin can widen connect-src per Site via a connect_src_extra break-glass list. It is gated by site:manage and a human token, SSRF-validated at write time (must be https://, no private/internal targets), and recorded as a security event. It is not a default and should stay empty unless you have a reviewed reason; the safer pattern is to have the page call Nexus and let a pipeline or connector reach the third party server-side.

Tenant isolation

Per-tenant isolation is structural, not configured per Site: one Nexus deployment, one database, and one storage namespace per tenant, with no cross-tenant path. A Site is served from that same deployment, so it inherits the tenant boundary — there is no shared, multi-tenant Sites host to escape from.

The SDK — the only token path

Nexus serves a first-party nexus-client.js SDK from /sites/_sdk/nexus-client.js (same origin, CSP-safe). It detects the render mode, owns authentication internally, and exposes typed helpers. Your page calls these; it never touches a raw token, and there is no API to obtain one by design.

const nexus = await Nexus.connect();          // detects desktop vs browser, handles auth
await nexus.listBridges();                     // GET /api/bridges
await nexus.listPipelines();                   // GET /api/pipelines
await nexus.runReport(reportId, params);       // POST /api/reports/{id}/execute
await nexus.runPipeline(pipelineId, input);    // POST /api/pipelines/{id}/start
await nexus.whoami();                          // async claims (desktop mode has no local token)
nexus.user;                                    // {name, roles, email} — claims only, never a token
Nexus.signIn();                               // explicit interactive sign-in for a "Sign in" button
await Nexus.signOut();                        // expire the site-session cookie

Because the SDK is the only path to the API, the page's authority is exactly the user's authority — the SDK sets the client-type and request-timestamp headers, handles the desktop-vs-browser difference, and lets RBAC on the server gate every call. The connect-src rule then ensures nothing the page reads can be shipped off-tenant.

Nexus also serves a self-hosted design system (stingray.css) under /sites/_sdk/ so you can build a professional, branded page with semantic HTML and little or no custom CSS. See Building a Site for the full styling reference.

Draft and published states

A Site is created in draft and goes live when you set its status to published. Both states are served — draft is your work-in-progress that you can open and test — but published is the intended live experience. Flipping status is a config save; there is no build step and no deploy pipeline. The Site is reachable at /sites/{slug}/ the moment its config exists, and typing the bare tenant host redirects to the Sites directory.

Per-site execution roles (optional)

Beyond running as the viewing user, a Site can optionally pin a curated execution role (a "kiosk" role) so that report and pipeline calls run under a fixed, admin-approved permission set rather than each visitor's own grants — useful for a shared status board that must show the same rolled-up data to everyone without granting each viewer standing access to the underlying source. The execution-role binding is admin-edit-locked and set over REST only (never through the general authoring tools), so an author cannot silently escalate a Site. See Execution Roles.

Limits and guarantees

  • A page cannot exceed the viewer's RBAC. Every /api/* call is gated server-side under the caller's principal; there is no ambient Site authority.
  • A page cannot exfiltrate data or tokens. connect-src confines egress to the Nexus origin plus Microsoft sign-in; the token never enters page JavaScript in either render mode.
  • Config is not versioned; text assets are. Name/status/default_document changes are not restorable via history — only text assets (.html/.js/.css/.json/etc.) are committed to the tenant's git-backed version control.
  • No direct workspace access. A Site's surface is the /api/* REST routes; it cannot query the internal workspace directly. To put workspace data on a page, author a saved report over the workspace bridge and run it from the page.
  • Sites cross-origin isolation is not shipped. Per-tenant host isolation for Sites has been proposed but is not deployed; today the CSP connect-src/frame-ancestors controls above are the deployed boundary.

See also