Encryption
Union.ai encrypts all data at rest and in transit across every storage and communication path in the platform. Transit encryption uses TLS for all communication paths, with mutual TLS (mTLS) layered through the Cloudflare Tunnel for cross-plane traffic. At-rest encryption is provided by cloud provider services (S3 SSE, GCS encryption, Azure SSE) for customer-side storage, and by managed cloud database services (AES-256/KMS) for the control plane. Data that transits control plane memory (structured task I/O, secret values during creation, log streams) is encrypted on every network hop but exists as plaintext in process memory during request handling.
Encryption at rest
| Storage | Standard | Key Management |
|---|---|---|
| Object Store (S3/GCS/Azure Blob) | Cloud-provider default (SSE-S3, Google-managed, Azure SSE) | Cloud provider managed; CMK supported |
| Container Registry | Cloud-provider encryption | Cloud provider managed |
| Secrets Backend (cloud) | Cloud-provider encryption | Cloud secrets manager |
| Secrets Backend (K8s) | etcd encryption | K8s cluster-level |
| Observability metrics store (per-cluster) | Encrypted persistent disk | Cloud provider managed |
| Control plane databases | Managed cloud database service | AES-256; cloud KMS managed |
All data at rest is encrypted using cloud-provider native encryption. Each storage backend uses the default encryption mechanism provided by the underlying cloud service, ensuring that encryption is always active without requiring additional configuration. For object stores (S3, GCS, Azure Blob Storage), customers can bring their own customer-managed keys (CMK) to gain full control over key rotation, access policies, and revocation. Control plane databases use AES-256 encryption managed through the cloud KMS, consistent with Union.ai’s SOC 2 Type II controls.
Encryption in transit
All communication paths in the Union.ai platform are encrypted using TLS:
- Client to control plane: all API and UI traffic uses TLS 1.2 or higher.
- Data plane to control plane: two outbound-only channels (Cloudflare Tunnel with mTLS, and direct gRPC over TLS 1.2+). See Network architecture.
- Client to object store: presigned URLs always use HTTPS, enforced by the cloud provider.
- Internal data plane communication: uses cloud-native TLS for inter-service traffic.
No unencrypted communication paths exist in the platform. The combination of TLS at the edge, mutual TLS through the tunnel, TLS on the gRPC channel, and HTTPS for presigned URLs ensures end-to-end encryption for all data in transit. Data content is never logged at any log level. (Note: if debug logging is enabled in the control plane, authentication credentials, not data content, may be logged in plaintext during request header propagation.)
For details on the cross-plane channels, see Network architecture.
Data protection summary
The following table summarizes the encryption state for each data category across all phases:
| Data category | In transit | At rest | Enters control plane memory? | Persisted in control plane? |
|---|---|---|---|---|
| Files, directories, DataFrames | HTTPS (presigned URL) | S3 SSE / GCS / Azure SSE | No | No |
| Code bundles | HTTPS (presigned URL) | S3 SSE / GCS / Azure SSE | No | No |
| Container images | HTTPS (registry pull) | ECR/GCR/ACR encryption | No | No |
| Inter-task I/O (in-cluster) | Cloud SDK TLS | S3 SSE / GCS / Azure SSE | No | No |
| Structured task inputs (run submission) | TLS + TLS/mTLS/tunnel | S3 SSE / GCS / Azure SSE | Yes (plaintext, transient) | No |
| Structured task I/O (retrieval) | TLS + TLS/mTLS/tunnel | S3 SSE / GCS / Azure SSE | Yes (plaintext, transient) | No |
| Secret values (create/update) | TLS + TLS/mTLS/tunnel | ASM/GCP SM/AKV/etcd encryption | Yes (plaintext, transient) | No |
| Secret values (get/list/delete) | TLS | ASM/GCP SM/AKV/etcd encryption | No (metadata only) | No |
| Secret values (runtime injection) | Linkerd mTLS / Kubernetes API | Secret backend encryption | No (data plane only) | No |
| Execution logs (streaming) | TLS + TLS/mTLS/tunnel | CloudWatch / Cloud Logging / Azure Monitor | Yes (plaintext, transient) | No |
| Task definitions (TaskSpec) | TLS | Control plane database (AES-256/KMS) | Yes (read from DB) | Yes (encrypted at rest) |
| Run/trigger specs | TLS | Control plane database (AES-256/KMS) | Yes (read from DB) | Yes (encrypted at rest) |
| Error messages | TLS (gRPC) | Control plane database (storage-level) | Yes (read from DB) | Yes |
| Execution metadata (phase, timestamps) | TLS (gRPC) | Control plane database (AES-256/KMS) | Yes (read from DB) | Yes (encrypted at rest) |
“Transient” means the data exists in process memory only for the duration of a single request and is not written to disk, cache, or logs. For details on each data flow pattern, see Data flow.
Verification
Encryption at rest
Reviewer focus: Confirm that all storage backends are encrypted and verify key management configuration.
How to verify:
-
Check object store encryption:
aws s3api get-bucket-encryption --bucket <customer-bucket>The output should show SSE configuration (SSE-S3, SSE-KMS, or SSE-C depending on configuration).
-
Check Kubernetes storage classes for ClickHouse volumes:
kubectl get sc -o yamlEncrypted storage classes should be configured for ClickHouse persistent volumes.
-
For customer-managed keys, verify key configuration:
aws kms describe-key --key-id <key-id>
This verification is fully self-service.
Customer-managed key authority
Reviewer focus: Confirm that bulk data is unreadable without the customer’s encryption keys, regardless of who holds a presigned URL.
How to verify:
-
From a successful workflow run, capture a presigned URL for an output artifact (Union.ai UI, browser developer tools → Network tab, or via
uctl). -
Confirm the URL fetches the artifact:
curl -o /tmp/output "<presigned-url>" -
Disable the customer-managed key that encrypts the bucket:
aws kms disable-key --key-id <bucket-kms-key-id>On GCP, disable the CMEK key version protecting the bucket. On Azure, disable the customer-managed key in Key Vault.
-
Re-fetch the same URL (or issue a fresh one). The request should fail with
KMS.DisabledException(AWS) or the equivalent. Re-enable the key to restore access.
The presigned URL itself is unchanged and still authentic. The data behind it is opaque without the customer’s key – proof that customer-controlled keys are the final gate on bulk data access, independent of who can issue URLs.
Encryption in transit
Reviewer focus: Confirm that all communication paths use TLS and that no plaintext channels exist.
How to verify:
-
Verify TLS on the control plane endpoint:
openssl s_client -connect <cp-endpoint>:443Confirm TLS version (1.2 or higher) and cipher suite in the output.
-
Check the browser lock icon when accessing the Union.ai UI for certificate details.
-
Confirm that all presigned URLs use HTTPS by inspecting any presigned URL generated by the platform.
-
Check the Cloudflare Tunnel pod logs for TLS handshake confirmation:
kubectl logs <tunnel-pod> -n <control-plane-namespace>
This verification is fully self-service.