Every audience that talks to your CRM talks to the same substrate. The staff operator running the platform. The tenant admin curating the customer base. The customer signing in to their own portal. The channel partner reading the deck you shared. The prospect filling in a form on your marketing site. The visitor reading a help article.
Six audiences. One data plane. Four hosts.
The boundary between them lives in the infrastructure — different container images, different containers, different reverse-proxy hosts, different origin cookies. A vulnerability discovered in the form renderer physically cannot reach an authenticated admin session because the code does not exist at that origin.
Four hosts, six audiences
The staff operator console lives on its own hostname — cross-tenant, staff-platform session kind, MFA-everywhere, sidebar hidden by design. The tenant admin SPA lives on its own hostname — the surface where curators of the customer base spend their days. The portal host serves three audiences: customers in the Client Portal, channel partners in the Partner Portal, and the general public reading a form, a knowledge-base article, or a gated asset. The embed host serves the framework-free embeddable widget — the one your tenant drops on their marketing site.
Each host is its own build artefact. Each carries its own __Host-prefixed HttpOnly cookie whose scope physically does not overlap the others. The admin SPA’s session cookie is scoped to the admin host and stays there. The partner cookie is scoped to the portal host and stays there. Browser same-origin policy is the outermost isolation boundary; a server-side session filter scopes every downstream query.
Substrate-level tenant isolation is the industry-standard bar. The origin boundary ships alongside it.
Twelve properties on every public submission
Every form submission on portal.{base} — customer inquiry, page feedback, partner registration, gated-asset request — passes through twelve enterprise-grade defensive properties before it lands as a row:
- Body-size cap.
Idempotency-Keydedupe on a one-hour trailing window.- Silent honeypot.
- Accepted-only per-IP rate limit.
- hCaptcha verification with encrypted at-rest secret.
- Email-domain allow-list.
- Origin allow-list.
- Field-shape validation.
- HMAC-SHA256-signed outbound webhooks.
- DNS-TXT vanity-domain ownership challenge.
- Tenant isolation at every query.
- Forensic capture of any unhandled exception in a server-side forensic table.
Bot traffic that fails honeypot, rate-limit, or captcha is never persisted. The submissions store stays proportional to legitimate traffic. The twelve properties live at the API layer, so any tenant that swaps the portal SPA for their own implementation inherits every one.
The embed widget — ~30 KB, closed shadow DOM
embed.{base} ships a framework-free JavaScript bundle at ~30 KB. It renders into a closed shadow root, so host-page CSS cannot leak in and widget state cannot leak out. Analytics scripts on the host page cannot read the visitor’s typed input. The widget only POSTs to the Actionary API on submit.
Tenants embedding third-party form widgets hit three recurring problems: CSP rule churn, style bleed, and analytics leakage. Shadow-DOM isolation closes all three at once. Drop it on a page; it renders natively and stays quarantined.
Filter chips the agent can author
The SPA’s filter chips are URL-authorable. ?filters[stage]=qualified&filters[owner]=me&filters[created][after]=2026-01-01 deep-links a filtered view of any entity list. Bookmarkable. Shareable. And agent-authorable — when the embedded agent decides the operator’s next task is “open the deals stuck in negotiation for more than 30 days”, it composes the URL and hands it to the operator. Entity-scoped resets, localStorage persistence, and one contract every consumer honours.
Help centre — repo-versioned, palette-searchable
Help articles are Markdown files in the repository. A startup loader parses them into a per-audience corpus (user | admin | both). A three-endpoint HTTP surface feeds the SPA; a Cmd+K palette group surfaces them anywhere in the product. Two MCP tools — search_help_articles and get_help_article — hand the same corpus to the agent, so “how do I set up SSO?” is answered by reading the same article an admin would open. One source of truth across product and agent.
Rich text without a hole in the wall
Any textarea field on any entity — account notes, deal descriptions, contact bios — is one Form Designer click away from being a TipTap rich-text editor. Bold, italic, headings, lists, links.
Every save runs through server-side sanitisation — a fourteen-tag allowlist (p, br, strong, em, u, ul, ol, li, code, pre, h2, h3, blockquote, a[href]) with a URL filter that rejects javascript:, data:, vbscript:, protocol-relative, and backslash-escaped variants. DOMPurify runs the same allowlist in the browser with a forced rel="noopener noreferrer" on every anchor. CSP blocks inline scripts as a third backstop. Three independently-sufficient layers of defence. Every surface in the product sanitises user-supplied HTML against a closed allowlist at write time.
Vanity domains and dedicated share codes
A tenant that wants their portal at partners.acme.com points a CNAME. A DNS-TXT ownership challenge verifies the domain; the reverse proxy issues the certificate and starts serving. The admin surface stays routed on its own hostname; the vanity domain reaches only the portal container, because the admin code is not deployed at that origin.
Partner content shares use the same grant primitive as every other share. A dedicated share code — one grant row with a partner grantee kind, one sidecar row for the external recipient — scopes a private download to a single audit-trailed recipient. Same object-storage bucket, same presigned-URL machinery, same audit log as the internal share flow. Every share flows through one primitive. Full partner-channel story here.
The takeaway
The substrate does the work. Six audiences read it through four role-appropriate hosts. Every write is idempotent, every session is origin-scoped, every submission passes defensive checks, and every field that accepts HTML runs a closed allowlist end-to-end. The agent has tool parity with the admin SPA across the public-surface MCP tool set — every inbound-form, page, public-asset, and partner-share operation the SPA supports is available to the agent through the MCP catalogue. Enterprise buyers ask where the boundary lives; the answer is the infrastructure.