Fail-closed at every layer. Open about every layer.
Engine: 15 of 15 external audit findings closed. App surface: SOC 2 via Assembly. Infrastructure: Microsoft Azure. Most trust portals gate the details. Ours doesn't.
Where your data lives
Three layers. Each audited on its own terms.
Customer data passes through Ditto's rendering engine, surfaces in the Assembly app for client review and approval, and rests on Microsoft Azure infrastructure. Each layer carries its own attestations and its own controls. We don't outsource any layer's posture to the layer below.
Ditto
Assembly ↗
Microsoft Azure ↗
The trust thesis: outsource what's already best-in-class (Azure infrastructure, Assembly app surface), engineer the rest yourself, audit it, and publish what you find.
Compliance roadmap
SOC 2 in flight. Dates, not vibes.
Most pre-SOC-2 vendors say "compliant." We say "in flight" with target dates and a published list of what's done versus what isn't. Below is the actual plan.
Engine audited. App + Infra inherited.
Fail-closed defaults across 8 boundaries. External audit complete.
- 15 of 15 external findings closed (April 2026)
- HMAC, SSRF, path-traversal, PII redaction live
- SOC 2 inherited via Assembly app surface
- ISO 27001 + SOC 2 inherited via Azure infra
SOC 2 Type I
Point-in-time attestation that controls are designed correctly.
- Drata or Vanta as the controls platform
- Control framework formalized + auditor selected
- Policy library published (NDA-gated)
- Background checks + JML formalized
SOC 2 Type II
Operational attestation across a 6-9 month observation window.
- Continuous evidence collection live
- Tabletop IR exercises on cadence
- Annual policy review completed
- Pen-test cadence formalized
Other frameworks · status
Engine · Ditto
Ten controls. All in production.
Ditto processes untrusted data: webhook payloads, CSV records, template references that fire network requests and run a headless browser. The threat surface is large. Every defense below is fail-closed by default and audit-verified.
Authentication & authorization
API keys with explicit scopes (read, write, admin). Dashboard keys separate from service keys. No password-based access, ever.
Per-client secret isolation
One credential per client, scoped to that client's data only, stored under a per-client environment variable. A leaked credential exposes one client, not the portfolio.
Webhook signature verification
HMAC-SHA256 over the exact raw request bytes (not a re-serialization). Constant-time compare. Mis-signed = 401, no exceptions. Inbound and outbound.
SSRF defense
Every network request from the headless browser is intercepted at the Playwright layer. Schemes restricted to file, data, blob, and allowlisted https. Hostnames canonicalized, DNS re-checked against private IP ranges.
Path-traversal prevention
Template IDs match a strict allowlist of characters. Filename components are sanitized for traversal sequences, null bytes, and OS-reserved characters. Resolved paths are re-verified under the expected base directory.
Input validation on every boundary
Zod schemas validate every API request body. Unknown fields are stripped. String lengths capped, numeric ranges enforced. A round-trip test forces every new option to be plumbed end-to-end before merge.
HTML injection prevention
Record data is HTML-escaped before injection. Image src values run through a safe-scheme guard. Preview server uses a JSON data island plus per-request CSP nonce so injected scripts can never execute.
PII-safe logging
Structured logs via pino. Known PII fields (name, email, phone, ssn, token, secret, etc.) are auto-redacted. Full record payloads are never logged. Production error responses are redacted to a request-ID reference.
Resource limits
JSON body cap (default 10 MB). Per-render timeout (30 s static, auto-scaled for video). Hard ceiling on concurrent Playwright pages. Rate limiting (200 req/min prod) with standard Retry-After headers.
Review-token security
Client-review URLs use 256-bit tokens (crypto.randomBytes(32)). Referrer-Policy: no-referrer on every response, expiry windows, manual revoke, one-shot on submission. Per-asset basename check on every fetch.
Layer 2 · App surface
The customer-facing experience runs on Assembly.
Client review and approval, file uploads, intake forms, messaging, and billing all happen inside Assembly. We picked it because the security posture comes built in: SOC 2 attestation with continuous control monitoring by Secureframe, plus alignment with GDPR, CCPA, and HIPAA. We don't reinvent any of that.
Inherited via Assembly
What you get when your data is on the app surface
Every claim below is a published Assembly control. None are paraphrased; all are verifiable on Assembly's own trust portal.
- SOC 2 attestation
- Secureframe continuous monitoring
- GDPR, CCPA, HIPAA alignment
- Encryption at rest + in transit
- MFA on all employee access
- Background checks + JML policy
- Annual security awareness training
- Vendor risk reassessed annually
- Incident response with tabletop testing
- BC/DR plan with regular testing
- Network traffic monitoring
- Unique IDs + least-privilege access
- Annual asset inventory review
- Dev/stage/prod environment segregation
Why we don't run our own app surface
- Posture before product. Building a SOC 2-attested app surface from scratch is 12-18 months of work that doesn't make Ditto a better rendering engine.
- Auditable subprocessor. Assembly publishes its policies, its sub-processors, and its monitoring vendor. Your TPRM team gets one more vendor packet, not a black box.
- Boundary-clean. Customer review, file uploads, and billing live in Assembly. Rendering and delivery live in Ditto. The seam is the integration, and the integration is documented.
- You inherit Assembly's posture for the part of Ditto where your data interacts with humans. The engine adds its own posture for the part where data interacts with code.
Layer 3 · Infrastructure
Microsoft Azure, with the paranoid defaults turned on.
Production runs on Azure because Azure ships with the strongest hyperscaler security baseline available. We turn on customer-managed keys, deny-all egress, and a separate delivery domain. We don't take any "off by default" feature for granted.
Azure Container Apps
Renderer + dashboard run as managed containers. Stateless renderer architecture means horizontal scale across regions when needed. No host OS for us to forget to patch.
Azure Blob Storage with customer-managed keys
Rendered assets land in encrypted Blob containers. Encryption keys are customer-managed via Azure Key Vault, not Microsoft-managed defaults. Output mounted on encrypted storage even before Blob, in dev.
Azure Front Door
TLS termination and reverse proxy for both the platform surface and the delivery surface. Native WAF rules layer over Ditto's own request validation.
Restricted egress
Container egress denies all outbound traffic by default. Only the specific services Ditto needs (Assembly webhook receiver, Airtable API, Anthropic, Azure Blob) are allowlisted at the network layer.
Two-domain isolation
Platform surface ({client}.dittoditto.io, auth-gated) and delivery surface (cdn.dittoditto.xyz, unauth, cache-heavy) live on separate origins. A misconfigured public asset URL can't leak admin auth state.
Region pinning + audit log
Compute and storage pinned to a named Azure region. Render-runs SQLite is a full audit log of every job that ever ran. Structured logs forward to a SIEM via Azure Monitor.
Why Microsoft Azure
The largest security baseline of any hyperscaler.
Azure inherits the broadest portfolio of attestations in the industry, the deepest customer-managed-key tooling, and a first-party WAF and reverse proxy that integrate without third-party glue. You don't have to trust us to have configured the floor correctly. Microsoft already did, and they publish the floor plan.
- Inherited certifications. ISO 27001, ISO 27017, ISO 27018, SOC 1, SOC 2, SOC 3, HIPAA, FedRAMP High, PCI DSS, plus 90+ regional and industry frameworks.
- Customer-managed keys are first-class. Key Vault integrates natively with Blob, and key rotation is a single API call.
- Front Door + WAF in one. No separate CDN-plus-WAF stack to misconfigure.
- Region pinning enforced. Container Apps and Blob containers are bound to a named region; data residency is set at provision time.
- Native SIEM integration. Azure Monitor pipes structured logs into your existing observability stack.
Inherited certifications via Microsoft Azure. Each of these covers the underlying compute, storage, and network we deploy on. Available on Azure's published trust documentation.
Threat model
What we defend against, mapped to defenses.
Ditto is an internal service today, but we engineer it as if it were internet-facing. Every threat below has a named defense, a specific control, and a place in the codebase you can ask about under NDA.
| Threat scenario | Primary defense | Control ref |
|---|---|---|
| Malicious webhook | HMAC-SHA256 signature on raw bytes; constant-time compare; WEBHOOK_ALLOWED_CLIENT_IDS gate on client.created events. |
§ 03 · Webhooks |
| Malicious CSV | HTML escape on text fields; isSafeImageSrc on image sources; per-column Zod validation on import. |
§ 06 · Validation, § 07 · XSS |
| Malicious template (SSRF) | Browser-level request intercept; scheme allowlist; canonicalized hostnames; DNS re-resolution against private IP blocklist. | § 04 · SSRF |
| Misconfigured deploy | enforceProductionPosture() at boot; missing API keys, CORS config, or webhook secrets refuse to start the process. |
§ Fail-closed defaults |
| Path traversal | Strict char allowlist on template IDs; sanitizeFilenameComponent; resolved-path guard verifies the final path is inside the expected base directory. |
§ 05 · Paths |
| Resource exhaustion | Body cap (JSON_BODY_LIMIT), per-render timeout, hard ceiling on concurrent Playwright pages, rate limiting with Retry-After. |
§ 09 · Limits |
| Credential leakage | Auto-redaction allowlist on PII fields in logs; full record payloads never logged; production error responses redacted to a request-ID; per-client secret isolation. | § 02 · Secrets, § 08 · Logging |
External audit log
15 findings. 15 closed. Receipts attached.
An external security review covered the codebase in April 2026. Below is the full table of findings, severity, fix PR, and current status. Most trust portals bury this. We publish it.
| Severity | Finding | Fix PR | Status |
|---|---|---|---|
| Critical | Webhook filename write allowed path escape | PR #60 | Closed |
| Critical | Webhook signature verification was opt-in | PR #60 | Closed |
| High | Raw-body re-serialization broke HMAC | PR #60 | Closed |
| High | Preview XSS via query params | PR #63 | Closed |
| High | NODE_ENV defaults to development |
PR #59 | Closed |
| High | CORS defaults to accept-all | PR #59 | Closed |
| Medium | client.created allowlist absent |
PR #60 | Closed |
| Medium | SSRF canonicalization incomplete | PR #61 | Closed |
| Medium | isSafeImageSrc bypassed via non-http scheme |
PR #61 | Closed |
| Medium | Error messages leak internals in prod | PR #64 | Closed |
| Medium | ffmpeg path validation missing on Windows | PR #65 | Closed |
| Low | No npm audit in CI |
PR #64 | Closed |
| Low | WEBHOOK_SECRET unvalidated at boot |
PR #59 | Closed |
| Low | DoS via oversized JSON body | pre-existing | Closed |
| Low | Preview enabled in prod by default | PR #59 | Closed |
Sub-processors
Every vendor in the data path. Disclosed.
A sub-processor is any third-party service that may process customer data on Ditto's behalf. Below is the complete list. Each row links to that vendor's own trust documentation. Notify your TPRM lead of any new addition before we onboard one — we'll publish it here first.
| Vendor | Purpose | Region | Posture | Reviewed |
|---|---|---|---|---|
| Microsoft Azure ↗ | Production hosting (Container Apps), encrypted blob storage, Front Door TLS termination, network controls. | US (default) region-pinned |
ISO 27001 SOC 1/2/3 HIPAA FedRAMP High GDPR | Annual |
| Assembly ↗ | Customer-facing app surface: client review, file uploads, intake forms, messaging, billing. | US | SOC 2 Secureframe GDPR CCPA HIPAA aligned | Annual |
| Anthropic (Claude) ↗ | AI-assisted template authoring (opt-in feature, off by default). Customer record data is not sent to Claude in production renders. | US | SOC 2 Type II GDPR no-train policy | Annual |
| Airtable ↗ | Customer data source when the client opts in. One Personal Access Token per client base; per-client environment variable. | US | SOC 2 Type II ISO 27001 GDPR | Annual |
| Google Fonts ↗ | CDN for typeface assets used in templates. No customer record data ever sent. Used at render time only. | Global CDN | ISO 27001 SOC 2/3 no-tracking | Annual |
Transitive sub-processors (via Assembly)
Assembly publishes the following vendors as part of its own posture. They process data only inside the Assembly app surface, not inside Ditto's engine. Listed here for completeness so your TPRM review covers the full dependency tree.
- AWS
- Vercel
- Sentry
- PostHog
- Stripe
- Customer.io
- Stream.io
- Twilio · Segment
- OpenAI
Incident response
When something goes wrong, here's the runbook.
A documented IR plan, tested on a regular cadence, with a published breach-notification SLA. Most pre-SOC-2 vendors keep this private. We'll send you the full playbook under NDA, but the contour is on the page.
Detect
Structured logs forwarded to a SIEM. Alerting rules on WEBHOOK_DELIVERY_FAILED, render failure rates, auth anomalies, and 5xx error budgets. /api/metrics per-error-code counts are scraped on cadence.
Triage
On-call acknowledges within 15 minutes. Severity classified per the IR runbook (P0 customer data exposure, P1 service down, P2 degraded, P3 cosmetic). Request IDs link every log line back to a job, a client, a render.
Contain
Per-client API key revocation is single-call. Per-token revocation on the review surface is single-call. Network egress allowlist and feature flags are tightened first; root-cause analysis follows.
Remediate
Fix lands as a PR with a regression test that fails on the original input. Backports to long-running deployments. Deploy through the standard pipeline; no out-of-band hotfixes.
Disclose
Affected customers notified within the SLA. Post-incident review published internally within 5 business days. Customer-facing post-mortem available on request, with a published lessons-learned distribution to the team.
Breach notification SLA
Maximum elapsed time from confirmed breach affecting customer data to written notification. Aligned with GDPR Article 33; we won't take longer than the regulator would let us.
- Detection target: within 24 hours of an exploitable event
- Initial triage: within 15 minutes of alert
- Tabletop cadence: quarterly
- Lessons-learned: published to the team within 5 business days
- Customer post-mortem: available on request, redacted only for other affected customers' details
Production hardening
The same checklist we run before every deploy.
Bible §8.14, verbatim. This is the gate every production deploy passes through, with the green-checked items already enforced and the in-flight items targeted for the SOC 2 Type I window.
NODE_ENV=production (or unset, both work — fail-closed default)
API_KEYS set to a long random value with explicit scopes
CORS_ORIGINS explicitly lists the dashboard's origin (no wildcard)
WEBHOOK_SECRET set to a new random value, rotated from dev
WEBHOOK_ALLOWED_CLIENT_IDS lists only the clients authorized to create new tenants
ALLOW_REMOTE_ASSETS defaults to false; explicit per-domain opt-in via campaign manifest
Run behind Azure Front Door with TLS termination and WAF rules
ProvisioningContainer egress denies all outbound except specific allowlisted services
ProvisioningOutput volume mounted on Azure Blob Storage with customer-managed keys via Key Vault
ProvisioningStructured logs forwarded to a SIEM (pino JSON via Azure Monitor)
Provisioning/api/metrics not exposed publicly (dashboard-only, or scraped internally)
Vulnerability disclosure & contact
Found something? Tell us first.
We treat security researchers as partners, not threats. Report responsibly and we'll work with you in good faith. The policy below is our public commitment.
Responsible disclosure policy
What we promise · what we ask
What we promise: we acknowledge every legitimate report within 48 hours, triage on a defined severity scale, share a fix or remediation timeline within 7 business days, and never take legal action against good-faith research conducted under this policy.
- Acknowledgement: within 48 hours of receipt
- Triage: within 7 business days, with severity assigned and a remediation plan
- Fix window: 90 days for unrestricted public disclosure; longer windows by mutual agreement
- Credit: on request, in our security acknowledgements
- Safe harbor: no legal action against research conducted in good faith under this policy
What we ask: report through the official channel below, give us a reasonable window to fix before public disclosure, and don't access more data than is necessary to demonstrate the issue.
- In scope: ditto.copilot.app, dittoditto.io, cdn.dittoditto.xyz, and any production endpoint operated by Ditto.
- Out of scope: third-party services we depend on (Assembly, Microsoft Azure, Anthropic, Airtable). Report those to the vendors directly.
- Excluded methods: social engineering of personnel, denial-of-service attacks, physical attacks against staff or property, and any disruption of customer data or service.
How to reach us
Direct, monitored, and answered.
Subject prefix Security — routes directly to the security responder.
All available under NDA. Most TPRM packages return within 2 business days.
This page is the canonical trust portal. Sub-processors, IR runbook revisions, and SOC 2 milestones are reflected here as they change. Last updated 2026-05-04.
Stop renting trust. Start owning it. Every claim above is on this page. The rest is a one-email NDA away.
Start a TPRM review
Get a plan
Start a Ditto Campaign