Security Posture Overview¶
Prism authenticates a diverse set of callers — web/mobile staff, customer-facing loyalty apps, POS/3rd-party integrators, and internal WhatsApp services — through six distinct auth layers, each validating a different credential against a different datastore. This page is the security map: who is trusted, how tenants are isolated, what the request pipeline monitors, and — most importantly — the known security findings that must be remediated.
Validation legend:
verified against source ·
representative, verify ·
security finding / risk
1. The six auth layers¶
Every protected route attaches exactly one auth middleware. All layers fail closed (401/403/500 on any error — never fall through unauthenticated).
| # | Middleware | File | Credential | Validates against | Sets on req |
Failure |
|---|---|---|---|---|---|---|
| 1 | authMiddleware |
middlewares/authMiddleware.js |
token header (primary) / password body (fallback, bcrypt) |
MySQL users + auth_tokens (joined w/ roles, addo_account_mapping, addo_business, business_category) |
req.body.created_by |
403 / 500 |
| 2 | adminMiddleware |
middlewares/adminMiddleware.js |
token header / password body |
MySQL (single conn) — hardcoded business_id == 1 |
req.body.created_by |
403 / 500 |
| 3 | appMiddleware |
middlewares/appMiddleware.js |
contactMappingId + token + parent_id (body) |
MySQL addo_contacts_mapping (status=1) |
— | 403 / 500 |
| 4 | crm_offer_middleware |
middlewares/crm_offer_middleware.js |
Authorization header |
MongoDB authorization_token (exact match) |
req.parent_business_id |
401 (uE_101) |
| 5 | jwtMiddleware / jwtWhatsappMiddleware |
middlewares/jwtMiddleware.js, middlewares/jwtWhatsappMiddleware.js |
Authorization: Bearer <jwt> |
JWT HS256 (hardcoded secret) |
req.user |
401 / 403 |
| 6 | injestion_middleware / thirdPartyMiddleware |
middlewares/injestion_middleware.js, middlewares/thirdPartyMiddleware.js |
token header |
Static string compare (hardcoded token) |
— | 401 |
Deep dive on each layer (headers, queries, decision table, flowchart): Authentication.
Two cross-cutting middlewares run before any auth, wired once in index.js:
- loggerService.traceIdMiddleware() — sets a request-scoped trace_id for log correlation.
- requestLogger (middlewares/requestLogger.js) — the plaintext-password monitor (see §3).
2. Multi-tenancy isolation¶
Prism is a shared multi-tenant platform. Isolation is application-enforced, not database-enforced (there is no per-tenant DB/schema and no row-level security).
parent_business_id— the brand/chain (the parent account).crm_offer_middlewarederives it from the matchedauthorization_tokendocument (req.parent_business_id) so a POS integrator can only act within its own brand.child_business_id— the individual outlet/store under a parent (in MySQL:addo_business.id, mapped viaaddo_account_mapping).authMiddlewarescopes a staff user to a specific outlet by requiring bothuserIdandbusiness_idin the join (WHERE u.userId=? AND b.id=?), and by binding tokens touserId(a token cannot be replayed against another account).- Socket.IO sessions carry
parent_business_id+child_business_idonwhatsapp_chat_sessions.
Isolation depends on every controller filtering by tenant. Because middleware sets
req.parent_business_id/req.body.business_idbut does not force controllers to use it, a controller that queries without a tenant filter can leak cross-tenant data. Treat tenant-scoping as a per-controller invariant, not a solved problem.
flowchart LR
A[parent_business_id<br/>brand / chain] --> B[child_business_id<br/>outlet 1]
A --> C[child_business_id<br/>outlet 2]
A --> D[child_business_id<br/>outlet N]
B --> E[users / staff<br/>scoped by userId + business_id]
C --> F[customers / orders<br/>crm_queue, customer 360]
3. The plaintext-password monitor (requestLogger)¶
middlewares/requestLogger.js is a detective control, not a preventive one. It fires on every request that carries a non-empty password in the JSON/form body or the query string, and emits a warn via loggerService.get("security", "plaintext-password-monitor") with error code INCOMING_PASSWORD_PRESENT.
Key properties (verified ):
- Never logs the password value. Only its presence. It even strips the query string from the
referer header before logging (referer.split("?")[0]) so a ?password= leaked via referer is not written.
- Logs metadata only: endpoint (method path), origin/referer (path-stripped), has_token, capped user_agent (≤512 chars), and unverified/spoofable user_id + tenant.business_id (client-supplied, pre-auth — do not treat as identity).
- Wrapped in try/catch — logging never breaks the request.
- Naming note: despite the "plaintext" name, it is presence-based — it fires even for correctly-bcrypted values. Read the signal as "a password was transmitted".
This monitor tells you passwords are flowing through request bodies/query strings (see the query-string finding in §4), but it does not block them. It is an observability signal to drive remediation, not a fix.
4. Security Findings & Risks¶
Each finding references file:line only. Secret values are never reproduced here — rotate the secret and read it from the source file if you need the current value. Every item is marked .
Hardcoded secrets in source (CRITICAL)¶
JWT signing secret (CRM) —
middlewares/jwtMiddleware.js:5. HS256 secret committed to the repo; anyone with source access can forge validreq.usertokens.JWT signing secret (WhatsApp) —
middlewares/jwtWhatsappMiddleware.js:5. Separate hardcoded HS256 secret; same forgery risk for WhatsApp login routes.Ingestion static token —
middlewares/injestion_middleware.js:8. A single shared string guards/injestion/ls_centerand/sync/crm/token; leak = full order-injection + token-sync access.Third-party API token —
middlewares/thirdPartyMiddleware.js:6. Single shared string; no per-partner keys, no revocation.New Relic license key —
newrelic.js(license field). Committed monitoring credential.Redis host hardcoded —
utilities/redis.js(ElastiCache host +rejectUnauthorized:false). Not env-driven; endpoint exposed and TLS verification disabled.Prism-services Mongo URI (with password) —
serverless-orders.yml:51,serverless-whatsapp.yml:67,serverless-whatsapp.yml:89. Plaintext MongoDB Atlas connection string including password committed to version control (prism-servicesrepo). Highest-severity secret in the fleet — grants direct DB access to the shareduengagedatabase.Prism-services Kafka brokers hardcoded —
serverless-orders.yml/serverless-whatsapp.yml(KAFKA_BROKER_URL). Broker endpoints baked into YML rather than env/SSM.
Committed credentials file¶
.env.devchecked into the repo — contains plaintext dev credentials (SQL password, commented Redis password). Should be.gitignored..env.prod.exampleas a template is acceptable;.env.prodis correctly not in the repo.
Transport & network exposure¶
Open CORS —
index.js(andsocket.js) use wildcard origin*. Any origin can call the API from a browser; combined with credentials-in-body this widens CSRF/abuse surface.No HTTPS enforcement — the app creates a plain
http.createServer(app)(index.js). TLS termination is assumed upstream but not enforced; there is no HSTS / redirect.No rate limiting — no throttling on any route. OTP (
/api/generateOTP), login, and ingestion endpoints accept unlimited requests (brute-force / DoS / OTP-bombing exposure).
Authorization weaknesses¶
Admin check hardcoded —
middlewares/adminMiddleware.js:9gates admin purely onbusiness_id == 1. Not role-based; any actor who can authenticate as business 1 is admin, and there is no defence-in-depth beyond the numeric check.Password acceptable in query string —
authMiddleware/requestLoggerboth readpasswordfrom the query string. Passwords in URLs land in access logs, proxies, and browser history.requestLoggerdetects this but does not block it.Static tokens in plaintext headers —
injestion_middleware,thirdPartyMiddlewarecompare a shared string with no rotation, no per-caller identity, and no expiry.
Remediation guidance (applies to all secret findings)¶
- Move secrets to env vars / a secrets manager (AWS Secrets Manager or SSM Parameter Store for the serverless side; env-injected config for the monolith). Never read secrets from committed files.
- Rotate every exposed secret listed above — assume all are compromised because they are in git history. Purge from history where feasible.
- Issue per-caller credentials (per-partner API keys with revocation) instead of single shared static tokens.
- Enforce TLS at the edge (HTTPS + HSTS), restrict CORS to known origins, and enable
rejectUnauthorizedfor Redis/Mongo TLS. - Add rate limiting (per-IP + per-tenant) on auth, OTP, and ingestion routes.
- Replace the
business_id==1admin gate with role-based authorization.
Remediation priority¶
| Priority | Finding | Location | Impact | Suggested action |
|---|---|---|---|---|
| P0 | Mongo URI + password in YML | serverless-orders.yml:51, serverless-whatsapp.yml:67,89 |
Direct DB compromise of shared uengage DB |
Rotate password now; move to Secrets Manager; purge history |
| P0 | JWT secrets hardcoded | jwtMiddleware.js:5, jwtWhatsappMiddleware.js:5 |
Token forgery / auth bypass | Rotate secrets; env vars; invalidate live tokens |
| P0 | .env.dev committed |
repo root | Credential exposure | Remove + .gitignore; rotate dev creds |
| P1 | Static ingestion / 3rd-party tokens | injestion_middleware.js:8, thirdPartyMiddleware.js:6 |
Order injection / integration abuse | Per-partner keys + rotation |
| P1 | New Relic license | newrelic.js |
Monitoring account exposure | Rotate; env var |
| P1 | No rate limiting | app-wide | Brute-force / OTP-bombing / DoS | Add per-IP + per-tenant limits |
| P1 | Admin gate business_id==1 |
adminMiddleware.js:9 |
Weak authZ | Role-based authorization |
| P2 | Open CORS * |
index.js, socket.js |
CSRF / browser abuse | Allowlist origins |
| P2 | No HTTPS enforcement | index.js |
Cleartext transport | Enforce TLS + HSTS at edge |
| P2 | Redis host hardcoded + TLS off | utilities/redis.js |
Endpoint exposure | Env-drive; enable cert verification |
| P2 | Kafka brokers hardcoded | serverless YMLs | Config drift / exposure | Env / SSM |
| P2 | Password in query string | authMiddleware, requestLogger |
Creds in logs/proxies | Reject query-string passwords |
Related¶
- Authentication — per-middleware deep dive + auth-selection flowchart
- Request Lifecycle — where auth sits in the pipeline
- API Overview · Endpoint Reference
- Deployment — where secrets should be injected