Security Policy: Pinbox24 W3/W4 Backend & Microservices
Applies to: bms-1 (94.23.26.113) — W3 (v32-prod, s3-v32-prod) and W4 (p24-back-ts, s3-w4)
Last updated: 2026-07-06
Owner: p24-infra (sys-admin + sys-security roles)
1. MongoDB Credentials
Principle: no service connects as MongoDB admin
| Role | Purpose | authSource |
|---|---|---|
admin | Replication, admin ops only | admin — never in app containers |
w3_app | W3 (v32-prod + s3-v32-prod) read/write | w3_db |
w4_app | W4 (p24-back-ts + s3-w4) read/write | w4_db |
Rules:
V32_MONGODB_URL,V32_PMONGODB_URL,V32_DB_URImust usew3_appV4_MONGODB_URL,V4_PMONGODB_URL,V4_DB_URImust usew4_app- Admin credentials (
MONGODB_RS0_ADMIN_PASSWORD) stay insecrets/mongodb-bms.env.sopsonly - On-server admin access:
mongoshvia SSH only, never in any container
Rotation schedule
- App users (w3_app, w4_app): every 90 days, or immediately on suspected exposure
- Admin user: every 90 days, or immediately on any security incident
- Reference:
docs/playbooks/w3-mongodb-credential-rotation.md
2. PM2 Log Security
Problem (issue #3066)
Node.js/mongoose logs the full MongoDB connection URI at startup, including the password:
Mongoose connected: mongodb://w3_app:PASSWORD@host/db
Required fix (pending in v32-prod source)
Mask password in connection log before it reaches PM2:
const safeUri = mongoUri.replace(/:([^@]+)@/, ':***@');
console.log('Mongoose connected:', safeUri);Until fixed: log files at /var/log/v32-prod/pm2/ and /var/log/s3-v32-prod/pm2/ must be treated as sensitive.
Log access policy
- Access via SSH only (root@bms-1)
- Never cat/tail log files in chat output — use
grep -corwc -lfor counts - Rotate app-user password immediately if PM2 log is read in full and URI was present
- PM2 log rotation: configured in PM2 ecosystem config (verify max-size limit)
3. Environment File Security
Build-dir env files (backend-environment.env, s3-environment.env)
These files contain plaintext credentials on disk in the live (GitLab-runner-owned) compose dir:
/home/gitlab-runner/builds/eZQeLfuJe/0/pinbox24/p24-v-3.2/backend-environment.env
/home/gitlab-runner/builds/eZQeLfuJe/0/pinbox24/p24-v-3.2/s3-environment.env
Confirm the live dir before touching these — the build-hash segment can drift (#4985):
docker inspect v32-prod --format '{{index .Config.Labels "com.docker.compose.project.working_dir"}}'.
Rules:
- Never commit to git — add to
.gitignoreif they aren’t already - Never display contents in chat
- Source of truth:
secrets/pinbox24-w3.env.sops(SOPS+age) - Update procedure: decrypt SOPS → Python stdin pipe via SSH → force-recreate container
- Reference:
docs/playbooks/pinbox24-w3-operations.md §Correct procedure: update credentials
docker-deploy-prod.sh — DEPRECATED
docker-deploy-prod.sh contains hardcoded credentials (issue #3063). It was found in the now-stale
root-owned /root/builds/pn3C9eHo/0/pinbox24/p24-v-3.2/ copy; the live compose dir is
/home/gitlab-runner/builds/eZQeLfuJe/0/pinbox24/p24-v-3.2/ (#4985). Check both when auditing.
Status: must be replaced. Until replaced:
- Never run this script
- Audit which credentials it contains and rotate any that are still active
- The
/root/builds/pn3C9eHo/...directory still holds this script on disk (a leak vector even though it no longer serves prod traffic). On-server audit 2026-08-01 (#4985): the directory canNOT be deleted wholesale — it is an ACTIVE W3-staging mount source (docker-compose-w3-stage.ymlmounts 5 files from it into the runnings3-v32-stage/W3-stage backend). Wholesalerm -rfbreaks staging. The credential-bearing files are NOT mounted by any container (onlypersistent-patches/*.js+app-backend/.../excel_import.jsare), so the leak vector can be scrubbed surgically — removedocker-deploy-prod.sh+ thebackend-environment.env*/s3-environment.env*/.env/stage.envfiles (none mounted;backend-environment.envis mode 644 = world-readable) while leaving the mounted patch files intact. NB: an unidentified writer created*.env.bak.20260801_*backups here on 2026-08-01 — identify/repoint it before scrubbing so a rotation path doesn’t rewrite them. Full removal only after W3-staging is migrated off the path (see the #4985 remediation plan). - Replacement: a deploy script that reads from
/opt/p24-infra/bms-1/pinbox24-w3.env(SOPS-deployed)
.env.prod committed to git in pinbox24/p24-back-ts (W4) — issue #4992
The W4 source repo (gitlab.com/pinbox24/p24-back-ts) has a .env.prod file committed at the repo
root containing hardcoded production credentials. Confirmed 2026-08-01 via read-only GitLab API
(key names only, never values) on both master (commit 235e2b6a) and development (ce9630bf):
31 populated keys, all real values — not placeholders and not ${VAR} references. The file has
been tracked since 2020-12-10 (10 commits touch it on master), so the values are reachable
throughout the repo history, not just at HEAD. .gitignore on master ignores .env but not
.env.prod, which is why the file was never excluded.
Secret-bearing keys present (names only): MONGODB_URI_LOCAL, NEW_MONGODB_URI, JWT_TOKEN_SECRET,
monitoringToken, tokenGenSecret, tokenGenIV, INVOICE_AUTH_TOKEN, PAYU_CLIENT_SECRET,
PAYU_MD5_SECOND_KEY, przelewy24Auth, REDIS_PASSWORD (plus non-secret config: PayU/przelewy24
IDs and URLs, S3/WK-HTML service URLs, PORT, FRONTEND_URL, VIRTUAL_HOST).
These keys are a subset of the #2763 rotation batch — MONGODB_URI_LOCAL is the same artnet
eat1 MongoDB user already covered by #2763’s NEW_MONGODB_URI. So the #2763 rotation, once
executed, remediates .env.prod too; no additional keys need to be added to that batch.
Remediation (human-gated — external prod repo, git filter-repo/force-push must be coordinated
before executing, per docs/w3-w4-stack-operations.md §2):
- Rotate the exposed credentials — folded into #2763 (script fix already merged via MR !787; ~23-key rotation still pending). Rotation is the primary remediation: history scrub without rotation leaves already-exposed values valid.
git rm --cached .env.prodand add.env.prod(or.env.*) to.gitignoreon bothmasteranddevelopment— mirrors the #2763 docker-deploy-prod.sh fix (inject values via GitLab CI/CD variables, never a committed file). Verify how the app loads.env.prodfirst (a dynamic.env.${NODE_ENV}loader would not string-match a repo grep) so removal does not break the runtime deploy.- Scrub
.env.prodfrom git history (git filter-repo/BFG) — destructive on a Pinbox24-team-owned external repo; treat force-push as human action and coordinate the approach before executing.
4. Container Runtime Security
Restart policy
Both W3 containers must have restart: unless-stopped to survive server reboots.
Current state (as of 2026-07-06):
- Set via
docker update --restart unless-stopped— survives until next force-recreate - Permanent fix: add to
docker-compose.yml(open task)
Verify: docker inspect v32-prod --format '{{.HostConfig.RestartPolicy.Name}}'
Network isolation
- W3 containers are on
test-net(external bridge, nginx-proxy controlled) - s3-v32-prod port 3000 is NOT exposed to internet — accessible only via v32-prod proxy on test-net
- Never add
ports:to s3-v32-prod in docker-compose
Legacy containers on bms-1 — audited 2026-08-02 (#5153)
Read-only docker inspect audit of the parallel /home/p24-server-scripts/v3/** + orphan containers
(passwords never printed — user/db/authSource identifiers only). MongoDB-credential findings:
| Container | Compose path | MongoDB env | Verdict |
|---|---|---|---|
cron-v32-prod | /home/p24-server-scripts/v3/v32 | none | Not a Mongo consumer — rotation-safe |
cron-v32-prod-reso | /home/p24-server-scripts/v3/v3reso | none | Not a Mongo consumer — rotation-safe |
cron-v32-prod-socket | /home/p24-server-scripts/v3/v3socket | none | Not a Mongo consumer — rotation-safe |
s3-v32-prod-reso | /home/p24-server-scripts/v3/v3reso (svc s3) | DB_URI, authSource=admin, user≠w3_app | ⚠️ app-tier container on admin auth — see below |
s3-v32-prod-socket | /home/p24-server-scripts/v3/v3socket (svc s3) | DB_URI, authSource=admin, user≠w3_app | ⚠️ same |
s3-v32-prod-renamed | orphan docker run, RestartPolicy=no | DB_URI, w3_app, authSource=w3_db | w3_app consumer — see rotation playbook Consumer map |
v32-prod-reso / v32-prod-socket | GitLab-CI compose (svc reso/socket) — not the private-registry path | MONGODB_URL/PMONGODB_URL/DB_URI, w3_app | w3_app consumers now covered by rotation script (#5153) |
No hardcoded MongoDB admin password was found in any container’s runtime env. However
s3-v32-prod-reso / s3-v32-prod-socket connect with authSource=admin (a non-w3_app user),
which violates §1 (“no service connects as MongoDB admin; w3 services must use authSource=w3_db”). This
is unrelated to the w3_app rotation (they do not use w3_app) but is a standing policy violation.
Action required (sys-security): file a security-labelled issue to identify the exact user these
two containers authenticate as and migrate them to w3_app/authSource=w3_db (or decommission the
/home/p24-server-scripts/v3/** path — see #5153 Ask #3 deploy-path-drift review).
5. ECR Image Pull Policy
| Item | Rule |
|---|---|
| Registry | 563740926945.dkr.ecr.eu-central-1.amazonaws.com (eu-central-1) |
| Auth token lifetime | 12 hours — must refresh before pull |
| Command | aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin <registry> |
| Pull before recreate | Only when new image is available — use local cache otherwise |
| Fallback | Local cached image if ECR token expired and no aws CLI |
6. SOPS Secret Management
Key hierarchy
secrets/pinbox24-w3.env.sops ← W3 app credentials (w3_app, Wasabi)
secrets/pinbox24-w4.env.sops ← W4 app credentials (w4_app, Wasabi)
secrets/mongodb-bms.env.sops ← MongoDB admin password only
Never cross-contaminate: admin credentials must not appear in pinbox24-w3.env.sops or pinbox24-w4.env.sops.
Distribution flow
SOPS (developer edits) → PR → merge → secrets-sync.yml CI
→ decrypt → SCP to /opt/p24-infra/bms-1/pinbox24-w3.env
→ (manual step) SSH + Python update script → backend-environment.env
→ docker-compose up -d --force-recreate
Note: secrets-sync.yml does NOT update build-dir env files or restart containers. The manual step is required after every credential rotation.
7. Incident Response
| Event | Immediate action |
|---|---|
| PM2 log shows MongoDB URI with password | Rotate app user password, wipe log, create GH issue |
docker-deploy-prod.sh accidentally run | Audit which creds were written, rotate all, recreate containers from SOPS |
| Admin password in app container | Rotate admin password, fix env file to use app user, recreate container |
| bms-1 SSH key compromised | Rotate via docs/playbooks/w3-mongodb-credential-rotation.md |
For all incidents: create GH issue in radieu/p24-infra with label security BEFORE using any workaround.
8. Open Issues
| # | Summary | Priority |
|---|---|---|
| #4992 | .env.prod committed in p24-back-ts (W4) — 31 real prod creds on master+development since 2020; untrack + .gitignore + history scrub (rotation folded into #2763) | High |
| #2763 | Rotate ~23 exposed W4 prod keys (docker-deploy-prod.sh; script fix merged MR !787) — covers .env.prod too | High |
| #3063 | docker-deploy-prod.sh has hardcoded credentials — replace with SOPS-aware script | High |
| #3066 | PM2 logs full MongoDB URI with password — fix in v32-prod source | High |
| — | restart: unless-stopped not in docker-compose.yml — add permanently | Medium |
| #5153 | Legacy containers audited 2026-08-02 (§4) — no admin-password leak; residual: s3-v32-prod-reso/-socket use authSource=admin (sys-security follow-up) + /home/p24-server-scripts deploy-path-drift review | Medium |