Skip to content

feat(release): encrypt release snapshots#18

Open
anserwaseem wants to merge 2 commits into
mainfrom
issue/4
Open

feat(release): encrypt release snapshots#18
anserwaseem wants to merge 2 commits into
mainfrom
issue/4

Conversation

@anserwaseem

@anserwaseem anserwaseem commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Closes #4

Summary

Encrypt release snapshots before upload so secrets and app state are not stored as plain JSON in Firebase Storage.

  • Create: snapshot → brotli → AES-256-GCM → upload releases/{appId}/{versionId}.enc.json
  • Use: download from Storage (Firebase auth) → decrypt locally → restore files, config, and secrets
  • Key: ENSEMBLE_ENCRYPTION_KEY in alias-scoped .env.secrets (or .env.secrets.<alias>); generate with openssl rand -hex 32
  • Legacy: plain .json releases rejected on release use; re-create after adding the key

What’s in Storage

A new release in the bucket is an encrypted envelope — not readable app JSON:

{
  "v": 1,
  "comp": "br",
  "alg": "AES-256-GCM",
  "iv": "",
  "tag": "",
  "ciphertext": ""
}

ciphertext holds the brotli-compressed snapshot; only a holder of ENSEMBLE_ENCRYPTION_KEY can decrypt it locally.

UX

Situation Behavior
Key missing release create, list, and use fail immediately with a clear message + hint
Key invalid (wrong length/format) Fail at gate with file-scoped message
Key wrong but valid format List/picker still works (metadata is in Firestore); decrypt fails on use with a user-friendly error
Legacy plaintext release Rejected with instruction to re-create

Release metadata (date, message, hash) comes from Firestore. Encryption protects snapshot content in Storage.

Test plan

  • npm run build && npm test
  • release create / list / use with valid key (dev + --app uat)
  • release use --hash <id> (non-interactive)
  • Missing key → blocked on create, list, use (per-alias secrets file in error)
  • Invalid key length → blocked at gate
  • Wrong key (one char changed) → list OK, decrypt fails with friendly message
  • Legacy .json release → rejected on use
  • Firebase Storage: new .enc.json blob is encrypted envelope, not plaintext
  • APP_ALIAS=dev ./scripts/test-secure-release-local.sh from project root
  • release useensemble push end-to-end

Screenshots

  1. incorrect enc key - tested by altering one or two chars of enc key
  2. invalid key - tested by reducing length of enc key
  3. no enc key
image

an example release enc.json file accessed using authenticated url
image

Ops [DONE]

Storage rules: tightened releases/{appId}/* to collaborator-scoped read (before: any authenticated user). Encryption protects content; rules control who can download ciphertext.
Read more on the blog post here

// Ensemble user id — collaborators use users_{userId}, not Firebase sub.
function releaseCollaboratorRole(appId) {
  let appPath = /databases/(default)/documents/apps/$(appId);
  return firestore.exists(appPath)
    ? firestore.get(appPath).data.collaborators.get('users_' + request.auth.token.userId, '')
    : '';
}
function canAccessRelease(appId) {
  return request.auth != null && releaseCollaboratorRole(appId) in ['write', 'owner'];
}

// release snapshots (CLI — encrypted .enc.json)
match /releases/{appId}/{allPaths=**} {
  allow read, write: if canAccessRelease(appId);
}

Plain JSON in Storage exposed secrets. Snapshots now AES-256-GCM
to .enc.json; use decrypts locally and restores env files.
@anserwaseem anserwaseem self-assigned this Jun 23, 2026
@sharjeelyunus

Copy link
Copy Markdown
Member
image

unable to use the release I just created

@anserwaseem

Copy link
Copy Markdown
Contributor Author

unable to use the release I just created

could you test again please - i tested again on:

existing app
image

new app:
image

- Release create now orders snapshot lists from .manifest.json
so filesystem/readdir order cannot scramble widgets or langs.
- Release use writes manifest literally via manifestFromSnapshot
instead of merging stale local state.
- Env files upsert on use
to preserve key order.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

make the cli releases secure

2 participants