Skip to content

Security & compliance

Everything a security reviewer usually asks about a PointOfContactAI deployment, with pointers to the pages that go deeper. The short version: all conversation data lives in your Azure subscription, only your Function App’s managed identity can read it, and the only data that ever leaves your stack is two content-free status payloads.

Where your data lives

Every data-holding resource is deployed into the Managed Resource Group in your subscription, in the region you choose in the wizard:

ResourceHoldsAccess
Cosmos DBChat sessions, messages, token-usage recordsKey-based auth disabled; only the Function App’s managed identity holds a data-plane role.
Key Vault (RBAC mode)Your admin key (admin-key secret)Only the Function App’s managed identity holds a data-plane role. Nobody in your tenant — and not the publisher — can read secrets.
Azure OpenAIProcesses chat completionsKey-based auth disabled; MI-only.
SignalR ServiceTransient real-time messagesMI-only token minting.
Storage accountFunctions runtime state + deployment package (no conversation data)Shared-key access retained for the Functions host and publisher package publishing.
Application InsightsOperational telemetry (no message content — see below)Yours; the publisher has no access.

Residency note: data at rest stays in your chosen region (single-region Cosmos, no geo-replication). One nuance to disclose to reviewers: the gpt-5-mini model deployment uses Azure OpenAI’s GlobalStandard SKU — data at rest stays with your resource, but Microsoft may route inference processing to Azure OpenAI capacity in other regions. If you require strictly in-region inference, contact support before deploying.

Retention: conversations are kept until you delete them — there is no automatic purge. Deleting the Managed Application destroys all data immediately.

What leaves your stack (complete list)

Your stack calls exactly two publisher endpoints, and both payloads are schema-validated with unknown fields rejected at the boundary:

  • seat-reportsubscriptionId, seatQuantity, timestamp, plus optional aggregates: tokenUsage { inputTokens, outputTokens, windowStart } and quotaStatus { exceeded, seatsUsed, seatQuota, daysOverQuota, gracePeriodDays, blocked, quotaExceededAt }. Used for Marketplace billing/metering and the quota-grace email.
  • deploy-statussaasSubscriptionId, deploymentId, deployedVersion, status, timestamp, optional errorMessage (capped, no content). Used to confirm your deployment is healthy.

No message bodies, no user names, no email addresses, no session ids. The Concepts page shows where these sit in the architecture.

Publisher access (what we can and cannot do)

  • We hold Contributor on the Managed RG only — a control-plane role used for plan PATCHes, runtime updates, and support-requested configuration changes.
  • Contributor carries no data-plane access: Cosmos rejects it (local auth disabled, no data-plane role), Key Vault rejects it (RBAC mode, no data-plane role). We cannot read your conversations or secrets.
  • We never authenticate into your tenant or run code as your users.
  • Audit us: every publisher action lands in your Activity Log under the PointOfContactAI-Orchestrator principal (app id d3393ba1-85a0-496c-8c07-49d020e1fabd). Details in Managed Resource Group.

Authentication model, per surface

SurfaceAuth
Visitor chat (POST /api/chat, /api/chat/stream, GET /api/messages, POST /api/negotiate)Anonymous. Browser access limited by the allowedOrigins CORS allowlist; per-IP and per-session rate limits. No bearer token — CORS does not stop non-browser clients.
Admin surface (/api/dashboard, /api/cost, /api/sessions/*, /api/ops/rotate-key, Teams app generator)Admin key — single shared secret in your Key Vault; ?code=, Authorization: Bearer, or an HttpOnly session cookie signed with the key. Details →
Agent surface (/api/agent/*)Teams SSO — JWTs from your AAD app, validated by signature against your tenant’s keys. No publisher-side identity in the path. Details →
Status (GET /api/health, GET /api/plan)Anonymous, content-free.

Full endpoint list in the API reference.

Secrets inventory

SecretWhereWho can read it
Admin keyYour Key Vault (admin-key), created at your first rotationYour Function App’s MI; whoever you gave the key to. Not us.
AAD app values (client id, tenant id, resource URI)Function App app settingsNon-secret identifiers.
AAD app client secretPrinted once by the setup script; not consumed by the current runtime (why)You. Never send it to anyone, including us.

Note the honest caveats: a fresh deploy has no admin key until you rotate (do it first), and the admin key is a single shared secret — treat it like a root credential and rotate it when staff change.

Telemetry & logging

  • Application Insights lives in your subscription; the publisher has no access to it.
  • The codebase enforces a no-content-logging rule in CI: a lint gate fails the build if any log statement references content-bearing field names (message bodies, emails, display names, session ids, …). The enforced field list is in Telemetry events.
  • The two egress payloads above are the only cross-boundary telemetry.

Encryption

  • In transit: HTTPS everywhere — the Function App is httpsOnly, storage enforces HTTPS with minimum TLS 1.2, and browser↔widget, Teams↔stack, and stack↔publisher calls are all TLS.
  • At rest: Azure platform encryption (default, always on) for Cosmos DB, Storage, Key Vault, and Application Insights.

Known limitations to put in your risk register

  1. Visitor chat endpoints are anonymous by design — protected by CORS (browser-only) and rate limiting, not by tokens. Anyone with your Function App hostname can start a chat session, as with most public website chat widgets.
  2. The wizard’s default allowedOrigins is * — tighten it to your real domains (how).
  3. The admin surface uses one shared key, not per-user accounts — no per-admin audit trail on dashboard actions.
  4. A fresh deployment starts with no admin key — the dashboard is open until your first rotation. It’s step 1 of the post-install checklist.