Date: 2026-06-29 Repo: SenoaMonoRepo (branch development) Scope: Full monorepo — backend, blockchain indexer, web, admin-panel, infra/CI
Senoa is a Web3 social + marketplace platform built as a Turbo/PNPM monorepo. It comprises a large NestJS backend (42 modules, crypto custody, KYC/KYB, accounting, affiliate/MLM rewards), a Next.js 15 web client, a Vite/React admin panel, and a NestJS blockchain events indexer.
The architecture is reasonable and the code is consistently structured, but the project is not production-ready. The dominant themes are:
localStorage, a hardcoded Alchemy RPC key, hardcoded DB/JWT secrets in docker-compose.yml, default Swagger password, no rate-limiting or security headers.1.00.Overall production-readiness: ~70%. Risk level: HIGH.
| App | Stack | Files | Tests |
|---|---|---|---|
apps/backend | NestJS 9, TypeORM, Postgres, Passport/JWT, 124 migrations | 625 | 6 (trivial) |
apps/web | Next.js 15, React 19, thirdweb/viem, TanStack Query, Zustand, socket.io | 280 | 0 |
apps/admin-panel | Vite 7, React 19, CoreUI, Redux Toolkit, RR7 | 62 | 1 |
apps/blockchain | NestJS 9, ethers v5, Bull/Redis, Postgres | 14 | 1 (failing) |
packages/* | shared eslint-config, tsconfig | — | — |
Tooling: Turbo + PNPM 10.15, TypeScript, Tailwind, Docker, AWS Elastic Beanstalk (GitHub Actions deploy).
Repo-wide metrics: 37 TODO/FIXME, 264 console.* in TS files, 208 remote branches (significant branch sprawl).
apps/blockchain/src/app.service.ts:31
new ethers.providers.JsonRpcProvider(
`https://polygon-amoy.g.alchemy.com/v2/fBeW2OzQkFjcaNQVXzqx5rDBeO_AwMFO`)
Live key in source + git history. Rotate immediately, move to process.env.
docker-compose.ymlDB passwords (P71\Q4;jlpyJ, jRw0hf56E6x') and JWT_SECRET=your-super-secret-jwt-key-change-in-production are committed. Also scripts/init.sql ships appuser/rootpassword. Move to secrets/.env (prod already uses AWS SSM Parameter Store via the EB prebuild hook — good — but compose does not).
apps/backend/src/main.ts:91-96 and gateway/chat.gateway.ts use origin: "*" with credentials: true. CSRF / cross-origin credential exposure risk. Pin to explicit origins.
Backend 6 trivial .spec files (0.97%), web 0, admin 1, blockchain 1 (and that e2e test asserts the wrong string, so it fails). No tests on auth, custody, accounting, KYC/KYB, or rewards.
accounting.service.ts:655,752,877 — non-USDT exchangeRate hardcoded to "1.00" (TODO: price oracle). All non-stablecoin ledger values are wrong.circle.service.ts:1297-1346 — reward distribution has TODO: [DB INSERT] placeholders; rewards are computed but never recorded. Users would not be paid.localStorage (web + admin)43 reads in web, plus admin panel. XSS-exfiltratable. Client-side-only token expiry (token-init-date) is trivially bypassed. Move to httpOnly cookies; rely on server 401s for expiry.
| # | Area | Finding | Location |
|---|---|---|---|
| H1 | Web | XSS via dangerouslySetInnerHTML with regex-only sanitization (4 sites) | ProfilePreview.tsx:70, AboutUsEditor.tsx:298, etc. |
| H2 | Backend | Default Swagger password swagger@123 fallback | main.ts:47 |
| H3 | Backend | No rate-limiting (@nestjs/throttler) and no security headers (helmet) anywhere | global |
| H4 | Backend | S3 uploads use raw filename (collision/overwrite), no virus scan, 24h presigned URLs | aws.service.ts, upload.service.ts |
| H5 | Backend | Password-reset token stored in plaintext in DB | auth.service.ts:862 |
| H6 | Backend | Individual KYC is minimal — no workflow, no status, no audit trail (KYB is robust) | kyc/ |
| H7 | Blockchain | No reorg protection, no block-confirmation wait, no idempotency, attempts:1 (no retries) → events silently lost/duplicated | app.service.ts, erc20transfer.processor.ts |
| H8 | Admin | Role checks are client-side only (Redux/localStorage); no CSRF protection | ProtectedRouteByRole.js |
| H9 | All | Oversized files: web settings/page.tsx (3139), profile/[id]/page.tsx (2540); backend posts.service.ts (2256), circle.service.ts (1911); admin ContentCreators.js (930) | — |
web and admin-panel ("lint": "echo skipped"); blockchain too. ESLint configs exist but never run.strict:false, noUnusedLocals:false) and blockchain (strictNullChecks:false, noImplicitAny:false). Web is strict.any usages** in web reduce type safety (esp. error handling).localhost / empty password — won't fail fast in prod.API_BASE_URL duplicated** ~58× in web with http://localhost:3000 fallback baked into production component files.node:latest (non-reproducible builds); no .nvmrc/engines despite CI pinning Node 22.npm test. Deploys to prod/dev on push with npm install (not npm ci).1768461271858-auto.ts** is an auto-generated schema sync — review for unintended changes..gitignore** lists !.env.example but never ignores .env itself (real .env files are not gitignored — currently none committed, but the guard is missing).ValidationPipe with whitelist+forbidNonWhitelisted; 385 class-validator decorators across 173 DTOs.crypto.randomBytes(32) reset tokens, AES encryption of custody API keys via CUSTODY_ENCRYPTION_KEY.Immediate (hours):
docker-compose.yml / init.sql (C2).Short term (1–2 weeks):
helmet + @nestjs/throttler (H3); hash reset tokens (H5); UUID-prefix S3 uploads (H4).dangerouslySetInnerHTML with DOMPurify (H1).Medium term (1–2 months):
API_BASE_URL/avatar-URL utilities; add KYC workflow + audit trail (H6).| App | Architecture | Security | Tests | Production-ready? |
|---|---|---|---|---|
| backend | Good | High risk (CORS, uploads, reset tokens) | Critically low | ~70% |
| web | Good | High risk (localStorage JWT, XSS) | None | No |
| admin-panel | Good | High risk (client-side roles, no CSRF) | Minimal | No |
| blockchain | PoC | Critical (hardcoded key, no reorg/idempotency) | Broken | PoC only |
Bottom line: solid foundation and architecture, but treat as a late-stage prototype. Address the six critical items before exposing this to real users or funds.
NestJS backend (apps/backend). Each node is a feature module; arrows show **imports** dependencies declared in each *.module.ts (A --> B = A imports B).
AppModule is the composition root and imports all 40 feature modules (edges omitted for clarity). Several edges are mutual (forwardRef) — e.g. auth ⇄ user, user ⇄ notification.
| Domain | Modules |
|---|---|
| Identity & Access | auth, user, user-placement |
| Compliance | kyc, kyb |
| Content & Social | posts, feed, content-creator, recommended, ad, project-creation |
| Business & Marketplace | business, business-pages, listings, storefronts |
| Messaging & Notifications | chats, messages, notification, notifications |
| Web3 & Finance | custody, accounting, transak, xrp-swap, crypto-prices, pledge |
| Rewards / MLM / Passes | circle, affiliate, commission-structure, reward, advocacy, pass, user-pass |
| Shared Infrastructure | common, response-helper, upload, avatar, mail, dynamic-compression, search |
| Dev / Seed | seed |
Note:crypto-prices,feed,recommended,commission-structure,notificationsare leaf modules (no inter-moduleimports); they are consumed by others and registered directly inAppModule.
For a per-module breakdown (each module + its connections), switch to the Architecture Explorer.
Generated from the 66 TypeORM entities in apps/backend/src. Boxes are tables (with columns; PK = primary key, FK = foreign-key column). Relationship lines reflect the actual relation decorators:
| Notation | Decorator | Meaning |
|---|---|---|
| `A | --o{ B` | |
| `A | -- | |
A }o--o{ B | @ManyToMany | many-to-many (join table) |
Labels has 1 / has 2 … distinguish multiple FKs from the same table to the same target (e.g. Post → User author vs. editor; UserConnection → User requester vs. addressee).
User is the central hub. 11 standalone tables have no entity-level relations (config/log/derived data): CryptoPrice, DynamicCompression, ErrorLog, FeeConfiguration, Feed, MediaAsset, MonthlyPool, MonthlyPoolDistribution, Pledge, Recommended, WalletBalance.
For a per-table breakdown (each table + its relations), switch to the Architecture Explorer.