This page describes the security model in one place: where the trust boundaries sit, what DeployYourApp can read, how organizations are isolated from each other, and which algorithms are used where. Each area links to a page with the full detail.
Trust boundaries
There are four parties in an OTA update, and each holds different secrets.
| Boundary | Holds | Never holds |
|---|---|---|
| Your machine / CI | Signing private key, encryption public key, plaintext bundle | — |
| DeployYourApp server | Signing public key, encryption public key, uploaded bundle bytes, wrapped session key | Signing private key, encryption private key |
| Object storage (MinIO) | Uploaded bundle bytes exactly as received | Any key material |
| End-user device | Encryption private key (compiled into your native binary), signing public key (plugin config) | Signing private key |
The signing private key never leaves the machine that generated it. dya keys generate writes
it to disk with mode 0600 and uploads only the two public keys. See
Code signing.
What DeployYourApp can and cannot see
Without bundle encryption, the server receives and stores your bundle as a plain ZIP. Our operators can read its contents.
With bundle encryption, the CLI encrypts the bundle before upload. The server stores ciphertext plus an RSA-OAEP-wrapped AES session key. The key that unwraps it is the encryption private key, which lives only in your app binary — the server never receives it. Under that configuration we cannot read bundle contents. See Bundle encryption.
Encryption is not on by default. It activates when dya-encryption-public.pem is present in
the directory you run dya deploy from.
Separately, the SDKs report a random device UUID and a set of version strings on every update
check, and send analytics events if analytics is left enabled. eventData on an analytics
event is whatever your app puts in it, and we store it verbatim.
SDK data collection lists every field.
Tenant isolation
Isolation is enforced at the request level, not by separate databases. All organizations share one PostgreSQL database and one MinIO bucket namespace.
- Every organization-scoped route runs
orgContext, which loads the org by the:orgIdpath parameter, rejects soft-deleted orgs, and looks up amemberrow for the calling user. No member row means403. - An API key is bound to the organization it was created in.
orgContextrejects it against any other org, even one its creator belongs to. - Permission checks run separately from membership checks.
requirePermissionresolves the member's role and tests it against the required permission string. API keys must additionally carry that permission in their scope list. - Bundle objects are stored under a path prefixed with the organization ID and app ID, and are
served through presigned URLs valid for 1 hour (or through your CDN if
BUNDLE_CDN_URLis set). - Superadmin platform routes require
isSuperAdminon the user record and are checked independently of organization membership.
The POST /api/update and POST /api/stats endpoints used by devices are unauthenticated by
design. They are keyed by the globally unique app ID and are rate limited.
Cryptography summary
Every value below is what the shipped code does. Details on the linked pages.
| Purpose | Algorithm | Parameters | Details |
|---|---|---|---|
| Bundle signing | RSASSA-PKCS1-v1_5 with SHA-256 | RSA-4096 key, base64 signature | Code signing |
| Bundle encryption | AES-256-GCM | 256-bit key, 96-bit nonce, 128-bit tag | Encryption |
| Session key wrapping | RSA-OAEP | SHA-256 digest, MGF1-SHA-256, empty label, RSA-4096 key | Encryption |
| Bundle integrity | SHA-256 | Hex-encoded, timing-safe comparison server-side | Encryption |
| Password hashing | scrypt | N=16384, r=16, p=1, dkLen=64, 16-byte random salt | Authentication |
| API key hashing | SHA-256 | Hex-encoded, plaintext never stored | Authentication |
| Two-factor codes | TOTP | 6 digits, 30-second period | Authentication |
| Build environment variables | AES-256-GCM | 256-bit key from FIELD_ENCRYPTION_KEY, 128-bit IV |
— |
| Transport | HTTPS/TLS | HSTS max-age=31536000, includeSubDomains, preload |
— |
Server hardening
- Security headers are set by
@fastify/helmet: a content security policy withdefault-src 'self', HSTS as above, andReferrer-Policy: strict-origin-when-cross-origin. - CORS uses an explicit origin allow-list. There is no subdomain wildcard, so a compromised
unrelated subdomain is never granted credentialed CORS.
localhostis accepted only whenNODE_ENV=development. - All request bodies are validated with Zod schemas at the route boundary. Fields not in the schema are stripped before any database write.
- Database access goes through Prisma, which parameterises every query.
- In production the error handler returns a generic
INTERNAL_ERRORbody; stack traces are logged, never returned. - Every endpoint is rate limited. See the table in Authentication.
- Privileged actions are written to an audit log with actor, organization, IP address, user agent, and timestamp. The audit log is available on the Scale plan and above.
What we do not have
We hold no SOC 2, ISO 27001, PCI DSS, or HIPAA certification or attestation. We do not run a paid bug bounty. Do not represent otherwise to your own customers.
Report a vulnerability to info@deployyour.app. See
Incident response for what happens next.