Someli Platform — Security Findings (Self-Contained)¶
Compiled 2026-05-22. This document stands alone — it does not link to any other document. Everything you need to understand and fix each issue is here: a plain-language glossary of the vulnerability types, the findings grouped by repository, and a ticket table (Appendix A) you can paste into a ticketing system.
Written for a developer who is new to security. Each finding tells you what it is, where it is, why it matters, and how to fix it. Recurring vulnerability types are explained once in the Glossary (§3) and referenced by a code like [G1], so the per-repo entries stay short.
Secret values are truncated (e.g.
xoxb-3144…) so this document can be shared safely. The real values are in the source code at the citedfile:line. Any secret named here must be treated as compromised and rotated — it has been committed to git.
1. How to use this document¶
- Fixing issues top-down? Start with the 🔴 Critical findings, then 🟠 High. The repo sections (§4) are ordered by severity within each repo.
- Creating tickets? Go to Appendix A — one row per finding, with a stable ticket ID (e.g.
SEC-API-01), severity, category, location, and a one-line fix. The matching detail is in §4 under the same ID. - New to a vulnerability type? Read its glossary entry in §3 (the [G#] codes).
- Severities follow §2. A "Phase" suggests urgency: 0a = this week, 0 = this quarter, 1 = 3–9 months, 2 = 9+ months.
Contents¶
- 2. Severity legend
- 3. Vulnerability glossary
- 4. Findings by repository
- 4.1 someli-api — main backend
- 4.2 someli-platform — customer web app
- 4.3 designer-api — designer backend
- 4.4 Someli-admin-api — admin backend
- 4.5 someli-dashboard-be — dashboard service
- 4.6 admin_console_R — admin web app
- 4.7 Someli-Designer — designer web app
- Appendix A — All findings (ticket table)
2. Severity legend¶
| Badge | Meaning |
|---|---|
| 🔴 Critical | Exploitable now, leads directly to account/data compromise. Fix immediately (Phase 0a). |
| 🟠 High | Serious; leads to compromise with a small extra step, or exposes a live secret. Fix this quarter (Phase 0). |
| 🟡 Medium | Real weakness, harder to exploit or limited blast radius. Fix in 0–9 months. |
| ⚪ Low | Hardening / hygiene; low likelihood or low impact. Fix when convenient. |
3. Vulnerability glossary¶
Each entry: what it is, why it matters, the general fix. The per-repo findings point here by code.
G1 — SQL injection¶
What: Building a database query by gluing user input straight into the SQL text, e.g. `... WHERE email='${req.body.email}'`. A value like ' OR '1'='1 changes what the query does.
Why it matters: An attacker can read or change any data (dump password hashes, log in as anyone, delete rows). On a login endpoint this needs no account at all.
General fix: Never concatenate input into SQL. Use parameterised queries — placeholders with a values array: con.query('... WHERE email = ?', [email]). The database driver then treats the input as data, never as SQL. Table/column names can't be parameterised — validate those against a fixed allow-list.
G2 — Hardcoded / committed secrets¶
What: API keys, tokens, passwords, or .env/credential files written into source code or baked into a Docker image.
Why it matters: Everyone with repo (or image) access — current/former staff, contractors, anyone a clone leaked to — has the secret. Git keeps it in history forever, even after deletion.
General fix: (1) Rotate the secret at its provider (assume it's already stolen). (2) Move it to an environment variable read via conf.js/process.env. (3) Remove the file from the image build and .gitignore it. (4) Scrub git history (git filter-repo/BFG) and force-push. (5) Add a secret scanner (gitleaks) to CI.
G3 — Insecure credential handling¶
What: Storing passwords in plaintext, comparing them with ==, or hashing them in the browser instead of the server.
Why it matters: Plaintext passwords leak wholesale if the DB is read. A password == storedValue check can be bypassed if you can submit the stored value. Client-side hashing is security theatre — the hash becomes the de-facto password.
General fix: Hash with bcrypt on the server at registration; verify with bcrypt.compare(). Never store or compare plaintext. Send the plaintext password over HTTPS (TLS is the transport protection) and hash it server-side.
G4 — Broken access control¶
What: Deciding what a user is allowed to do in the browser (hiding buttons by role) or only in a SQL WHERE clause, without the server independently checking the caller's identity/role on every request. Also covers login logic that grants access it shouldn't.
Why it matters: A user can edit a cookie, call the API directly, or type a URL to do things they shouldn't. The UI is not a security boundary.
General fix: Enforce authorisation on the server, per request, from the authenticated identity. Treat any role/account value sent by the client as untrusted. UI gating is UX only.
G5 — Insecure session-token storage¶
What: Keeping the login token where browser JavaScript can read it — localStorage or a cookie without the HttpOnly flag.
Why it matters: If any script on the page is malicious or compromised (see G10), it can read the token and impersonate the user. JS-readable token + XSS = account takeover.
General fix: Have the backend set the token as an HttpOnly; Secure; SameSite cookie so JavaScript can never read it; send requests with credentials included. Stop returning the token in the JSON body for the SPA to store.
G6 — Weak token lifecycle¶
What: Tokens that never expire, can't be reliably revoked (revocation list kept only in process memory), or are guessable/forgeable (predictable format).
Why it matters: A stolen token works forever; a "logout" or "revoke" doesn't actually stop it; a predictable token can be forged without stealing anything.
General fix: Put an expiry (exp) in tokens; store revocations in a shared, persistent place (DB/Redis), not memory; make tokens unguessable (signed/encrypted random values), never id_timestamp_id or Bearer <known value>.
G7 — Missing HTTP security headers (incl. clickjacking)¶
What: The server doesn't send protective response headers: Strict-Transport-Security (force HTTPS), X-Content-Type-Options: nosniff, X-Frame-Options/CSP frame-ancestors (stop your page being embedded in a hostile <iframe> — "clickjacking"), Referrer-Policy.
Why it matters: Browsers can't enforce protections they're not told about. No frame-ancestors/X-Frame-Options means an attacker can frame your app and trick users into clicks.
General fix: Add helmet() in Express apps; add the headers in nginx for SPAs: add_header X-Frame-Options "DENY", Content-Security-Policy "frame-ancestors 'none'", Strict-Transport-Security, X-Content-Type-Options nosniff, Referrer-Policy.
G8 — No Content Security Policy (CSP)¶
What: No CSP header telling the browser which script/style/connect sources are allowed.
Why it matters: If an attacker injects a script (XSS, G10), nothing limits what it can do. CSP is the safety net that stops injected/remote scripts from running.
General fix: Add a Content-Security-Policy header (nginx or meta tag) listing allowed sources; roll out in -Report-Only mode first, then enforce. Aim to remove 'unsafe-inline' over time.
G9 — No Subresource Integrity (SRI) / risky third-party scripts¶
What: Loading JavaScript from a CDN or third party with no integrity="sha384-…" hash, so the browser runs whatever the CDN serves.
Why it matters: If the CDN (or that third party) is compromised, attacker JavaScript runs on your site with full access — including reading tokens (G5).
General fix: Add integrity + crossorigin attributes to every external <script>/<link> (generate with openssl dgst -sha384 -binary | openssl base64 -A); better, self-host the file from npm so there's no third party to trust; remove scripts you don't need.
G10 — Cross-site scripting (XSS)¶
What: Rendering untrusted text as HTML — Vue's v-html, React's dangerouslySetInnerHTML, or element.innerHTML — so a <script> or <img onerror=…> in the data executes.
Why it matters: Injected scripts run as the user: steal tokens (G5), perform actions, deface the page. AI-generated or uploaded content is a common source.
General fix: Prefer plain text interpolation ({{ value }}). When HTML is genuinely needed, sanitise with DOMPurify first. Add a lint rule (vue/no-v-html, react/no-danger) to catch new uses.
G11 — CORS misconfiguration¶
What: Access-Control-Allow-Origin: * (e.g. app.use(cors()) with no options) — any website can call your API from a browser.
Why it matters: Widens the attack surface; combined with cookie auth it enables cross-site request abuse. A wildcard origin is also incompatible with sending credentials.
General fix: Configure an allow-list of known origins; set credentials: true only with a specific origin, never *.
G12 — No rate limiting¶
What: No cap on how many requests an IP/user can make, especially to /login / /authenticate.
Why it matters: Attackers can brute-force passwords or "credential-stuff" (try leaked password lists) as fast as the server answers; also enables cost-driving abuse of AI endpoints.
General fix: Add express-rate-limit — a tight limit on auth endpoints (e.g. 10/15 min per IP) and a looser global one. Behind nginx, set trust proxy so the real client IP is used.
G13 — Oversized request body limit (DoS)¶
What: Accepting very large request bodies (e.g. 50–150 MB JSON) on every route.
Why it matters: One attacker sending big bodies can exhaust server memory (denial of service), especially on unauthenticated endpoints.
General fix: Set a small global limit (e.g. 1mb); raise it per route only where large payloads are genuinely needed. Route binary uploads through a file-upload middleware with explicit size caps.
G14 — Sensitive data / PII in logs or browser storage¶
What: Writing passwords, tokens, hashes, or personal data (email, name) to server logs (console.log(req.body)) or localStorage.
Why it matters: Logs and shared-browser storage are read by people and systems who shouldn't see credentials/PII; it's also a compliance problem.
General fix: Never log credentials or whole request bodies; redact. Don't persist PII in localStorage; source it from app state or a /me endpoint and clear it on logout.
G15 — Outdated / EOL dependencies¶
What: Frameworks/libraries past end-of-life (e.g. Vue 2, Nuxt 2, Webpack 4) that no longer receive security patches; no automated update tooling.
Why it matters: New vulnerabilities in these will never be fixed; transitive CVEs pile up unnoticed.
General fix: Enable Dependabot (or Renovate) security PRs; run npm/yarn audit in CI; plan migration off EOL frameworks.
G16 — TLS / transport misconfiguration¶
What: Allowing deprecated TLS versions (TLS 1.0/1.1), leaking the server version banner, or serving mixed (http + https) content.
Why it matters: Old TLS enables downgrade/interception attacks and fails PCI-DSS (relevant where payments are processed); version banners aid attackers; mixed content can be tampered with.
General fix: ssl_protocols TLSv1.2 TLSv1.3; only; server_tokens off;; force HTTPS and use upgrade-insecure-requests.
4. Findings by repository¶
Seven repositories. Backends are Express + MySQL; the customer and designer web apps are Nuxt 2 / Vue 2; the admin web app is Vite + React. Many issues repeat across repos — that's expected (three backends were forked from one another) and each needs its own fix/ticket.
4.1 someli-api¶
Main customer-facing backend (Express + MySQL). The platform's largest and most security-critical service — it owns login.
🔴 SEC-API-01 — Login bypass: social:true logs in as anyone by email · Critical · [G4]¶
Where: routes/routes.js:6522-6620 (the /webauthenticate handler).
Why it matters: When a request includes social: true, the handler (a) matches the account by email alone and (b) issues a valid session token even after the password check fails. The only gate is a shared constant password marker. An attacker who knows a victim's email can log in as them — no password. This is the most serious issue on the platform.
Fix (Phase 0a):
1. Delete the branch that issues a token when social == true after the password comparison failed (around routes/routes.js:6612).
2. Social login must complete through the OAuth/Passport callback that sets the session — not via a flag on the password endpoint.
3. Remove the password == storedValue fallback (see SEC-API-03) so only a successful bcrypt.compare grants access.
🔴 SEC-API-02 — SQL injection in the login endpoints · Critical · [G1]¶
Where: /webauthenticate routes/routes.js:6529-6548; /authenticate routes/routes.js:6393-6403. (Hundreds of other interpolated queries exist platform-wide; the login ones are pre-auth and worst.)
Why it matters: email/sid are glued straight into the SQL. An unauthenticated attacker can inject — dump password hashes via UNION, or bypass the login logic for any account.
Fix (Phase 0a):
1. Make the query wrapper parameter-aware: at actions/actions.js:187 forward an optional values array — db.query(reqObj.q, reqObj.params || [], cb).
2. Rewrite the login queries with ? placeholders: WHERE m.username = ? / m.sid = ?, passing reqObj.params = [email, sid].
3. Convert the direct con.query(\… id=${…}`)calls in the same handler (:6554,:6567,:6594,:6604`) to placeholder form.
🟠 SEC-API-03 — Plaintext password storage & comparison · High · [G3]¶
Where: /register (stores plaintext); /login routes/routes.js:3039 and /webauthenticate :6603 (password == storedValue comparison — "pass-the-hash").
Why it matters: Passwords readable if the DB leaks; the == comparison can be satisfied by submitting the stored value (which SEC-API-02 can leak).
Fix (Phase 0):
1. Hash passwords with bcrypt at registration.
2. Verify only with bcrypt.compare(); delete every password == row.password branch.
3. Select the account by sid/email with a placeholder; never put the password in the SQL.
🟠 SEC-API-04 — Forgeable tokens · High · [G6]¶
Where: /auth/login mints a plaintext token id_timestamp_id (routes/routes.js:3060); on success the server stores auth_token = 'Bearer ' + sid where sid is non-secret (:6594).
Why it matters: Anyone who sees the token format can forge one / extract the user ID; a non-secret stored token is guessable.
Fix (Phase 0):
1. Replace the plaintext token at :3060 with the existing AES helper: encryptData({ userId, defaultPaymentId }) (already imported at :104).
2. Stop persisting Bearer <sid>; use an unguessable signed/encrypted token with an expiry.
🟠 SEC-API-05 — Hardcoded secrets (Slack token, Polotno key, session secret) · High · [G2]¶
Where: Slack token xoxb-3144… in 19 job files (e.g. job_color_check.js:11); Polotno key FXZv… in 49 files (e.g. job_design_generation.js:119); express-session secret "3eB(2…" in server.js:13.
Why it matters: Anyone with repo access can use these; rotating today means editing dozens of files.
Fix (Phase 0a):
1. Rotate all three at their providers.
2. Add SLACK_BOT_TOKEN, POLOTNO_KEY, SESSION_SECRET to .env + conf.js.
3. Replace the literals (a single shared accessor for Slack/Polotno makes future rotation one change); generate the session secret with crypto.randomBytes(32).toString('hex').
4. Scrub git history; add a secret scanner to CI.
🟠 SEC-API-06 — No rate limiting on auth endpoints · High · [G12]¶
Where: server.js / routes/routes.js — no limiter on /login (:3024), /authenticate (:6387), /webauthenticate (:6522), /register (:2421).
Why it matters: Brute force / credential stuffing is unthrottled; AI endpoints can be hammered to drive cost.
Fix (Phase 0):
1. yarn add express-rate-limit.
2. Add a tight authLimiter (e.g. 10 per 15 min/IP) on each credential route; a looser global one elsewhere.
3. Set app.set('trust proxy', 1) (nginx fronts the app) so limiting keys on the real IP.
🟡 SEC-API-07 — Credentials logged at login · Medium · [G14]¶
Where: console.log("resss", req.body) routes/routes.js:6525; console.log("ress", …, result[0].password, reqObj.password) :6585.
Why it matters: Cleartext passwords and stored hashes land in production logs.
Fix (Phase 0): Remove these logs (and any that print req.body/passwords/hashes); redact if a log is needed.
🟡 SEC-API-08 — CORS accepts all origins · Medium · [G11]¶
Where: app.use(cors()) with no options, server.js:145.
Why it matters: Any website's JS can call the API.
Fix (Phase 0): Configure an origin allow-list from conf.ALLOWED_ORIGINS with credentials: true; mount it before routes.
🟡 SEC-API-09 — No security headers (helmet not loaded) · Medium · [G7]¶
Where: server.js — helmet is in package.json:54 but never wired in.
Why it matters: None of the protective headers are sent.
Fix (Phase 0): Add app.use(helmet({ contentSecurityPolicy: false })) as the first middleware (it's a JSON API). Ensure nginx isn't setting duplicates.
🟡 SEC-API-10 — 150 MB request body limit · Medium · [G13]¶
Where: express.json({ limit: '150mb', parameterLimit: 50000 }) server.js:24.
Why it matters: Memory-exhaustion DoS, including on unauthenticated routes.
Fix (Phase 0): Lower global to 1mb/parameterLimit: 1000; per-route override only where large payloads are needed; route binary uploads through express-fileupload.
🟡 SEC-API-11 — Input validation on one route only · Medium · [G1]¶
Where: middlewares/validation.js defines only registerVal.
Why it matters: Most handlers trust input; no boundary validation supports the injection risks above.
Fix (Phase 0): Add validator chains (e.g. loginVal with isEmail()/notEmpty()) and apply them on each route before the handler.
🟡 SEC-API-12 — Token revocation kept in memory · Medium · [G6]¶
Where: helper/revokeToken.js — revoked tokens in an in-process Set.
Why it matters: Restart re-validates "revoked" tokens; revocation doesn't apply across replicas; tokens have no expiry, so a stolen one is effectively permanent.
Fix (Phase 0): Persist revocations in MySQL/Redis (shared, survives restarts); pair with token expiry.
⚪ SEC-API-13 — Middleware order / no error handler · Low · [G7]¶
Where: server.js — CORS mounted after session (:145); no global error handler; no request logger.
Why it matters: Unconventional ordering; uncaught errors may leak stack traces; requests aren't traceable.
Fix (Phase 1): Order helmet → cors → session → fileUpload → body parser; add a final error-handling middleware that hides stack traces in production; add a request logger (morgan).
4.2 someli-platform¶
Customer-facing web app (Nuxt 2 / Vue 2 single-page app), served behind nginx.
🔴 SEC-WEB-01 — AWS access key + secret hardcoded · Critical · [G2]¶
Where: plugins/aws_sdk.js:4-6 (accessKeyId/secretAccessKey), committed since 2022-08-01.
Why it matters: Live AWS credentials in git history for ~3.5 years — anyone with any past repo access has them. Treat as a breach.
Fix (Phase 0a):
1. Rotate the AWS key now in IAM; audit CloudTrail for misuse since 2022-08-01.
2. Delete the file; scrub git history (git filter-repo/BFG), force-push, re-clone.
3. Add gitleaks to CI.
🟠 SEC-WEB-02 — Session token in browser-readable cookie · High · [G5]¶
Where: token and usedetail cookies set without HttpOnly (auth strategy in nuxt.config.js:285-298).
Why it matters: Any same-origin script (including the CDN scripts and v-html sinks below) can read the token → account takeover.
Fix (Phase 0): Backend sets an HttpOnly; Secure; SameSite cookie; switch the @nuxtjs/auth strategy to server-set-cookie mode and stop writing the token from JS; send requests with withCredentials.
🟠 SEC-WEB-03 — No Content Security Policy · High · [G8]¶
Where: No CSP in nginx.conf or app; nuxt.config.js:121 sets __dangerouslyDisableSanitizers: ['script'].
Why it matters: Any XSS has unrestricted script execution.
Fix (Phase 0): Add a baseline CSP in nginx (allow self + the known CDN hosts + frame-ancestors 'none'); tighten over time; move inline analytics to files to drop 'unsafe-inline'.
🟠 SEC-WEB-04 — No Subresource Integrity on 14 CDN scripts · High · [G9]¶
Where: nuxt.config.js:23-119 (head.script) — includes flagmeister.github.io (a GitHub-Pages-hosted third party).
Why it matters: A compromised CDN runs arbitrary JS with access to the token.
Fix (Phase 0a): Add integrity="sha384-…" + crossorigin to each pinned script; self-host where possible.
🟠 SEC-WEB-05 — v-html renders unsanitised HTML (incl. AI chat output) · High · [G10]¶
Where: 9 v-html sites across 4 components; worst is components/Chatbot.vue:1421-1453 (renders AI/server text as HTML).
Why it matters: Stored/reflected XSS — markup in the chat/profile data executes.
Fix (Phase 0): Add DOMPurify; wrap each v-html value in a safeHtml() sanitiser or switch to {{ }}; add the vue/no-v-html lint rule.
🟠 SEC-WEB-06 — End-of-life framework stack · High · [G15]¶
Where: vue@2.7, nuxt@2.18, webpack@4 — all EOL.
Why it matters: No security patches for new vulnerabilities.
Fix (Phase 2): Plan Vue 2→3 / Nuxt 2→3 migration; until then treat dependency PRs as the only patch channel.
🟠 SEC-WEB-07 — No automated dependency updates · High · [G15]¶
Where: No .github/dependabot.yml / Renovate.
Why it matters: Transitive CVEs accumulate unnoticed.
Fix (Phase 0): Add Dependabot security PRs (cover the /polotno-editor sub-package too); run yarn audit in CI.
🟠 SEC-WEB-08 — Role gating trusts a user-editable cookie · High · [G4]¶
Where: middleware/head.js branches on roleType/account_role read from cookies the user can edit.
Why it matters: A user can change their cookie to escalate privileges; the UI is the only gate.
Fix (Phase 1): Enforce roles on the backend per request; fetch the user record from an authenticated /me endpoint rather than trusting cookie values.
🟡 SEC-WEB-09 — Missing nginx security headers · Medium · [G7]¶
Where: nginx.conf sets no HSTS / X-Frame-Options / X-Content-Type-Options / Referrer-Policy.
Why it matters: Browsers can't enforce protections, including anti-framing (clickjacking).
Fix (Phase 0): Add the header block (HSTS, X-Content-Type-Options nosniff, Referrer-Policy, plus the anti-framing headers in SEC-WEB-10).
🟡 SEC-WEB-10 — Clickjacking: app is framable (external report) · Medium · [G7]¶
Where: No X-Frame-Options/CSP frame-ancestors anywhere. (An external researcher reported this 2026-05-22; their wider claims were unsubstantiated, but the framing gap is real.)
Why it matters: A hostile site can embed the app in an invisible <iframe> and trick a logged-in user into clicking a one-click action.
Fix (Phase 0): add_header X-Frame-Options "DENY" always; and add_header Content-Security-Policy "frame-ancestors 'none'" always;. If any single-click destructive action exists on a framable page, add a confirm/re-auth step too.
🟡 SEC-WEB-11 — Deprecated TLS 1.0/1.1 enabled · Medium · [G16]¶
Where: nginx.conf:36 ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;.
Why it matters: TLS 1.0/1.1 are deprecated and PCI-DSS-prohibited (the app takes payments).
Fix (Phase 0): ssl_protocols TLSv1.2 TLSv1.3;.
🟡 SEC-WEB-13 — CORS allow-list unverified · Medium · [G11]¶
Where: Frontend uses SameSite=None cookies; backend CORS config not confirmed.
Why it matters: If the backend allows * with credentials, cross-site abuse is possible.
Fix (Phase 0): Confirm the backend CORS allow-list is a specific origin, not * (see SEC-API-08).
🟡 SEC-WEB-18 — Ad-blockers break payment SDKs · Medium (operational) · [—]¶
Where: Stripe.js / Paddle.js loaded globally via nuxt.config.js head.script.
Why it matters: Common blockers stop these URLs → silent checkout failures (revenue loss), not a security hole.
Fix (Phase 0): Load payment SDKs only on checkout routes; detect a failed load and show a clear message; instrument it.
⚪ SEC-WEB-12 — nginx version disclosure · Low · [G7]¶
Where: nginx.conf:26 — server_tokens off; is commented out.
Fix (Phase 0): Uncomment server_tokens off;.
⚪ SEC-WEB-14 — Public env vars in bundle (verify no secrets) · Low · [G2]¶
Where: Build-time .env values ship in the client bundle.
Fix (Phase 0a): Grep a fresh build to confirm no OAuth client secrets / EXPRESS_SECRET are bundled; move any that are server-side.
⚪ SEC-WEB-15 — Token possibly in URL (verify) · Low · [G6]¶
Where: Auth flows — spot-check whether any put the token in query params. Fix (Phase 1): Confirm; if found, move to header/cookie (URLs leak via history/logs/Referer).
⚪ SEC-WEB-16 — Hardcoded telemetry/license keys · Low · [G2]¶
Where: Polotno license key + analytics IDs in nuxt.config.js/polotno-editor/.
Fix (Phase 1): Move to env where applicable; confirm Polotno license terms tolerate a committed key.
⚪ SEC-WEB-17 — Mixed content · Low · [G16]¶
Where: Spot-check pages//components/ for http:// asset URLs.
Fix (Phase 1): Use HTTPS everywhere; add upgrade-insecure-requests to the CSP.
4.3 designer-api¶
Backend for the internal designer tool (Express + MySQL). Thinner than someli-api; no shared auth middleware.
🟠 SEC-DAPI-01 — Hardcoded Slack bot token · High · [G2]¶
Where: teamsnotification.js:1-2 (xoxb-3144…, channel C05H25MDY3Z).
Fix (Phase 0a): Rotate the token; move to SLACK_BOT_TOKEN/SLACK_CHANNEL_ID in conf.js; replace the literals; scrub history.
🟠 SEC-DAPI-02 — Hardcoded Unsplash access key · High · [G2]¶
Where: routes/routes.js:47-50 (accessKey: 't0uI…').
Fix (Phase 0a): Rotate at Unsplash; move to conf.UNSPLASH_ACCESS_KEY; fail fast if unset.
🟠 SEC-DAPI-03 — No shared auth middleware · High · [G4]¶
Where: No middlewares/ directory; each handler does its own auth (or forgets).
Why it matters: A new endpoint added by someone who forgets the check is publicly callable.
Fix (Phase 0): Create middlewares/auth.js validating the Bearer <sid> token against tMember (parameterised query); mount it on the router with an explicit public allow-list (/webauthenticate, health); add the folder to the Dockerfile COPY.
🟠 SEC-DAPI-06 — Committed Google OAuth client secret · High · [G2]¶
Where: conf/credentials.json (tracked, not gitignored) — contains client_secret.
Fix (Phase 0a): Rotate in Google Cloud Console; git rm --cached + .gitignore; inject at runtime; scrub history. (Also remove from the Docker image — see SEC-DAPI-13.)
🟠 SEC-DAPI-13 — .env baked into the Docker image · High · [G2]¶
Where: Dockerfile:35 COPY .env conf.js favicon.ico ./.
Why it matters: Every secret (OPENAI_API_KEY, AWS keys, DB password, SendGrid/Pexels/Pixabay, etc.) is readable by anyone who can pull the image.
Fix (Phase 0a): Drop .env from the COPY; inject secrets at runtime (--env-file, secrets manager, or orchestrator secrets); add .env to .dockerignore; rotate all baked-in secrets.
🟡 SEC-DAPI-04 — CORS wildcard + malformed credentials header · Medium · [G11]¶
Where: server.js:8-15 — cors() + a manual res.header('"Access-Control-Allow-Credentials" : true') (the whole string is the header name).
Fix (Phase 0): Replace with one configured cors({ origin: allowlist, credentials: true }); delete the malformed manual header.
🟡 SEC-DAPI-07 — SQL injection in interpolated queries · Medium · [G1]¶
Where: request-controlled values interpolated at routes/routes.js:1499, :1577, :1580, :1620, :1700, :1703, :1807, :1810, :1845, and the login query :187.
Fix (Phase 0): Parameterise these with ? placeholders + values array; extend the DB wrapper (modules/dbDriver/lib/mysql.js) to forward bind params; allow-list any dynamic table names.
🟡 SEC-DAPI-05 — 50 MB body limit · Medium · [G13]¶
Where: server.js:17 global bodyparser.json({ limit: '50MB' }).
Fix (Phase 1): Lower global to 256kb; per-route override for Polotno/image endpoints; cap express-fileupload.
🟡 SEC-DAPI-10 — No rate limiting · Medium · [G12]¶
Where: No express-rate-limit; /webauthenticate (routes/routes.js:171) is unauthenticated.
Fix (Phase 0): Add express-rate-limit — tight on /webauthenticate, looser globally; trust proxy for the real IP.
🟡 SEC-DAPI-08 — forEach over async work (correctness) · Medium · [—]¶
Where: 18 job files use forEach(async …) (e.g. job_get_image.js:304).
Why it matters: forEach doesn't await — race conditions, unbounded concurrency on OpenAI/S3, re-entrancy bugs. (Reliability, not a direct vuln.)
Fix (Phase 0): Use for…of with await, or p-limit for bounded parallelism; reset re-entrancy guards in a finally.
⚪ SEC-DAPI-09 — Polotno license key hardcoded · Low · [G2]¶
Where: routes/routes.js — key: 'FXZv…' in 11 places (+ job scripts).
Fix (Phase 0a): Move to conf.POLOTNO_KEY; rotate if your plan treats the key as secret (some Polotno keys are domain-scoped/public).
⚪ SEC-DAPI-11 — No app-layer security headers · Low · [G7]¶
Where: server.js sets no helmet/HSTS.
Fix (Phase 1): Add helmet({ hsts: … }); confirm nginx redirects HTTP→HTTPS.
⚪ SEC-DAPI-12 — nginx.conf not audited · Low · [G7]¶
Where: Committed nginx.conf shipped via Dockerfile:36, contents unreviewed.
Fix (Phase 1): Review it; ensure server_tokens off, HSTS, X-Content-Type-Options, matching client_max_body_size; nginx -t before deploy.
4.4 Someli-admin-api¶
Backend for the internal admin console (Express + MySQL). Forked from someli-api.
🟠 SEC-AAPI-01 — Hardcoded session secret · High · [G2]¶
Where: server.js:11 secret: "3eB(2…" (same for dev/uat/prod); saveUninitialized: true.
Fix (Phase 0a): Rotate (random 48 bytes, per-env); move to conf.SESSION_SECRET with fail-fast; set saveUninitialized: false + httpOnly/secure/sameSite cookie; scrub history.
🟠 SEC-AAPI-02 — Hardcoded Slack bot token · High · [G2]¶
Where: routes/auth.js:20-21 (xoxb-3144…, channel C05TS9AHBH6).
Fix (Phase 0a): Rotate; move to conf; replace literals at :20-21,:25; reduce bot scopes to chat:write; scrub history.
🟠 SEC-AAPI-10 — Committed Google OAuth client secret · High · [G2]¶
Where: conf/credentials.json (tracked, committed 75b92e9, not gitignored) — client_secret for an OAuth web client.
Fix (Phase 0a): Rotate in Google Cloud Console; git rm --cached + .gitignore; inject at deploy; scrub history.
🟡 SEC-AAPI-03 — In-memory token revocation · Medium · [G6]¶
Where: helper/revokeToken.js — revoked tokens in a Set.
Fix (Phase 0): Persist in a tRevokedToken table (parameterised insert/lookup); make middlewares/auth.js check it; add expiry cleanup.
🟡 SEC-AAPI-04 — Tokens never expire · Medium · [G6]¶
Where: helper/tokenGenerator.js:3,8 — JWT_EXPIRES_IN not in conf.js, so expiresIn is undefined → perpetual token.
Fix (Phase 0): Set JWT_EXPIRES_IN (e.g. 24h) with a safe default in code; jwt.verify then rejects expired tokens automatically.
🟡 SEC-AAPI-05 — CORS wildcard · Medium · [G11]¶
Where: server.js:75 app.use(cors()).
Fix (Phase 0): Allow-list from conf.CORS_ORIGINS (e.g. https://admin.someli.ai, http://localhost:8080) with credentials: true.
🟡 SEC-AAPI-07 — Role gating in SQL only · Medium · [G4]¶
Where: No requireRole middleware; each handler enforces role via WHERE filters (e.g. routes/auth.js:74,93-102 — role=all yields an empty filter = everything).
Fix (Phase 0): Add a requireRole(allowed) middleware reading res.role; apply per route; make the role=all branch admin-only.
🟡 SEC-AAPI-08 — SQL injection via dynamic filters · Medium · [G1]¶
Where: request-derived interpolation, e.g. the member-update at routes/auth.js:410 (sid='${reqObj.email}' … WHERE id=${reqObj.id}) and member-list at routes/routes.js:817-821,837.
Fix (Phase 0): Rewrite with ? placeholders + values array; allow-list dynamic identifiers; grep for the rest and classify.
⚪ SEC-AAPI-06 — Webhook body-parser exemption with no handlers · Low · [—]¶
Where: server.js:19-25 exempts Stripe/Paddle webhook paths from JSON parsing, but no handlers exist (they 404).
Fix (Phase 1): If vestigial, delete the exemption; if planned, mount handlers that verify the signature over the raw body before trusting it.
⚪ SEC-AAPI-09 — 150 MB body limit · Low · [G13]¶
Where: server.js:23 express.json({ limit: '150mb', parameterLimit: 50000 }).
Fix (Phase 1): Lower to 1mb; per-route override where needed; cap express-fileupload; add helmet.
4.5 someli-dashboard-be¶
Tiny standalone Express service mirroring someli-api/dashboard/. In production the in-process copy inside someli-api runs; this standalone is for local dev. The two have drifted — fixes here should also be applied to the copy inside someli-api/dashboard/.
🟡 SEC-DASH-02 — Auth-by-convention · Medium · [G4]¶
Where: Handlers read res.userId/res.accountId assuming an upstream middleware set them; if mounted without that upstream auth, they run unauthenticated.
Fix (Phase 0): Add an in-router guard (routes/index.js:24) that 401s when res.userId/res.accountId are missing — fails closed.
⚪ SEC-DASH-01 — Fallback session secret "change-me" · Low · [G2]¶
Where: server.js:9 secret: process.env.SESSION_SECRET || "change-me".
Fix: Remove the unused express-session entirely (nothing reads req.session), or fail-fast if SESSION_SECRET is unset + add httpOnly/secure/sameSite.
⚪ SEC-DASH-03 — Mock auth is deployable · Low · [G4]¶
Where: mock/middlewares/auth.js sets fake userId/accountId when NODE_ENV=development.
Why it matters: If ever deployed with NODE_ENV=development, every request is a fake authenticated user.
Fix: Gate the mock behind an explicit ALLOW_MOCK_AUTH=yes flag that throws otherwise; set NODE_ENV=production in prod.
⚪ SEC-DASH-04 — No standalone hardening · Low · [G7]¶
Where: server.js has no helmet/CORS allow-list/rate-limit when run on its own.
Fix: Add helmet(), a CORS allow-list, and express-rate-limit on /dashboard/*.
⚪ SEC-DASH-05 — Spurious / deprecated dependencies · Low · [G15]¶
Where: package.json declares unused jsonwebtoken, crypto-js, request (deprecated), node-cron, path (npm clone of a built-in).
Fix: Confirm unused, then npm uninstall them; run npm audit.
4.6 admin_console_R¶
Internal admin web app (Vite + React + TypeScript + shadcn/ui) — the only modern frontend. Bootstrapped from Lovable.dev.
🟡 SEC-ADMINUI-01 — Auth token in localStorage · Medium · [G5]¶
Where: src/context/AuthContext.tsx:32-34, src/services/api.ts:16-18.
Why it matters: XSS-readable; combined with no server-side expiry/revocation → long-lived takeover.
Fix (Phase 0): Move to an HttpOnly; Secure; SameSite=Strict cookie set by the backend (fetch(..., { credentials: "include" })); interim, use in-memory/sessionStorage not localStorage.
🟡 SEC-ADMINUI-03 — Third-party Lovable.dev script in production · Medium · [G9]¶
Where: index.html:14-15 — <script src="https://cdn.gpteng.co/gptengineer.js"> with no SRI.
Why it matters: If that CDN is compromised, arbitrary JS runs in admin staff browsers.
Fix (Phase 0): Remove it from production (it's only needed inside the Lovable editor) — delete the tag, or strip it in a Vite transformIndexHtml plugin when mode !== 'development'; if it must stay, pin with integrity + crossorigin.
🟡 SEC-ADMINUI-04 — No Content Security Policy · Medium · [G8]¶
Where: No CSP header or meta tag in the repo.
Fix (Phase 0): Add a CSP at the nginx layer (or meta tag): default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' <api>; frame-ancestors 'none'; object-src 'none'; roll out report-only first.
🟡 SEC-ADMINUI-11 — No 401 → logout · Medium · [G6]¶
Where: src/services/api.ts:5-13 (handleResponse) doesn't branch on status.
Why it matters: A revoked/expired token leaves the user "logged in" client-side with every action failing.
Fix (Phase 0): On 401/403, clear auth state and redirect to /login (register a module-level callback that AuthContext wires to logout()).
⚪ SEC-ADMINUI-02 — Hardcoded Apptype header · Low · [G2]¶
Where: src/services/api.ts:23 btoa("admin-console") while ENV.APP_TYPE exists.
Fix: Use btoa(ENV.APP_TYPE) (from src/config/env.ts:8); fail loudly if unset. (Not a secret — just dead config.)
⚪ SEC-ADMINUI-05 — No SRI · Low · [G9]¶
Where: The only external script is the Lovable one (SEC-ADMINUI-03).
Fix: Resolve via SEC-ADMINUI-03; add a CI grep that fails on an https:// <script> without integrity.
⚪ SEC-ADMINUI-09 — dangerouslySetInnerHTML without sanitisation · Low · [G10]¶
Where: src/components/ui/chart.tsx:79 — shadcn ChartStyle, builds inline CSS from a developer-defined config (not user input today).
Fix: Keep config developer-defined; if chart colours/labels ever come from API/user data, validate them before interpolation; add the react/no-danger lint rule; use DOMPurify for any real HTML rendering.
⚪ SEC-ADMINUI-10 — PII (user_email etc.) in localStorage · Low · [G14]¶
Where: user_email/useid/user_first_name written in AuthContext.tsx and read across pages; logout() (:206) only clears the token.
Fix: Source these from auth context / a /me endpoint; if cached, use sessionStorage; clear all keys on logout.
⚪ SEC-ADMINUI-06 — HMR overlay disabled (dev-only) · Low · [—]¶
Where: vite.config.ts:18-21 hmr: { overlay: false }.
Fix: Re-enable the overlay (dev quality-of-life; no production impact).
⚪ SEC-ADMINUI-07 — lovable-tagger dev plugin · Low · [G15]¶
Where: vite.config.ts:25 (gated to dev).
Fix: Keep the mode === 'development' gate; verify dist/ has no data-lovable; remove entirely if Lovable is retired.
⚪ SEC-ADMINUI-08 — Minimal Vite fs.deny (dev-only) · Low · [—]¶
Where: vite.config.ts:14-17 deny: ['.env', '.git'].
Fix: Broaden to ['.env', '.env.*', '*.pem', '*.key', '.git', '.git/**']; keep strict: true; don't expose the dev server beyond localhost.
4.7 Someli-Designer¶
Internal designer web app (Nuxt 2 / Vue 2 + embedded Polotno editor).
🟠 SEC-DESUI-01 — Auth guard is per-page, not global · High (verify) · [G4]¶
Where: nuxt.config.js:49-51 registers only axios middleware globally; middleware: 'auth' is declared on 53 pages individually (e.g. pages/AI-Content.vue:57).
Why it matters: Any new page that forgets the line ships unguarded.
Fix (Phase 0): Make the guard global — router: { middleware: ['auth', 'axios'] }; public pages opt out with auth: 'guest'; regression-test with a cleared session.
🟡 SEC-DESUI-02 — Token in localStorage / userdetail cookie · Medium · [G5]¶
Where: @nuxtjs/auth local strategy default + the userdetail cookie (read in middleware/guest.js:3).
Fix (Phase 0, needs backend coord): Backend sets an HttpOnly cookie; switch the strategy to cookie mode (token: { property: false }); hydrate the user from GET /me; drop the userdetail cookie.
🟡 SEC-DESUI-03 — No server-side logout · Medium · [G6]¶
Where: nuxt.config.js:72 logout: false — the FE never tells the backend to invalidate the token.
Fix (Phase 0, needs backend coord): Add POST /logout (revoke token + clear cookie); set logout: { url: 'logout', method: 'post' }; wire a logout control in components/Navbar.vue.
🟡 SEC-DESUI-04 — Role gating UI-only · Medium · [G4]¶
Where: components/Navbar.vue hides items by role_type (magic numbers 1/2/6/12/13); URL-typing bypasses it.
Fix (Phase 1): Enforce roles on the backend per route (authoritative); add an FE route guard as defence-in-depth; replace magic numbers with named constants.
🟡 SEC-DESUI-06 — No Content Security Policy · Medium · [G8]¶
Where: nginx.conf is referenced by Dockerfile:77 but not committed, so no CSP is reviewable.
Fix (Phase 0): Commit nginx.conf; add a CSP whitelisting jsDelivr/Hotjar/Polotno/Pexels + the companion headers (HSTS, X-Frame-Options, X-Content-Type-Options); report-only first.
⚪ SEC-DESUI-05 — CDN scripts without SRI · Low · [G9]¶
Where: nuxt.config.js:16-20 — jsDelivr Bootstrap bundle, no integrity.
Fix (Phase 1): Add integrity + crossorigin, or self-host from npm (Bootstrap 5.3.3 is already a dependency).
⚪ SEC-DESUI-08 — Hardcoded Polotno key + design data in localStorage · Low · [G2]¶
Where: polotno-editor/index.js:7 key: "FXZv…" (also in committed polotno-bundle.js); designs/brand data persisted to localStorage.
Fix (Phase 1): Move the key to a build-time env var; rotate (confirm origin-binding/terms); scope persisted design keys per user and clear on logout; strip design-state console.logs in polotno-editor/App.js.
⚪ SEC-DESUI-09 — Client-side bcrypt hashing · Low · [G3]¶
Where: pages/login.vue:98,156-160 hash the password in the browser before login.
Why it matters: A browser-side bcrypt with a random salt is non-deterministic and can't be verified server-side — the hash just becomes the password, providing no protection.
Fix (Phase 1): Remove client-side hashing; send the plaintext password over HTTPS; verify with bcrypt on the backend; drop the bcryptjs FE dependency.
⚪ SEC-DESUI-10 — v-html renders AI/PDF content · Low · [G10]¶
Where: 7 pages (e.g. pages/AI-Content.vue:38, pages/pdfToContent.vue:36).
Fix (Phase 1): Sanitise with a DOMPurify Vue filter (v-html="x | clean"), or use {{ }} where markup isn't needed.
⚪ SEC-DESUI-07 — Bootstrap-Vue v2 ↔ Bootstrap v5 mismatch (UX) · Low · [—]¶
Where: bootstrap-vue@2.x (targets BS4) + bootstrap@5.3.3 + a CDN BS5 bundle loaded together.
Fix (Phase 1): Pick one Bootstrap line; remove the redundant CDN script (also closes SEC-DESUI-05).
Appendix A — All findings (ticket table)¶
One row per finding. Columns: ID (stable ticket key), Repo, Sev, Cat (glossary code), Title, Key location, Fix summary, Phase. Detail for each ID is in §4 under the same ID.
| ID | Repo | Sev | Cat | Title | Key location | Fix summary | Phase |
|---|---|---|---|---|---|---|---|
| SEC-API-01 | someli-api | 🔴 Critical | G4 | Login bypass via social:true |
routes/routes.js:6612 |
Delete the social token-grant branch; OAuth-only social login | 0a |
| SEC-API-02 | someli-api | 🔴 Critical | G1 | SQL injection in login endpoints | routes/routes.js:6529,6393 |
Parameterise email/sid; make queryAll param-aware |
0a |
| SEC-API-03 | someli-api | 🟠 High | G3 | Plaintext password storage & compare | routes/routes.js:3039,6603 |
bcrypt only; remove == comparisons |
0 |
| SEC-API-04 | someli-api | 🟠 High | G6 | Forgeable tokens | routes/routes.js:3060,6594 |
AES token; drop Bearer+sid; add expiry |
0 |
| SEC-API-05 | someli-api | 🟠 High | G2 | Hardcoded Slack/Polotno/session secrets | job_color_check.js:11 +67 files |
Rotate; move to env; replace literals; scrub history | 0a |
| SEC-API-06 | someli-api | 🟠 High | G12 | No rate limiting on auth | routes/routes.js:3024,6387,6522 |
express-rate-limit on auth routes |
0 |
| SEC-API-07 | someli-api | 🟡 Medium | G14 | Credentials logged at login | routes/routes.js:6525,6585 |
Remove credential/req.body logs |
0 |
| SEC-API-08 | someli-api | 🟡 Medium | G11 | CORS accepts all origins | server.js:145 |
Origin allow-list + credentials | 0 |
| SEC-API-09 | someli-api | 🟡 Medium | G7 | No security headers (helmet) | server.js |
app.use(helmet(...)) |
0 |
| SEC-API-10 | someli-api | 🟡 Medium | G13 | 150 MB body limit | server.js:24 |
Global 1mb; per-route override |
0 |
| SEC-API-11 | someli-api | 🟡 Medium | G1 | Input validation on one route only | middlewares/validation.js |
Validator chains per route | 0 |
| SEC-API-12 | someli-api | 🟡 Medium | G6 | In-memory token revocation | helper/revokeToken.js |
Persist revocations; add expiry | 0 |
| SEC-API-13 | someli-api | ⚪ Low | G7 | Middleware order / no error handler | server.js |
Reorder; add error handler + logger | 1 |
| SEC-WEB-01 | someli-platform | 🔴 Critical | G2 | Hardcoded AWS access key + secret | plugins/aws_sdk.js:4-6 |
Rotate; delete; scrub history | 0a |
| SEC-WEB-02 | someli-platform | 🟠 High | G5 | Token in browser-readable cookie | nuxt.config.js:285-298 |
Server-set HttpOnly cookie | 0 |
| SEC-WEB-03 | someli-platform | 🟠 High | G8 | No Content Security Policy | nginx.conf |
Add nginx CSP; drop unsafe-inline | 0 |
| SEC-WEB-04 | someli-platform | 🟠 High | G9 | No SRI on 14 CDN scripts | nuxt.config.js:23-119 |
Add integrity; self-host |
0a |
| SEC-WEB-05 | someli-platform | 🟠 High | G10 | v-html XSS (AI chat output) |
components/Chatbot.vue:1421 |
DOMPurify; vue/no-v-html |
0 |
| SEC-WEB-06 | someli-platform | 🟠 High | G15 | EOL Vue 2 / Nuxt 2 / Webpack 4 | package.json |
Plan Vue3/Nuxt3 migration | 2 |
| SEC-WEB-07 | someli-platform | 🟠 High | G15 | No automated dependency updates | .github/ |
Enable Dependabot; yarn audit CI |
0 |
| SEC-WEB-08 | someli-platform | 🟠 High | G4 | Role gating trusts editable cookie | middleware/head.js |
Enforce roles server-side | 1 |
| SEC-WEB-09 | someli-platform | 🟡 Medium | G7 | Missing nginx security headers | nginx.conf |
Add HSTS/XCTO/Referrer-Policy | 0 |
| SEC-WEB-10 | someli-platform | 🟡 Medium | G7 | Clickjacking — app is framable | nginx.conf |
X-Frame-Options + frame-ancestors | 0 |
| SEC-WEB-11 | someli-platform | 🟡 Medium | G16 | TLS 1.0/1.1 enabled | nginx.conf:36 |
ssl_protocols TLSv1.2 TLSv1.3 |
0 |
| SEC-WEB-13 | someli-platform | 🟡 Medium | G11 | CORS allow-list unverified | backend | Confirm specific origin, not * |
0 |
| SEC-WEB-18 | someli-platform | 🟡 Medium | — | Ad-blockers break payment SDKs | nuxt.config.js |
Load SDKs on checkout; detect failure | 0 |
| SEC-WEB-12 | someli-platform | ⚪ Low | G7 | nginx version disclosure | nginx.conf:26 |
server_tokens off |
0 |
| SEC-WEB-14 | someli-platform | ⚪ Low | G2 | Public env vars in bundle | build | Grep build; no secrets bundled | 0a |
| SEC-WEB-15 | someli-platform | ⚪ Low | G6 | Token possibly in URL | auth flows | Verify; move to header/cookie | 1 |
| SEC-WEB-16 | someli-platform | ⚪ Low | G2 | Hardcoded telemetry/license keys | nuxt.config.js |
Move to env; confirm Polotno terms | 1 |
| SEC-WEB-17 | someli-platform | ⚪ Low | G16 | Mixed content | pages/,components/ |
HTTPS only; upgrade-insecure-requests | 1 |
| SEC-DAPI-01 | designer-api | 🟠 High | G2 | Hardcoded Slack token | teamsnotification.js:1 |
Rotate; move to env; scrub history | 0a |
| SEC-DAPI-02 | designer-api | 🟠 High | G2 | Hardcoded Unsplash key | routes/routes.js:47-50 |
Rotate; move to env | 0a |
| SEC-DAPI-03 | designer-api | 🟠 High | G4 | No shared auth middleware | (no middlewares/) |
Add auth middleware + public allow-list | 0 |
| SEC-DAPI-06 | designer-api | 🟠 High | G2 | Committed Google OAuth secret | conf/credentials.json |
Rotate; untrack; inject at runtime | 0a |
| SEC-DAPI-13 | designer-api | 🟠 High | G2 | .env baked into Docker image |
Dockerfile:35 |
Drop from COPY; inject at runtime; rotate | 0a |
| SEC-DAPI-04 | designer-api | 🟡 Medium | G11 | CORS wildcard + malformed header | server.js:8-15 |
One configured cors(); delete manual header |
0 |
| SEC-DAPI-07 | designer-api | 🟡 Medium | G1 | SQL injection in interpolated queries | routes/routes.js:1499… |
Parameterise; extend DB wrapper | 0 |
| SEC-DAPI-05 | designer-api | 🟡 Medium | G13 | 50 MB body limit | server.js:17 |
Global 256kb; per-route override |
1 |
| SEC-DAPI-10 | designer-api | 🟡 Medium | G12 | No rate limiting | routes/routes.js:171 |
express-rate-limit; trust proxy |
0 |
| SEC-DAPI-08 | designer-api | 🟡 Medium | — | forEach over async (correctness) |
job_get_image.js:304 +17 |
for…of await / p-limit |
0 |
| SEC-DAPI-09 | designer-api | ⚪ Low | G2 | Hardcoded Polotno key (11×) | routes/routes.js:1261… |
Move to env; rotate if secret | 0a |
| SEC-DAPI-11 | designer-api | ⚪ Low | G7 | No app-layer security headers | server.js |
Add helmet |
1 |
| SEC-DAPI-12 | designer-api | ⚪ Low | G7 | nginx.conf not audited |
nginx.conf |
Review; add headers; nginx -t |
1 |
| SEC-AAPI-01 | Someli-admin-api | 🟠 High | G2 | Hardcoded session secret | server.js:11 |
Rotate; env; fail-fast; secure cookie | 0a |
| SEC-AAPI-02 | Someli-admin-api | 🟠 High | G2 | Hardcoded Slack token | routes/auth.js:20-21 |
Rotate; env; reduce scopes | 0a |
| SEC-AAPI-10 | Someli-admin-api | 🟠 High | G2 | Committed Google OAuth secret | conf/credentials.json |
Rotate; untrack; inject at deploy | 0a |
| SEC-AAPI-03 | Someli-admin-api | 🟡 Medium | G6 | In-memory token revocation | helper/revokeToken.js |
Persist in tRevokedToken |
0 |
| SEC-AAPI-04 | Someli-admin-api | 🟡 Medium | G6 | Tokens never expire | helper/tokenGenerator.js:3 |
Set JWT_EXPIRES_IN + default |
0 |
| SEC-AAPI-05 | Someli-admin-api | 🟡 Medium | G11 | CORS wildcard | server.js:75 |
Origin allow-list | 0 |
| SEC-AAPI-07 | Someli-admin-api | 🟡 Medium | G4 | Role gating in SQL only | routes/auth.js:74 |
requireRole middleware per route |
0 |
| SEC-AAPI-08 | Someli-admin-api | 🟡 Medium | G1 | SQL injection via dynamic filters | routes/auth.js:410 |
Parameterise; allow-list identifiers | 0 |
| SEC-AAPI-06 | Someli-admin-api | ⚪ Low | — | Webhook exemption, no handlers | server.js:19-25 |
Remove, or implement + verify signature | 1 |
| SEC-AAPI-09 | Someli-admin-api | ⚪ Low | G13 | 150 MB body limit | server.js:23 |
Lower to 1mb; add helmet |
1 |
| SEC-DASH-02 | someli-dashboard-be | 🟡 Medium | G4 | Auth-by-convention | routes/index.js:24 |
In-router guard fails closed | 0 |
| SEC-DASH-01 | someli-dashboard-be | ⚪ Low | G2 | Fallback session secret change-me |
server.js:9 |
Remove unused session / fail-fast | 1 |
| SEC-DASH-03 | someli-dashboard-be | ⚪ Low | G4 | Mock auth deployable | mock/middlewares/auth.js |
Gate behind explicit opt-in flag | 1 |
| SEC-DASH-04 | someli-dashboard-be | ⚪ Low | G7 | No standalone hardening | server.js |
helmet + CORS + rate-limit | 1 |
| SEC-DASH-05 | someli-dashboard-be | ⚪ Low | G15 | Spurious/deprecated deps | package.json |
Prune unused deps | 1 |
| SEC-ADMINUI-01 | admin_console_R | 🟡 Medium | G5 | Token in localStorage | src/context/AuthContext.tsx:32 |
HttpOnly cookie (backend coord) | 0 |
| SEC-ADMINUI-03 | admin_console_R | 🟡 Medium | G9 | Lovable.dev script in production | index.html:14-15 |
Remove from prod build / SRI-pin | 0 |
| SEC-ADMINUI-04 | admin_console_R | 🟡 Medium | G8 | No Content Security Policy | (no CSP in repo) | Add CSP (nginx/meta); report-only first | 0 |
| SEC-ADMINUI-11 | admin_console_R | 🟡 Medium | G6 | No 401 → logout | src/services/api.ts:5-13 |
Clear session + redirect on 401 | 0 |
| SEC-ADMINUI-02 | admin_console_R | ⚪ Low | G2 | Hardcoded Apptype |
src/services/api.ts:23 |
Use ENV.APP_TYPE |
1 |
| SEC-ADMINUI-05 | admin_console_R | ⚪ Low | G9 | No SRI | index.html |
Resolve via -03; CI grep guard | 1 |
| SEC-ADMINUI-09 | admin_console_R | ⚪ Low | G10 | dangerouslySetInnerHTML unsanitised |
src/components/ui/chart.tsx:79 |
Keep config dev-defined; lint guard | 1 |
| SEC-ADMINUI-10 | admin_console_R | ⚪ Low | G14 | PII in localStorage | AuthContext.tsx:67 |
Source from context; clear on logout | 1 |
| SEC-ADMINUI-06 | admin_console_R | ⚪ Low | — | HMR overlay disabled (dev) | vite.config.ts:18-21 |
Re-enable overlay | 2 |
| SEC-ADMINUI-07 | admin_console_R | ⚪ Low | G15 | lovable-tagger dev plugin |
vite.config.ts:25 |
Keep dev gate; verify not in dist/ |
2 |
| SEC-ADMINUI-08 | admin_console_R | ⚪ Low | — | Minimal Vite fs.deny (dev) |
vite.config.ts:14-17 |
Broaden deny list; localhost only | 2 |
| SEC-DESUI-01 | Someli-Designer | 🟠 High | G4 | Auth guard per-page not global | nuxt.config.js:49-51 |
Global ['auth','axios']; guest opt-out |
0 |
| SEC-DESUI-02 | Someli-Designer | 🟡 Medium | G5 | Token in localStorage/cookie | nuxt.config.js:65-76 |
HttpOnly cookie (backend coord) | 0 |
| SEC-DESUI-03 | Someli-Designer | 🟡 Medium | G6 | No server-side logout | nuxt.config.js:72 |
Add POST /logout; wire $auth.logout() |
0 |
| SEC-DESUI-04 | Someli-Designer | 🟡 Medium | G4 | Role gating UI-only | components/Navbar.vue |
Enforce server-side; FE guard | 1 |
| SEC-DESUI-06 | Someli-Designer | 🟡 Medium | G8 | No CSP (nginx.conf uncommitted) |
Dockerfile:77 |
Commit nginx.conf; add CSP + headers | 0 |
| SEC-DESUI-05 | Someli-Designer | ⚪ Low | G9 | CDN scripts without SRI | nuxt.config.js:16-20 |
Add integrity; self-host |
1 |
| SEC-DESUI-08 | Someli-Designer | ⚪ Low | G2 | Hardcoded Polotno key + localStorage designs | polotno-editor/index.js:7 |
Env var; rotate; scope/clear storage | 1 |
| SEC-DESUI-09 | Someli-Designer | ⚪ Low | G3 | Client-side bcrypt hashing | pages/login.vue:98,156-160 |
Remove; hash server-side; drop dep | 1 |
| SEC-DESUI-10 | Someli-Designer | ⚪ Low | G10 | v-html renders AI/PDF content |
pages/AI-Content.vue:38 +6 |
DOMPurify filter / {{ }} |
1 |
| SEC-DESUI-07 | Someli-Designer | ⚪ Low | — | Bootstrap v2/v5 mismatch (UX) | nuxt.config.js:16-20 |
Pick one Bootstrap line | 1 |
Totals: 80 findings — 3 🔴 Critical, 20 🟠 High, 30 🟡 Medium, 27 ⚪ Low. (A few closely-related issues in someli-api are merged into single findings here — e.g. plaintext password storage + comparison, and the two forgeable-token issues — so this count is slightly lower than an un-deduplicated per-row tally.)
Recurring categories worth grouping into epics: hardcoded secrets [G2] spans 6 repos (and most Phase-0a tickets); missing headers/CSP/SRI [G7/G8/G9] span all front-facing services; SQL injection [G1] and CORS [G11] span the backends; insecure token storage/lifecycle [G5/G6] span every web app. Fixing each category with one shared pattern (then applying per repo) is the efficient path.