Security
What DataEgress never has access to
- Your production database. DataEgress only calls the HTTP endpoint you
expose via
defineDataset(...).handler()— it never holds database credentials, never opens a DB connection, never sees your schema. - Raw destination credentials at rest.
secretAccessKeyvalues are encrypted (AES-256-GCM) before being stored, and are never returned by any API response —GET/POST /api/destinationsalways mask them.
What DataEgress does have access to, and for how long
- Row data, transiently, while generating an export file. Rows pass through DataEgress's process to be written to CSV/Parquet and uploaded; they are not retained beyond the local temp file, which is deleted after a successful upload.
- The generated export file itself, for up to 7 days, for
signed_urldestinations (stored in DataEgress's own storage — local disk in dev, R2 in production if configured) or as a preview mirror alongsides3deliveries. An hourly automated sweep (sweepExpiredExportObjects) deletes the object after that window; the export's row count/checksum/manifest metadata is kept in the database regardless, for delivery history. Files delivered directly to a customer-owned S3/R2 bucket are not covered by this — once delivered, they're entirely under that bucket's own retention, outside DataEgress's control. See limitations.md.
Authentication
Two independent mechanisms, both resolving to the same server-side
organizationId scoping — never trusted from the request body:
- Cookie session (dashboard, human operations): password hashed with scrypt, opaque random session token stored in Postgres, httpOnly/secure cookie. Logging out deletes the session row — it stops authenticating immediately, not just client-side.
- API key (
Authorization: Bearer dte_live_..., SDK / server-to-server): 192 bits of random entropy; only its SHA-256 hash is ever persisted, never the full key. Shown once, at creation. Revoking a key is immediate — the hash lookup simply stops matching an active row.
A basic, single-instance in-memory rate limit slows down repeated failed Bearer attempts. It is not a distributed rate limiter (that would need Redis/Upstash — deliberately out of scope for now) and resets on cold start.
Encryption
- Destination credentials: AES-256-GCM, key derived from
DATARELAY_ENCRYPTION_KEY. - Local signed download URLs: HMAC-SHA256 tokens (
DATARELAY_SIGNING_SECRET), time-limited (1 hour). - In transit: all destination uploads use HTTPS (AWS SDK default); the
fetchPageHTTP call to your dataset endpoint should be HTTPS in production (the SDK doesn't enforce this in V1 — see limitations.md).
Change both DATARELAY_ENCRYPTION_KEY and DATARELAY_SIGNING_SECRET to
real random values before deploying anywhere beyond local dev. The
defaults are intentionally insecure placeholders.
GDPR / data retention
- Generated export files in DataEgress-controlled storage are automatically
deleted after 7 days (
lib/retention.ts'sEXPORT_OBJECT_RETENTION_DAYS, enforced by an hourly Inngest sweep — not a manual or best-effort process). Run metadata (row counts, checksums, manifests) is retained in the database after that, for delivery history. - Files delivered directly to a customer-owned S3/R2 bucket are outside this policy entirely — DataEgress never deletes anything from a destination bucket it doesn't own.
Not yet implemented as product features:
- No per-destination data residency controls.
- No formal DPA/subprocessor documentation (this is a codebase, not a company with a legal entity yet).
If you're evaluating DataEgress for a compliance-sensitive use case today, treat V1 as infrastructure you operate yourself, not a vetted subprocessor.
SOC 2
Not pursued yet, deliberately — see the "why" in the product's own research: a SOC 2 Type II engagement costs real money and takes a 6–12 month observation period, and the customer-callback architecture (DataEgress never touches your database) meaningfully lowers what a security reviewer needs to approve without it. Revisit once a real deal is blocked on it.
Reporting a security issue
This is an early-stage internal project without a public disclosure program yet. File an issue in this repo, or contact the maintainer directly.