Security Overview

The DeployYourApp security model — trust boundaries, what we can and cannot see, tenant isolation, and the cryptography in use.


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, the bundle you built
DeployYourApp server Signing public key, uploaded bundle bytes Signing private key
Object storage (MinIO) Uploaded bundle bytes exactly as received Any key material
End-user device Signing public key (plugin or updater 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 public keys. See Code signing.

What DeployYourApp can and cannot see

The server receives and stores your bundle as a plain ZIP, so our operators can read its contents. Bundles are not encrypted — that is a deliberate choice, because an opaque payload is unreviewable by an app store and was the direct cause of a Guideline 2.5.2 rejection. See Encryption for the full reasoning and for what protects a bundle instead.

What we cannot do is produce a bundle your app will accept: the signing private key stays on your machine, verification of it is mandatory on every client, and each bundle additionally carries a per-file SHA-256 manifest checked during extraction.

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 :orgId path parameter, rejects soft-deleted orgs, and looks up a member row for the calling user. No member row means 403.
  • An API key is bound to the organization it was created in. orgContext rejects it against any other org, even one its creator belongs to.
  • Permission checks run separately from membership checks. requirePermission resolves 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_URL is set).
  • Superadmin platform routes require isSuperAdmin on the user record and are checked independently of organization membership.

Granting platform superadmin

There is deliberately no API or dashboard control for this: isSuperAdmin is declared to Better Auth with input: false, so no sign-up or update-user request can set it. Promotion is a direct database change, made against an account that already exists:

docker exec -it dya-postgres psql -U deployyourapp -d deployyourapp \
  -c "UPDATE \"user\" SET \"isSuperAdmin\" = true WHERE email = 'person@example.com';"

UPDATE 1 confirms it; UPDATE 0 means no account with that email has signed up yet. The holder sees an Admin Panel entry in the dashboard sidebar and can reach /admin; a page reload is enough to pick it up, because the session is re-read from the database on each request. Set the column back to false to revoke.

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, verification mandatory on every client Code signing
Bundle integrity SHA-256 Hex-encoded, timing-safe comparison server-side Code signing
Per-file bundle manifest SHA-256 One hash per file, checked in both directions during extraction Code signing
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
Two-factor secrets at rest AES-256-GCM Symmetric, keyed by BETTER_AUTH_SECRET Encryption
Build environment variables AES-256-GCM 256-bit key from FIELD_ENCRYPTION_KEY, 128-bit IV Encryption
Transport HTTPS/TLS HSTS max-age=31536000, includeSubDomains, preload Encryption

Bundle payloads are not encrypted. See Encryption.

Server hardening

  • Security headers are set by @fastify/helmet: a content security policy with default-src 'self', HSTS as above, and Referrer-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. localhost is accepted only when NODE_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_ERROR body; 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.


Previous
Team & Access
Next
Code Signing