# SCAN Briefing: humand-main-api **Date**: 2026-02-13 **Repo**: git@github.com:HumandDev/humand-main-api.git **Commit**: bb975d08cc **Type**: Internal — Node.js/TypeScript monorepo (pnpm, Nx) **Stack**: Express, Sequelize (PostgreSQL), Redis, AWS S3/CloudFront, gRPC, SAML, JWT --- ## Summary humand-main-api is the core backend for Humand — a multi-tenant SaaS platform for enterprise employee engagement (posts, courses, marketplace, time tracking, documents, forms, etc.). The monolith package contains ~4,700 TypeScript files. It has strong auth middleware (JWT access/refresh tokens with session tracking, scope validation, bot-app impersonation flow), but several critical and high-severity issues were found. --- ## Findings ### F1: Master Password Bypasses All Authentication (CRITICAL) **Confidence**: HIGH **File**: `src/api/modules/auth/business/services/auth.ts:391` **CWE**: CWE-798 (Use of Hard-Coded Credentials) / CWE-287 (Improper Authentication) The `verifyPasswordOfUser` method accepts a `MASTER_PASSWORD` environment variable that bypasses bcrypt password verification for **any user**: ```typescript const isMasterPasswordUsed = passwordToCheck === this.masterPassword; if (!isMasterPasswordUsed) { // only then check bcrypt } ``` **Impact**: Anyone who knows the master password can authenticate as **any user in the system**, including admins across all tenants. This is a single point of failure for the entire platform's authentication. The same master password is used to create a service user ("humand") on every instance (`usersService.ts:3883-3889`), meaning it persists in the database hashed, but the plaintext comparison happens first. **Attack vector**: Login endpoint → supply master password → gain access to any user account on any tenant. --- ### F2: SQL Injection via Sequelize.literal in profileFieldUUID (HIGH) **Confidence**: HIGH **File**: `src/api/modules/users/business/services/usersService.ts:2635` **CWE**: CWE-89 (SQL Injection) The `removeProfileFieldInUsers` function interpolates a path parameter directly into a `Sequelize.literal()` call: ```typescript await UserDAO.update( { profileData: Sequelize.literal(`"profileData"-'${profileFieldUUID}'`) }, { where: { instanceId } } ); ``` The `profileFieldUUID` comes from the URL path `/:id/profile-fields/:profileFieldUUID` and is validated only by `validateProfileFieldUUIDPathParam` which checks non-empty/non-null — **no UUID format validation**: ```typescript export const validateProfileFieldUUIDPathParam = (req, res, next) => validatePathParam(req, res, next, 'profileFieldUUID'); // validatePathParam only checks: req.params[paramName] != null && req.params[paramName].length > 0 ``` **Route**: `DELETE /backoffice/instances/:id/profile-fields/:profileFieldUUID` **Requires**: Authenticated admin with `manageInstance` permission. **Impact**: An admin user (or anyone who compromises an admin account via F1) can execute arbitrary SQL. In a multi-tenant SaaS, this means cross-tenant data access, privilege escalation, or full database compromise. --- ### F3: Hardcoded Secrets in .env.example (HIGH) **Confidence**: HIGH **File**: `.env.example` (lines 52-54, 64, 154-155) **CWE**: CWE-798 (Use of Hard-Coded Credentials) `.env.example` contains real cryptographic keys and credentials, not placeholders: 1. **Firebase RSA Private Key** (line 64): Full `-----BEGIN RSA PRIVATE KEY-----` block (1024-bit key) 2. **IDP SAML Private Key** (line 154): Full `-----BEGIN PRIVATE KEY-----` block (2048-bit key) 3. **IDP Signing Certificate** (line 155): Full `-----BEGIN CERTIFICATE-----` block 4. **Twilio Credentials** (lines 52-54): ``` TWILIO_ACCOUNT_SID=AC666423755d7ca429a5d26c021c0b8bc3 TWILIO_AUTH_TOKEN=666425df8249bca19b0581cbe3ea8202 TWILIO_ACCOUNT_SUBACCOUNT_SID=VA66642685ebdf6cd6df28ecabe3edebf4 ``` 5. **Cursor Pagination Encryption Key** (line 60): `405b0c70dcc8f819` 6. **API Key Salt** (line 71): `$2a$16$fZ7WdtFjAt6Dv/xIAqhK5u` **Impact**: Even if these are "dev" keys, the Firebase private key and IDP keys could be used for token forgery or SAML assertion forging if the dev environment is accessible. The Twilio credentials enable sending SMS/calls billed to the account. Committed to git, these are in the repo history permanently. --- ### F4: Environment Variable Exfiltration via External Endpoints (MEDIUM) **Confidence**: HIGH **File**: `src/api/services/externalEndpoints.ts:57-66` **CWE**: CWE-200 (Exposure of Sensitive Information) The `replaceHeadersWithEnv` function resolves `${VARNAME}` patterns in external endpoint headers against `process.env`: ```typescript private replaceHeadersWithEnv = (headers: Record): Record => { Object.keys(headers).forEach((header) => { headers[header] = currentValue.replace(/\$\{\w+}/gi, (paramWithBraces) => { const param = paramWithBraces.replace('${', '').replace('}', ''); return process.env[param] ?? paramWithBraces; }); }); return headers; }; ``` External endpoints are DB-stored configurations. If an actor with DB write access (or via F2 SQL injection) can insert an external endpoint with headers containing `${JWT_KEY}`, `${AWS_SECRET}`, `${MASTER_PASSWORD}`, etc., the resolved values get sent as HTTP headers to an attacker-controlled URL. **Impact**: Full exfiltration of all environment variables (JWT signing keys, AWS credentials, database passwords, master password) through a single crafted external endpoint record. --- ### F5: Multiple Sequelize.literal Injections with String Interpolation (MEDIUM) **Confidence**: MEDIUM **Files**: Multiple locations **CWE**: CWE-89 (SQL Injection) Several other `Sequelize.literal()` calls interpolate variables that may be indirectly user-controlled: 1. `cursorPagination.ts:67` — `dir` parameter in ORDER BY: `Sequelize.literal(\`"hiringDate" ${dir} NULLS LAST\`)` - `dir` is typed as `'ASC' | 'DESC'` and goes through validation, but `extractSortInfo` casts without strict checking at line 197 2. `forms.ts:1518` — `valueToIncrement` in position update: `Sequelize.literal(\`position + (${valueToIncrement})\`)` 3. `slas.ts:146,159-160` — `percentage` interpolated in HAVING and WHERE clauses 4. `taskDAO.ts:45` — `courseId` interpolated in subquery These require further investigation to confirm exploitability, as some may be sufficiently validated upstream. --- ### F6: SSRF via External Endpoints Service (MEDIUM) **Confidence**: MEDIUM **File**: `src/api/services/externalEndpoints.ts:68-78` **CWE**: CWE-918 (Server-Side Request Forgery) The external endpoints service makes HTTP requests to arbitrary URLs stored in the database. Authenticated users with `invokeExternalEndpoint` permission can trigger requests to: - Internal network services (AWS metadata at `169.254.169.254`, internal gRPC services) - Localhost services The URL comes from DB configuration (not directly user-controlled), but combined with F2 or admin access, an attacker could reconfigure endpoints to target internal infrastructure. --- ### F7: CSS Injection via style Attribute in Sanitizer (LOW) **Confidence**: MEDIUM **File**: `src/api/middlewares/classTransformers/sanitize.ts:10` **CWE**: CWE-79 (Cross-Site Scripting) The HTML sanitizer allows the `style` attribute on all elements: ```typescript allowedAttributes: { '*': ['style'], ... } ``` While `sanitize-html` strips `