Skip to content

Harden recovery backup flow and add promotion smoke gate - #3

Merged
ErenAri merged 15 commits into
mainfrom
codex/pilot-readiness
Apr 15, 2026
Merged

Harden recovery backup flow and add promotion smoke gate#3
ErenAri merged 15 commits into
mainfrom
codex/pilot-readiness

Conversation

@ErenAri

@ErenAri ErenAri commented Apr 15, 2026

Copy link
Copy Markdown
Collaborator

This mirrors the backup-recovery reliability and promotion hardening updates:\n\n- Fix blob-store permission initialization on EC2 compose startup\n- Add reusable backup upload/recovery smoke-check script\n- Add optional smoke-check execution in EC2 configure/start helper\n- Harden web onboarding/sign-in paths to fail closed when recovery backup cannot be persisted\n- Add regression tests for backup failure paths and registration PoW nonce coverage\n- Add promote workflow gate to run post-deploy backup/recovery smoke check\n- Add governance policy/validator updates for the new promotion gate\n- Add ignore rules for local smoke artifacts\n\nEnvironment setup was also applied in this repo:\n- pilot + production environments configured\n- PQMSG_PUBLIC_API_BASE_URL set\n- main-only deployment branch policy on both environments\n- production wait timer 5 minutes

Copilot AI review requested due to automatic review settings April 15, 2026 21:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the recovery-backup workflow end-to-end (client + server), adds an EC2 single-host backend runbook/scripts, and introduces a promotion-time post-deploy smoke gate to verify encrypted backup upload/recovery against the live API.

Changes:

  • Add encrypted browser recovery backup upload + cross-browser restore (including linked-device import) to the hosted web app, plus regression tests and PoW registration nonce support.
  • Add EC2 provisioning/deploy/configure + backup/recovery smoke-check scripts, and wire the smoke check into the promote workflow + governance validators.
  • Add Cloudflare Pages _headers contract validation and WASM staging for production builds.

Reviewed changes

