Access and security
The platform holds real people's contact details, purchase history and private journal entries. The controls below exist to keep that data where it belongs. They are not optional in dev: the dev database contains real contacts.
How access is gated
| Resource | Gate | Notes |
|---|---|---|
Dev database (development-database-vm) | Tailscale only (or the vnet-emaw-dev virtual network from the dev Django VM) | No public endpoint. DB_HOST in .env is a Tailscale IP. |
| Dev Django VM | Tailscale + SSH key | Where the dev compose stack runs |
Prod PostgreSQL (emawweb-server) | vnet only (EMAWWebVnet), reached by the Django app | No SSH, no public access, no Tailscale yet |
| Prod DockerApp VM | SSH key + IP whitelist | Tailscale is planned here too |
Portainer (https://52.234.34.217:9443/) | IP whitelist | Ask Matt Birne to add your IP |
| Dev website, Admin CRM (dev and prod), these docs | Cloudflare Access: Google accounts ending in @everymanawarrior.com | Staff API endpoints also verify the Access JWT server-side |
| Admin CRM app login | App account in the CRM Staff group or with granted capabilities | Second login after Access |
Backend .env | Password manager | Never in git, chat or email |
SSH
- Key-only. Password authentication is disabled everywhere.
- Ask Troy or Matt to create your user with your public key. Send the public key (
id_ed25519.pub); generate one withssh-keygen -t ed25519 -C "<you>@everymanawarrior.com"if you have none. - You must be on the tailnet to reach the dev VM at all. For the prod VM, your IP must also be allow-listed.
Rules for developers
Secrets
.envfiles are git-ignored in the backend. In the React apps,.env.developmentand.env.productionare committed on purpose because they only hold publicVITE_*values; anything secret does not belong in a Vite app at all.- Stripe keys in dev are test-mode keys. Deposco points at the sandbox. If you see a live key outside production, report it.
- Documentation (including this site) names variables, never values.
- Rotating
SECRET_KEYon the backend also invalidatesGOOGLE_TOKEN_KEY's default derivation, so staff must reconnect Google. Rotate deliberately.
Data
- Never enable campaign sending against dev. Keep
CAMPAIGN_SENDING_ENABLEDunset. If you must test a send, also setCAMPAIGN_SEND_ALLOWED_GROUP_IDS=36so only the Email Testing group can be targeted. The Admin CRM enforces the same guard client-side. - Do not log emails, tokens, verification codes, passwords or request bodies. Use
EMAW.security_log.audit()for security events; log user ids only. The log formatter redacts JWTs, emails and Stripe keys as a backstop, but do not rely on it. - No email addresses in URLs except the
qsearch parameter in the CRM. CSV exports run only on an explicit click and are never cached. - Crop personal data out of screenshots before adding them to these docs.
Database
- The dev DB is shared. Starting the compose stack runs
migrateon it, so a branch with migrations changes the schema for everyone. Coordinate first. - Tests must never run against the shared DB; use the throwaway Postgres container described in Backend testing.
EMAW_SALTmust match the database you point at, or no one can log in (usernames are salted email hashes).
Code
- DRF denies by default. Public views set
AllowAnyexplicitly. - Staff views use
HasCrmAccess("<capability>")/CrmAccessViewMixinandscope_contacts(); never hand-roll staff checks. Any view built on these is behind Cloudflare Access automatically. - Compare verification codes with
VerificationCode.verify()(constant-time, attempt-limited), never==.
Reporting a problem
If you find exposed credentials, an open port, or data where it should not be, tell Troy or Matt directly and do not post details in a shared channel.