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

RolePurposeauthSource
adminReplication, admin ops onlyadmin — never in app containers
w3_appW3 (v32-prod + s3-v32-prod) read/writew3_db
w4_appW4 (p24-back-ts + s3-w4) read/writew4_db

Rules:

  • V32_MONGODB_URL, V32_PMONGODB_URL, V32_DB_URI must use w3_app
  • V4_MONGODB_URL, V4_PMONGODB_URL, V4_DB_URI must use w4_app
  • Admin credentials (MONGODB_RS0_ADMIN_PASSWORD) stay in secrets/mongodb-bms.env.sops only
  • On-server admin access: mongosh via 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 -c or wc -l for 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 .gitignore if 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.yml mounts 5 files from it into the running s3-v32-stage/W3-stage backend). Wholesale rm -rf breaks staging. The credential-bearing files are NOT mounted by any container (only persistent-patches/*.js + app-backend/.../excel_import.js are), so the leak vector can be scrubbed surgically — remove docker-deploy-prod.sh + the backend-environment.env* / s3-environment.env* / .env / stage.env files (none mounted; backend-environment.env is 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 batchMONGODB_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):

  1. 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.
  2. git rm --cached .env.prod and add .env.prod (or .env.*) to .gitignore on both master and development — 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.prod first (a dynamic .env.${NODE_ENV} loader would not string-match a repo grep) so removal does not break the runtime deploy.
  3. Scrub .env.prod from 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:

ContainerCompose pathMongoDB envVerdict
cron-v32-prod/home/p24-server-scripts/v3/v32noneNot a Mongo consumer — rotation-safe
cron-v32-prod-reso/home/p24-server-scripts/v3/v3resononeNot a Mongo consumer — rotation-safe
cron-v32-prod-socket/home/p24-server-scripts/v3/v3socketnoneNot 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-renamedorphan docker run, RestartPolicy=noDB_URI, w3_app, authSource=w3_dbw3_app consumer — see rotation playbook Consumer map
v32-prod-reso / v32-prod-socketGitLab-CI compose (svc reso/socket) — not the private-registry pathMONGODB_URL/PMONGODB_URL/DB_URI, w3_appw3_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

ItemRule
Registry563740926945.dkr.ecr.eu-central-1.amazonaws.com (eu-central-1)
Auth token lifetime12 hours — must refresh before pull
Commandaws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin <registry>
Pull before recreateOnly when new image is available — use local cache otherwise
FallbackLocal 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

EventImmediate action
PM2 log shows MongoDB URI with passwordRotate app user password, wipe log, create GH issue
docker-deploy-prod.sh accidentally runAudit which creds were written, rotate all, recreate containers from SOPS
Admin password in app containerRotate admin password, fix env file to use app user, recreate container
bms-1 SSH key compromisedRotate 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

#SummaryPriority
#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
#2763Rotate ~23 exposed W4 prod keys (docker-deploy-prod.sh; script fix merged MR !787) — covers .env.prod tooHigh
#3063docker-deploy-prod.sh has hardcoded credentials — replace with SOPS-aware scriptHigh
#3066PM2 logs full MongoDB URI with password — fix in v32-prod sourceHigh
restart: unless-stopped not in docker-compose.yml — add permanentlyMedium
#5153Legacy 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 reviewMedium