# Phase 6 - Security Audit
## Executive Summary
The application has a solid baseline in two important areas: server-side authorization is used broadly across route handlers, and many sensitive workflow actions are audited. The main security risks are not widespread missing auth checks, but a smaller set of high-impact gaps in file delivery, request hardening, dependency posture, and abuse protection.
The most serious application-layer issue is direct public exposure of uploaded online-lesson assets. Uploaded video, attachment, and thumbnail files are stored under `public/uploads/...`, serialized back to clients as direct public URLs, and rendered directly in the UI. This bypasses the guarded download pattern that already exists in other parts of the system.
The second major risk is the import surface. Both spreadsheet import endpoints accept files based only on extension presence, read the entire file into memory, and rely on `xlsx`, which `npm audit` currently reports with high-severity advisories. Combined with the absence of rate limiting and explicit file-size controls on import endpoints, this increases the risk of denial-of-service and unsafe spreadsheet parsing.
## Threat Surface Inventory
Primary security-sensitive surfaces reviewed:
- Auth.js credential login flow
- Session and active-organization switching
- Server-side authorization helpers
- Permission-management APIs
- Announcement, online-lesson, and certificate file handling
- Training-record and employee spreadsheet imports
- Report export endpoints
- Notification and workflow action endpoints
- Next.js runtime/configuration and proxy perimeter
## Authentication Assessment
### Strengths
- `src/auth.ts:25-33` validates login input and uses bcrypt password verification.
- `src/auth.ts:57,81,103,137` records login success and failure events in audit logs.
- Self-service registration is disabled in `src/app/api/auth/register/route.ts`.
- Session enforcement is centralized through `requireSession()`.
### Findings
- `src/auth.ts:33` falls back to `dev-only-auth-secret-change-me` outside production. This is acceptable for local development but risky if a non-production environment is internet-exposed with weak secret hygiene.
- `src/auth.ts:36` sets `trustHost: true`. This is common in proxied deployments, but it raises deployment sensitivity if host/header forwarding is not tightly controlled upstream.
- No brute-force controls, lockout logic, or request throttling were found around credential login.
- Password policy is minimal: sign-in and auth forms enforce only `min(8)`, and `src/features/users/schemas/user.ts` does not enforce password strength itself.
### Assessment
Authentication is functionally correct, but it lacks abuse resistance and stronger operational guardrails.
## Authorization Assessment
### Strengths
- Broad route coverage uses server-side guards such as `requireOrganizationAccess`, `requirePermission`, `requireHRD`, `requireAllPermissions`, and `requireSuperAdminOrganizationAccess`.
- Permission-management endpoints are protected at the route-handler level even though they are not included in the proxy perimeter list.
- Download routes for announcement attachments and training-record certificates enforce organization-aware access checks before reading files from disk.
### Findings
- `src/proxy.ts:4-18,41-56` only protects a subset of API prefixes at the perimeter. Notably, online-lesson and permission-management APIs are not listed there, even though their handlers do perform server-side checks. This is a defense-in-depth gap rather than a direct bypass.
- `src/lib/auth/session.ts` and `src/app/api/organizations/active/route.ts` can auto-create admin memberships for super-admin/HRD organization access. This may be intended, but it is a privileged side effect inside an access path and should be treated carefully.
- Permission-management routes use `requireSuperAdminOrganizationAccess()`, which currently depends on the same organization-access helper that can create fallback memberships.
### Assessment
Authorization is stronger than average for an admin dashboard. The main risk is not broken route protection by default, but privileged side effects embedded inside access-resolution flows.
## Input Validation Assessment
### Strengths
- Core workflow routes use Zod or equivalent server-side validation.
- Online-lesson URLs are constrained to `http/https` via schema validation.
- Route params are commonly parsed and rejected when numeric IDs are invalid.
### Findings
- `src/app/api/training-records/import/route.ts:228-236` and `src/app/api/import-employees/route.ts:460-474` only verify that the upload is a `File` and that the name ends with `.xlsx`; no explicit file-size or MIME validation is enforced before parsing.
- Import endpoints load the full spreadsheet with `await file.arrayBuffer()` before further validation.
- Several workflow routes perform large amounts of procedural validation inline inside route handlers, which makes consistency harder to maintain.
### Assessment
Input validation is decent for routine CRUD, but weaker on large-file and batch-import surfaces.
## Injection Assessment
### Strengths
- Most database access uses Drizzle query builders and parameterized expressions.
- No command-execution patterns or child-process usage were found in application runtime paths.
- Storage helpers sanitize path segments and verify resolved paths stay under upload roots.
### Findings
- `npm audit` reports a high-severity advisory for `drizzle-orm` regarding SQL injection via improperly escaped SQL identifiers in versions `<0.45.2`.
- `src/features/reports/server/report-data.ts:446-555` uses raw SQL composition for reporting. The visible usage appears parameterized, but this area should be considered high-scrutiny because it is the most SQL-heavy path in the app.
- `npm audit` reports high-severity advisories for `xlsx`, which is used to parse untrusted spreadsheet uploads.
- CSV export exists in `src/app/api/audit-logs/export/route.ts`; this phase did not fully verify whether cells beginning with spreadsheet formula prefixes are neutralized.
### Assessment
There is no clear app-authored SQL injection bug in the reviewed code, but dependency-level injection risk is real and currently unresolved.
## XSS Assessment
### Strengths
- Announcement content, review comments, and online-lesson descriptions are rendered as plain text with `whitespace-pre-wrap`, not injected HTML.
- No user-content Markdown or rich-text renderer was found in the reviewed content workflows.
- The single `dangerouslySetInnerHTML` usage in `src/components/ui/chart.tsx` is used to emit generated CSS variables, not user-provided rich text.
### Findings
- Online-lesson external video URLs are rendered into outbound links and embed decisions. The code restricts embeddable iframes to recognized YouTube hosts, which is good, but general outbound URLs are still allowed for non-embeddable links.
- No repo-level Content Security Policy was found in `next.config.ts` or related runtime config.
### Assessment
Direct stored-XSS exposure appears low in the reviewed flows. The larger XSS concern is missing browser-side hardening such as CSP.
## CSRF & Request Protection Assessment
### Strengths
- State-changing operations generally use non-GET methods.
- Auth is session-based and routed through Auth.js rather than custom ad hoc cookies.
### Findings
- No explicit CSRF token validation, origin checks, or referer checks were found in state-changing route handlers.
- No centralized request-hardening middleware was found for mutation endpoints.
- Because cookie behavior is library-managed, SameSite/secure cookie posture was not explicitly verified from local code.
### Assessment
This is primarily a hardening gap, not a confirmed exploit in the reviewed code. Still, the app currently relies heavily on framework defaults and browser cookie behavior rather than explicit anti-CSRF controls.
## File Upload Assessment
### High-risk finding
- `src/lib/online-lesson-storage.ts:36-86` writes uploaded lesson assets to `public/uploads/online-lessons/...` and returns direct public URLs.
- `src/features/online-lessons/server/online-lesson-data.ts:51,53,55` serializes those direct file URLs to clients unchanged.
- `src/features/online-lessons/components/online-lesson-detail-page.tsx:198-200,284-290,328-336` renders those URLs directly in `
`, `