Security¶
Findings¶
F-1: Hardcoded Slack bot token (HIGH)¶
teamsnotification.js:1:
const token = 'xoxb-3144030948916-4162895069441-K1RjRxBuP8k9z6rQAxnmyKS2'
const channel = 'C05H25MDY3Z'
A live Slack bot token in source. Rotate immediately; move to env. (Same pattern as Someli-admin-api/routes/auth.js, different token.)
F-2: Hardcoded Unsplash access key (HIGH)¶
routes/routes.js:
const unsplash = createApi({
accessKey: 't0uIotWn5vRgDejBjDATYx6jY54WXihewTTtWzBxick',
fetch: nodeFetch
});
The Unsplash API key is in source. Rotate; move to process.env.UNSPLASH_ACCESS_KEY.
F-3: No middlewares/auth.js (HIGH)¶
Unlike someli-api and Someli-admin-api, this repo has no shared auth middleware. Every handler is responsible for its own auth check. New endpoints added by a future engineer who forgets are publicly callable.
Fix: introduce middlewares/auth.js; apply with router.use(auth); allowlist unauthenticated endpoints.
F-4: CORS wildcard + malformed credentials header (MEDIUM)¶
server.js:
app.use(cors());
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('"Access-Control-Allow-Credentials" : true');
next();
});
Two issues:
1. Wildcard origin (both via cors() and the manual header) — every origin can call
2. The second res.header(...) call has the entire string "Access-Control-Allow-Credentials" : true as the header name. Browsers ignore. Probably intended 'Access-Control-Allow-Credentials' (name), true (value).
Fix: cors({ origin: [<designer-fe-url>], credentials: true }); remove the manual headers.
F-5: 50 MB body limit (MEDIUM)¶
Acceptable for upload endpoints; excessive for the /webauthenticate POST. Tighten per-route or use a smaller default with overrides for upload endpoints.
F-6: conf/credentials.json likely in source (HIGH if true)¶
A file conf/credentials.json is present. Likely a Google service-account JSON. Verify whether committed; if so, rotate immediately and add to .gitignore.
F-7: SQL injection risk in string-interpolated queries (MEDIUM)¶
Many con.query(\SELECT ... WHERE id = ${i.id}`)patterns. Most interpolated values are integers from prior queries — safe. But places where${...}interpolates fromreq.bodyorreq.query` are vulnerable.
Audit: grep -nE "con\\.query\\(\.*\\$\{req\.(body|query|params)" routes/routes.js` to find candidates.
F-8: forEach over async work (MEDIUM, correctness)¶
Jobs frequently iterate with .forEach(async (row) => { ... await ... }). forEach does not await the async callbacks; the iteration continues immediately. Several outcomes:
- Race conditions on shared state (e.g.,
nextSequencemay be reused before the previous async row finishes) - Unbounded concurrency on external services (OpenAI / S3) when N rows are processed in parallel
- The
isOnProcess = falseline may fire before async work completes, allowing the next cron tick to re-enter
Fix: replace with for (const row of rows) { await ... }.
F-9: Polotno license key location (LOW-MEDIUM)¶
Verify whether the Polotno license is hardcoded in routes/routes.js. If so, move to env.
F-10: No request-rate limiting (MEDIUM)¶
server.js doesn't install any rate-limiting middleware. The /webauthenticate endpoint is unauthenticated and not rate-limited, enabling credential stuffing.
Fix: express-rate-limit on /webauthenticate (and any other unauthenticated POST).
F-11: No HTTPS enforcement at app layer (LOW)¶
The app doesn't redirect HTTP→HTTPS or set HSTS. Likely handled by nginx upstream; verify.
F-12: nginx.conf in repo not audited (LOW)¶
There's a nginx.conf shipped in the Dockerfile build. Its contents are not reviewed in this audit. Audit for:
- Correct upstream config (proxy_pass)
- HSTS headers
- Sensible buffer / body-size limits
- Server header suppression
Outcomes-based¶
- OWASP A02 / A05: hardcoded credentials (Slack, Unsplash, probably Polotno, possibly Google service account)
- OWASP A04 Insecure Design: no shared auth middleware
- OWASP A03 Injection: string-interpolated SQL in some places
- OWASP A05 Security Misconfiguration: CORS wildcard + malformed Access-Control header; no rate limiting
- OWASP A07 Identification & Authentication Failures: no rate limiting on
/webauthenticate
Recommendations (phased)¶
Phase 0a (this week)¶
- Rotate Slack token (F-1)
- Rotate Unsplash key (F-2)
- Verify and rotate
conf/credentials.jsonif committed (F-6) - Verify and rotate Polotno license if hardcoded (F-9)
Phase 0 (months 0-3)¶
- Add
middlewares/auth.js(F-3) - Fix CORS configuration (F-4)
- Add
express-rate-limit(F-10) - Audit and fix SQL-injection risks (F-7)
- Replace
forEachover async withfor...of await(F-8)
Phase 1 (months 3-9)¶
- Audit nginx.conf (F-12)
- Tighten body-size limits per route (F-5)
- Standardise on the same auth model as
Someli-admin-api/someli-api