Codebase Assessment / Confidential
Expert review of three repositories ahead of engineering handover
build/ directories, and ignored lockfiles. Reproducibility and secret hygiene are broken platform-wide.Express · Mongoose 8 · TypeDI · Bull · ~71,000 LOC TypeScript
Functional, but high-risk. An early-prototype-grade codebase running in production and handling real money. The skeleton of a sound design exists — layered structure, dependency injection, a job queue — but the discipline that makes 71K LOC safe is absent: no tests, type-safety effectively off, no verification on the payment flow, and weak multi-tenant isolation. The payment and data-isolation defects are exploitable today.
Success callbacks accept unauthenticated, unsigned, client-supplied JSON and mark fees paid. No reverse-hash verification, no server-side call to PayU; the paymentVerify function exists but is never called. Anyone can POST a fake success body and get a receipt with no money received.
routes/payUBiz.ts · controllers/payUBiz.ts · services/payu.ts
The recorded amount comes straight from the forgeable callback; the only guard is a weak lower bound, not equality against a gateway-confirmed amount. No txnid uniqueness check, so a callback can be replayed.
controllers/feePayment.ts:401–419
POST /api/auth/register is unauthenticated and accepts an arbitrary role — anyone can register as SuperAdmin. The /permission routes including _create are also unauthenticated.
routes/auth.ts · routes/permission.ts
Many by-id update/delete routes query by { _id } only, never scoped to schoolId — one school can modify another's records by guessing ObjectIds. List endpoints pass req.query.filter straight into Mongoose (operator injection, $where).
routes/routes.ts · controllers/routes.ts · routes/student.ts
forgotPassword returns the reset token in the response; new users get a password equal to their mobile number; a hardcoded PayU key and static hash are committed. Passwords and tokens are logged.
controllers/auth.ts · controllers/student.ts · controllers/schoolRequest.ts
Zero tests across 71K LOC including the fee and payment logic, with no test script wired up. Every change is blind.
Bulk fee payment re-runs calculateFee (6+ queries each) per month inside a per-student loop — roughly O(M²); a 40-student upload can fire tens of thousands of queries in one request. .lean() is used exactly once and autopopulate is on by default (15-collection fan-out). List endpoints have no enforced pagination; attendance bulk-create has a fire-and-forget N+1 that is also a data-race bug.
studentFeeBulkPayment.ts · business/feePayment.ts · studentAttendance.ts
No strict in tsconfig; dev runs ts-node --transpile-only; ~1,600 @ts-ignore and ~1,200 eslint-disable. The compiler — the only free safety net on untested code — is muted.
The compiled build/ (502 files) is committed while package-lock.json is gitignored — non-reproducible installs and stale shipped artifacts.
No CI, no Dockerfile, no graceful shutdown, no unhandledRejection handlers. Deploys kill in-flight payment requests; an unhandled rejection crashes silently.
The business layer imports up into controllers and routes; res is passed deep into business logic (there is a createMockRes hack), making it untestable and risking "headers already sent".
res.json(...).status(200) appears 162 times — .status() after .json() is a no-op, so 201/4xx codes are sent as 200.
Three competing response shapes; central error handler does not log and returns raw error text to clients.
~70 stray console.logs, ~2,300 lines of commented-out code, dead built-in imports, and typos baked into DI tokens (feeStrucuture, penality). An abandoned V2 refactor coexists with V1.
txnid idempotency./auth/register and /permission/*; never accept role from unauthenticated input.schoolId scoping on all by-id read/update/delete; whitelist query-filter keys.package-lock.json; stop committing build/.Mongoose 8 · TypeDI · express-jwt · AWS SDK · 115 models · ~5,200 LOC
Functional, but high-risk for a trust anchor. This library defines the data models, auth, and config that every service depends on — its defects propagate everywhere. JWT verification is configured correctly (the one bright spot). But it commits live secrets into source and a compiled build/, defines zero database indexes across 115 models, enables ref auto-population by default (the root cause of the consumers' fan-out), and has no versioning, tests, or type declarations.
Consumed by other services via a raw git reference to master. With the version frozen at 1.0.0 and no tags, every consumer silently tracks the latest commit and inherits breaking changes with no warning and no rollback.
The merchant key and salt — the secret that signs payments and verifies callbacks — are hardcoded and also committed compiled. With the salt an attacker can forge valid payment hashes. Config already reads these from env, so the hardcoded copies are redundant and dangerous. Rotate the salt with PayU immediately.
services/payUHash.ts:3-4 · build/services/payUHash.js
A SuperAdmin user with a committed bcrypt hash is created unconditionally whenever the model is imported — a crackable, public, full-privilege backdoor provisioned in any consumer's database.
models/user.ts:197-198, 219
Not a single compound index exists. The consumers' hot query path (filter by schoolId/classId/sectionId/studentId/month, aggregate dues) runs as a full collection scan every time. This is the primary cause of the backend's slow queries.
models/feePayment.ts · studentAttendance.ts · carryForward.ts
autopopulate: true appears 251 times across 83 of 115 models, and populated docs themselves auto-populate — a single read explodes into a recursive cascade across 15+ collections, and blocks lean reads and aggregation.
models/student.ts:98-99 · examSchedule.ts:13-19
The 492-file build/ (with source maps) is tracked and is the distribution channel. Every secret above is re-published as readable JS, and 84% of commits are build noise. A single bad hand-committed build reaches all consumers.
build/ (492 files) · .gitignore
Version frozen at 1.0.0, zero git tags, no changelog. Consumers can only reference a branch or SHA, so any model change is an invisible, simultaneous breaking change to all services.
declaration is off and no .d.ts ship. Consumers import compiled JS with no types — defeating the library's main purpose.
The shared filter middleware builds unbounded regexes from raw input with $regex allowed and only the top-level operator validated — catastrophic-backtracking DoS and operator injection, for every consumer.
middlewares/getFilters.ts:45-65 · config/index.ts:254
Job dashboard ships with agendash / 123456; SES identities default to a personal Gmail. Anyone reaching the dashboard controls background jobs.
config/index.ts:64-66, 238-244
No tests gate the build that reaches every consumer. Loaders connect once with no pooling, timeouts, error handlers, retry, or graceful shutdown.
loaders/mongoose.ts · loaders/agenda.ts
113 hand-maintained interfaces mirror 115 schemas; six models have none, and existing ones already mistype fields and omit enum values. The TS contract no longer matches runtime documents.
interfaces/IFeePayment.ts:13-25
A dead oldclasssubjectMapping.ts registers the same model name as the live file; uncommenting or importing it crashes every consumer at boot.
A unary-plus concatenation bug corrupts the hash string; the function returns the hash object instead of the hex digest, and logs the salt-bearing string.
{ String } shorthand defines empty subdocuments, and schemaless [] arrays become untyped, unindexable Mixed types.
package-lock.json gitignored; TS strict off; config throws on a missing .env at import — a surprising side effect in containerized consumers.
build/; commit package-lock.json.$regex, cap length, validate the full shape.React 16 · CRA 3.4.3 · Redux · ~94,500 LOC · 406 files · forked Vuexy template
Functional, but operationally fragile. An admin template forked and grown into a large app without re-architecting — a working "big ball of mud." It almost certainly will not build on a modern Node version, installs are non-reproducible, it has effectively zero tests, and authorization is entirely client-side. One real strength: route-based code-splitting is done thoroughly — don't touch it. The work is in tooling, security, data fetching, and breaking up the giant components.
The toolchain is end-of-life: react-scripts 3.4.3 and node-sass 4.14 do not build cleanly on the installed Node 20, and both lockfiles are gitignored — a fresh clone cannot reliably be run without workarounds. This blocks everything else.
node-sass 4.14 only ships bindings for Node ≤14; react-scripts 3.4.3 uses a webpack 4 stack that breaks on Node 17+. On Node 20 a fresh clone cannot start without workarounds.
package.json · runtime Node v20.14.0
Both package-lock.json and yarn.lock are gitignored, and install:clean deletes the lockfile. Every install floats to the newest matching versions — different trees per machine.
.gitignore · package.json
The school screen holds the merchant key and salt in state, renders the salt in a plaintext input populated from the API, and submits it back. The salt signs payments — returning it to the browser exposes it to any admin and any XSS.
components/school/ManageSchool.js:68-77, 1118-1129
Route and role gating is driven by jwt-decode, which performs no signature verification. The SuperAdmin/SubAdmin boundary is cosmetic — a user can edit decoded permissions or call privileged endpoints directly. Safe only if the backend re-validates every role.
src/router.js:145-158 · utils/localStorageConstants.js
Only the default App.test.js exists across ~94,500 LOC, and it would itself fail. Any change is unverifiable, which makes the Node upgrade and every refactor blind.
45 endpoints hardcode a "fetch everything" parameter, so every list view downloads the full collection and paginates in JS. A school with thousands of students transfers megabytes per mount. Server-side pagination scaffolding already exists, unused.
components/API.js · components/dataTable/DataTable.js:48
The session JWT is in localStorage (XSS-stealable). The .env is committed with a Google Maps key, and history leaks backend origin IPs. Move the token to an HttpOnly cookie; rotate and restrict the key.
views/authentication/Login.js:37 · .env
axios 0.19.2 (SSRF/ReDoS), xlsx/xlsx-style (prototype pollution, parses untrusted uploads), prismjs 1.20, node-sass 4, and the react-scripts 3.4.3 tree carry known advisories.
Raw fetch with no status check — 401s pass as success, no central logout. 183 of 192 catch blocks only console.log; failed saves leave users with no feedback.
components/API.js:10-21
16 files exceed 800 lines, 42 exceed 500. ViewSchool.js is a single 2,600-line class with ~22 state fields and 13 API calls — untestable, regression-prone.
views/superAdmin/schoolSection/ViewSchool.js
The main table rebuilds two hidden copies of the whole dataset on every render. ag-grid-enterprise, html2canvas, and jspdf are imported into common chunks; a 4 MB logo is bundled into three dashboards.
components/clientTable/ClientDataTable.js:7-8, 2046
The login form ships with a real-looking mobile number pre-filled as both username and password.
views/authentication/Login.js:17-18
Material-UI and Ant Design ship in full but are used in ~12 and ~5 files for trivial widgets reactstrap already provides. jQuery and dom7 are pulled in only by accidental unused imports.
The route switch has no fallback — any unknown URL renders a blank screen with no 404 page.
The boundary renders a spinner as its fallback (infinite loading on crash) and is mounted below the layout, so layout/store errors escape it.
Domain data never enters Redux and is re-fetched on every mount. Auth state is split across two slices; a duplicate store config has a broken import; the login thunk is a commented-out shell.
node-sass to sass; add .nvmrc and engines; get a clean build on a pinned Node.package-lock.json; stop deleting it.