Copilot reviewed 33 out of 34 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
scripts/security/validate_web_production_contract.py Enforces Pages _headers security header contract in production validation.
scripts/security/validate_release_governance_workflows.py Adds governance validation for the new promote smoke-gate step.
scripts/dev/start_local_hosted_web_backend.ps1 Helper to run a local relay configured for hosted web origin testing.
scripts/dev/start_cloudflare_quick_tunnel.ps1 Helper to expose a local relay via Cloudflare Quick Tunnel.
scripts/dev/aws/smoke_check_ec2_backup_recovery.ps1 New end-to-end backup upload/recovery smoke-check script.
scripts/dev/aws/provision_ec2_backend.ps1 New AWS CLI-driven EC2 provisioner for backend hosting.
scripts/dev/aws/deploy_ec2_backend_stack.ps1 Copies EC2 compose assets + installs systemd unit on the instance.
scripts/dev/aws/configure_and_start_ec2_backend.ps1 Generates env files, starts stack, optionally runs smoke check.
mobile/web/src/server.ts Adds typed client API calls for backup upload + recovery download; registration PoW nonce field.
mobile/web/src/router.ts Adds import-device route.
mobile/web/src/db.ts Adds IndexedDB insert-if-absent helper for message dedupe.
mobile/web/src/crypto.ts Adds backup upload auth header builder + linked-device package helpers + key rebinding.
mobile/web/src/crypto-wasm.ts Changes WASM import path to staged /pkg/pqmsg_core.js.
mobile/web/src/app.ts Implements hosted onboarding/sign-in hardening, backup sync/restore flows, linked-device import/export UX, message dedupe, PoW registration nonce solving.
mobile/web/src/app.flow.test.ts Adds regression coverage for backup failure paths, PoW nonce, hosted relay bootstrap, message dedupe overlap, settings button behavior, linked-device flows.
mobile/web/src/app.css Adds new onboarding layout styles and textarea styling.
mobile/web/scripts/stage-wasm-pkg.mjs Stages wasm-pack output into public/pkg for production builds.
mobile/web/public/_headers Adds hardened security headers + caching rules for Cloudflare Pages.
mobile/web/package.json Runs WASM staging as part of npm run build.
mobile/web/.env.production Sets default hosted relay URL for production builds.
docs/WEB_DEPLOYMENT.md Documents Cloudflare Pages deployment and local tunnel workflow.
docs/RELEASE_GOVERNANCE.md Adds requirement for post-deploy backup/recovery smoke check on promotions.
docs/AWS_EC2_BACKEND.md New EC2 backend runbook documentation.
deploy/aws/ec2/systemd/pqmsg-backend-compose.service Systemd unit to manage the compose stack.
deploy/aws/ec2/pqmsg-server.aws.env.example Example runtime config for EC2 compose backend.
deploy/aws/ec2/docker-compose.ec2.yml Compose stack: postgres/redis/blob-permissions-init/pqmsg-server/caddy.
deploy/aws/ec2/Caddyfile TLS termination + reverse proxy for the API.
deploy/aws/ec2/.env.example Example compose env file for domain/email/postgres password/image.
crates/pqmsg-server/tests/api.rs Adds test coverage for unauthenticated recovery-backup download.
crates/pqmsg-server/src/lib.rs Adds recovery-backup route and changes sender cert signing key fallback behavior.
crates/pqmsg-server/src/handlers/backups.rs Implements unauthenticated recovery-backup download handler.
README.md Links new EC2 backend runbook doc.
.gitignore Ignores local smoke-check artifacts.
.github/workflows/promote.yml Adds post-deploy backup/recovery smoke-gate execution and API URL resolution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/pqmsg-server/src/lib.rs
Comment on lines +148 to +156
pub(crate) async fn download_recovery_backup(
State(state): State<AppState>,
Path(user_id): Path<String>,
) -> Result<Json<BackupDownloadResponse>, AppError> {
check_rate_limit(&state, &format!("backup-recovery:{user_id}"))?;
validate_id("user_id", &user_id)?;
ensure_user_exists(state.pool(), &user_id).await?;

Ok(Json(load_backup_response(&state, &user_id).await?))

Copilot AI Apr 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new unauthenticated recovery-backup download endpoint only rate-limits on a key that varies by user_id ("backup-recovery:{user_id}"). That allows high-volume enumeration and bulk download attempts across many user IDs because there’s no IP-based bucket (unlike other unauthenticated flows such as sealed relay). Consider adding ConnectInfo + headers and applying an IP-based limiter (e.g., "backup-recovery-ip:{ip}") in addition to any per-user bucket.

Copilot uses AI. Check for mistakes.
Comment thread mobile/web/package.json
Comment thread mobile/web/src/app.ts
Comment on lines +40 to +50
$chars = (48..57 + 65..90 + 97..122 | ForEach-Object { [char]$_ })
$postgresPassword = -join (1..32 | ForEach-Object {
$chars[(Get-Random -Minimum 0 -Maximum $chars.Count)]
})
$senderKeyBytes = New-Object byte[] 32
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
try {
$rng.GetBytes($senderKeyBytes)
} finally {
$rng.Dispose()
}

Copilot AI Apr 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated Postgres password uses Get-Random over a character set, which is not a cryptographically secure RNG. Since this helper provisions credentials for an internet-reachable EC2 stack, switch to a CSPRNG approach (e.g., RandomNumberGenerator like the sender-cert signing key generation) and encode the bytes into a URL/INI-safe string.

Suggested change
$chars = (48..57 + 65..90 + 97..122 | ForEach-Object { [char]$_ })
$postgresPassword = -join (1..32 | ForEach-Object {
$chars[(Get-Random -Minimum 0 -Maximum $chars.Count)]
})
$senderKeyBytes = New-Object byte[] 32
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
try {
$rng.GetBytes($senderKeyBytes)
} finally {
$rng.Dispose()
}
$postgresPasswordBytes = New-Object byte[] 24
$senderKeyBytes = New-Object byte[] 32
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
try {
$rng.GetBytes($postgresPasswordBytes)
$rng.GetBytes($senderKeyBytes)
} finally {
$rng.Dispose()
}
$postgresPassword = [Convert]::ToBase64String($postgresPasswordBytes).Replace('+', '-').Replace('/', '_')

Copilot uses AI. Check for mistakes.
@ErenAri
ErenAri merged commit 2915793 into main Apr 15, 2026
52 checks passed
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.

2 participants