Skip to content

Security ​

Tenancy ​

Every business table carries organization_id as its second column, and reads go through TenantContext — the only source of "current organization" (decision D-002). The organization is never accepted from the client: the dashboard binds it from the session, the API binds it from the API key. Every tenant-scoped resource has a test proving org A cannot read, update or delete org B's row through every entry point.

RBAC ​

spatie/laravel-permission with teams keyed on organization_id (decision D-003). Permissions are <resource>.<verb> — recording.delete, assistant.publish. Roles are per organization, so being an admin of one tenant grants nothing in another.

Keys and secrets ​

  • Private API keys are stored hashed. The plaintext exists once, in the create response.
  • Public keys are stored in the clear, because they are public by definition, and carry origin and assistant restrictions since they ship inside browser bundles.
  • Provider credentials use a separate encryption key (CREDENTIAL_ENCRYPTION_KEY, decision D-004), not APP_KEY, so rotating the application key does not require re-encrypting every tenant's provider secrets.
  • Nothing logs a credential, an Authorization header or a raw webhook body containing one: App\Support\Redactor and packages/contracts/src/redact.ts exist for this.

SSRF ​

Every tenant-supplied URL — tool endpoints, webhook targets, MCP servers, knowledge sources — is checked before it is dereferenced, and again on every redirect hop. Private ranges, link-local addresses and the cloud metadata endpoint are refused.

Sandboxing ​

bash, computer and textEditor tools never execute in the gateway process. They run in a container with no network by default, a CPU and memory cap, a read-only root filesystem and a bounded output size.

Prompt injection ​

Untrusted content — tool results, MCP results, retrieved chunks, caller speech — is wrapped before it reaches the model, so text inside it cannot be read as an instruction from the operator.

Client auth ​

Browsers never hold a private key. See Authentication.

Audit log ​

audit_logs is insert-only and records who did what to which resource, from where. Credential values are excluded from the recorded diff.

Data retention ​

Per-organization retention, set separately for recordings, transcripts, events, logs and structured outputs, plus a zero-data-retention mode that keeps no audio or transcript beyond the call. Retention runs nightly; zero-data-retention with no delay runs inline at finalisation.

Recording consent is an announcement, not consent

An announcement can be played. Recording starts before it is spoken, and a refusal cannot be honoured — the gateway can only ever report announced, because nothing collects an acknowledgement. Read consent_state: "announced" as "a notice was made". See AI disclosure & consent.

Compliance ​

WARNING

SOC 2, HIPAA and PCI modes are designed and the controls above are the technical half of them. No certification is claimed.

SAML SSO is not implemented — the SSO service says so in as many words, and there is deliberately no route — for want of an XML-DSig library. OIDC is implemented. SCIM provisions users but not groups.

Documented against the code. Anything not yet implemented is labelled, never described as working.