Skip to content

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., nextSequence may be reused before the previous async row finishes)
  • Unbounded concurrency on external services (OpenAI / S3) when N rows are processed in parallel
  • The isOnProcess = false line 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.json if 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 forEach over async with for...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