Skip to content

Latest commit

 

History

History
109 lines (60 loc) · 23.6 KB

AUDIT.md

File metadata and controls

109 lines (60 loc) · 23.6 KB

CodeSync Production Readiness Audit

Audit date: 2026-08-19

Scope: app/, components/, hooks/, lib/, root server/configuration files, server actions, package.json/lockfile, and all tracked Supabase SQL. This is a static code review; no application code or database state was changed. “Confirmed” means the behavior follows directly from the tracked code. Findings that depend on which SQL files were applied are labeled as such.

Executive summary

This repository is not safe to expose to untrusted users and does not currently implement the real-time collaborative editor described by the README. The most urgent issue is an unauthenticated Socket.IO endpoint that gives any network client control of a host shell. Separately, the editor contains no Yjs integration and never emits or consumes the server's code/cursor events, so concurrent editing is ordinary last-write-wins database saving rather than CRDT collaboration.

The database setup is also not reproducible: multiple mutually incompatible schemas and policy “fix” scripts are committed, including scripts that disable RLS or make all source code globally readable/editable. There is no ordered migration history that establishes one known production state.

Validation performed:

  • npm run build: passes when Google Fonts is reachable.
  • tsc --noEmit: passes.
  • npm run lint: fails before linting because the ESLint 9 CLI no longer accepts the configured --ext option.
  • npm audit --omit=dev: 12 production findings (8 high, 3 moderate, 1 low).
  • npm outdated: core versions are behind current releases; details are prioritized below.
  • Tracked-secret scan: no literal API key, service-role key, private key, password, or token was found. Runtime secrets are nevertheless exposed to the terminal process described below.

Critical

  • Remove or completely isolate the unauthenticated remote-shell endpoint. server.ts:16-24 accepts cross-origin Socket.IO clients with origin: "*"; server.ts:28-119 has no authentication, authorization, origin enforcement, event schema validation, or rate limit. A caller chooses the executable and working directory (server.ts:42-66), the process receives the entire server environment (server.ts:66), and arbitrary input is written to the PTY (server.ts:88-93). This is direct remote code execution under the application OS account and exposes values such as ANTHROPIC_API_KEY and deployment credentials available in process.env. The TODO at server.ts:54-56 explicitly confirms that the claimed directory whitelist does not exist.

  • Fix cross-user terminal takeover and resource exhaustion. Terminal IDs are global, caller-controlled map keys (server.ts:27, server.ts:42-48, server.ts:68). Every browser starts with terminal ID "1" (components/terminal/TerminalManager.tsx:27-35), so unrelated users attach to the same server shell, receive its output, and can send input, resize it, or kill it. Any anonymous client can also spawn unbounded PTYs; disconnect does not terminate them (server.ts:121-123). Dynamic per-terminal event names and unbounded event payloads add further memory/CPU denial-of-service paths.

  • Implement the advertised collaboration system; it is currently absent. No application file imports yjs, y-websocket, y-monaco, or y-protocols. CodeEditor is a controlled Monaco textarea (components/editor/CodeEditor.tsx:133-156), and RoomEditor only performs a debounced Supabase save (components/editor/RoomEditor.tsx:53-77). The client never emits join-room, code-change, or cursor-move, and never listens for code-update or cursor-update; those events exist only as an unused unauthenticated relay in server.ts:33-39 and server.ts:111-119. There is therefore no Y.Doc, awareness/presence, CRDT merge, persistence, reconnection recovery, or Yjs WebSocket server. Concurrent edits overwrite each other.

  • Close the room-membership policy bypass. The intended policies allow any authenticated user to insert a room_members row for themselves in any room, without proving possession of the room code, an invitation, or public-room eligibility (supabase/SETUP.sql:162-170; repeated by supabase/COMPREHENSIVE_RLS_FIX.sql:47-54). Because the anon key is intentionally public, a client can call Supabase directly and bypass joinRoom()'s code lookup. Once inserted, the user satisfies the read/edit policies for that room. Enforce invitation/public-room rules atomically in a restricted database function and revoke direct insert access.

High

  • Replace the contradictory SQL collection with one ordered, idempotent migration chain. supabase/SETUP.sql drops all application tables (:5-17), creates code_sessions.file_name (:67-75) while the application requires name, type, and parent_id, and does not create snippets, the storage bucket, or find_room_by_code. supabase_schema.sql defines different table/column names (chat_messages, message) and a different role model. Ad-hoc scripts repeatedly drop/recreate policies and triggers. It is impossible to infer or reproduce the deployed schema from version control, and applying files in the wrong order can destroy data or silently change the security model.

  • Remove dangerous database scratch scripts from the deployable path. supabase/disable_code_sessions_rls.sql:1-5 disables RLS. supabase_schema.sql:61-74 allows everyone to read all rooms, members, code, and chat, and allows every authenticated user to edit every code session. supabase/check_auth_state.sql:7-10, debug_disable_trigger.sql:4-6, disable_trigger_ver2.sql:4-8, and fix_signup_final.sql:5-24 drop authentication triggers/functions. fix_and_verify_room.sql contains a hard-coded production-looking room UUID and assigns the first auth user as owner. Several of these files are tracked despite .gitignore patterns intended to exclude debug/check scripts.

  • Make the WebSocket layer authenticate every connection and authorize every room/event. Apart from the terminal RCE, any caller can join arbitrary Socket.IO room names and relay arbitrary code/cursor payloads (server.ts:28-39, :111-119). Add Supabase JWT verification during the handshake, membership/role checks for each room, typed and size-bounded event schemas, origin restrictions, per-user/per-IP connection and message limits, room-size limits, and backpressure. Proxy is explicitly excluded from /socket.io, so HTTP route protection does not help (proxy.ts:21-23).

  • Resolve the split development/production server architecture. npm run dev uses the custom Express/Socket.IO server, while npm start runs next start and never executes server.ts (package.json:6-9). Thus terminal and socket features disappear in production. NEXT_PUBLIC_SITE_URL is undocumented and defaults to http://localhost:3000, which makes production browsers connect to their own localhost when unset (components/providers/socket-provider.tsx:28-35). A Vercel deployment cannot host these stateful PTYs. proxy.ts, on the other hand, is the valid Next.js 16 request-boundary convention and is not inherently redundant. Decide on two explicit services (recommended: standard Next.js plus a separately deployed, hardened collaboration service) or consistently build/start a hardened custom server. Next.js also documents that custom servers remove Automatic Static Optimization: Custom Server.

  • Upgrade the vulnerable production dependency graph immediately. The lockfile's npm audit --omit=dev result is 8 high, 3 moderate, and 1 low. Directly relevant issues include multiple Next.js 16.1.6 Proxy bypass, Server Action DoS/SSRF, and WebSocket-upgrade SSRF advisories, plus ws memory-exhaustion and disclosure issues and vulnerable Socket.IO transport/parser dependencies. npm audit offers a non-major Next upgrade; current npm outdated reports Next/eslint-config-next 16.3.1 and ws 8.21.3. At minimum, upgrade and regression-test these before deployment. Examples: Next Proxy bypass, Next custom-server Server Action SSRF, ws memory exhaustion, and Engine.IO connection exhaustion.

  • Protect the Anthropic-backed Server Actions from key abuse. getAICompletion and getInlineSuggestion have no authenticated-user check, authorization, input-size limit, usage quota, concurrency limit, or per-IP/user rate limit (app/actions/ai.ts:10-71). Anyone who can invoke the Server Actions can spend the project's Anthropic balance and submit arbitrarily large code/context. Log safe usage metadata, not prompts/code, and enforce server-side budgets.

  • Update the retired Anthropic models; both AI paths are broken as of the audit date. claude-3-5-sonnet-20241022 was retired on 2025-10-28 and claude-3-5-haiku-20241022 on 2026-02-19, so requests at app/actions/ai.ts:20 and :55 fail. Anthropic's current lifecycle table and replacements are documented in Model deprecations. The SDK is also far behind (0.71.2; npm outdated reports 0.117.1).

  • Fix the FileExplorer request/subscription loop. It creates a new Supabase client on every render (components/editor/FileExplorer/index.tsx:41), includes that identity in loadFiles dependencies (:44-59), and includes both in the effect (:61-84). loadFiles() calls setFiles, causing a render, new client/callback, cleanup, resubscribe, and another fetch. Memoize/singleton the browser client and make effect dependencies stable. RealtimeRoomsListener has the same unstable client pattern (components/rooms/RealtimeRoomsListener.tsx:7-36), though it does not itself set local state on initial subscription.

  • Prevent editor data loss. Clearing a file cannot be saved because handleCodeChange returns when value is an empty string (components/editor/RoomEditor.tsx:53-60). The debounced save marks a file clean in finally even when the save failed (:63-76), falsely telling the user data is persisted. Closing a dirty tab has no confirmation/flush, and there is no unload flush. With no CRDT/version check, stale clients overwrite newer content.

  • Unify file state and constrain every mutation to the actual room. RoomEditor owns an initial/open-file snapshot while FileExplorer independently refetches another file list; rename/delete/remote updates do not reconcile open tabs. Initial files are not filtered by type, so folders can become Monaco tabs (components/editor/RoomEditor.tsx:26-36). saveFileContent checks permission for caller-supplied roomId but updates solely by fileId (actions/file-actions.ts:78-100). Version creation/restoration similarly accepts unrelated roomId, fileId, and versionId values (actions/version-actions.ts:6-37, :61-93). Correct RLS may block some cross-room writes, but the actions themselves do not establish object relationships; a mismatched version row can already be inserted because the foreign keys do not require the version room and file room to match.

  • Make room lifecycle changes transactional and enforce invariants in the database. Room creation and owner membership are separate statements; membership failure is only logged and the user is still redirected (actions/room-actions.ts:20-62). The committed fix_orphaned_rooms.sql demonstrates this has already produced orphaned rooms. leaveRoom allows an owner to remove their own membership (actions/room-actions.ts:107-127), leaving owner_id intact but locking that owner out of RoomPage, which requires membership. Last-owner and role-transition rules exist only in race-prone application checks and can be bypassed via direct Supabase calls.

  • Restrict storage objects by tenant/room and validate uploads. The supposedly private project-files bucket allows every authenticated user to read every object and upload under arbitrary keys (supabase/storage_and_realtime.sql:10-24). ImportFilesDialog uploads source files there, then duplicates their content in code_sessions (components/editor/ImportFilesDialog.tsx:47-95). There are no file count/size/type limits, path ownership checks, content scanning, rollback when database creation fails, or cleanup of orphaned uploads. This is a cross-tenant source-code disclosure if those policies are deployed.

  • Stop publishing users' email addresses globally. Profiles contain email and are selectable by everyone (supabase/SETUP.sql:27-32, :126-131; repeated in migrations/create_profiles.sql). This exposes the user directory through the public Supabase API. Split public display data from private identity data or scope profile reads to relevant room memberships.

Medium

  • Repair linting and make it a required check. npm run lint exits with “Invalid option --ext” under ESLint 9 (package.json:10). The repo has legacy .eslintrc.json, while ESLint 9 expects flat configuration by default. As a result, no lint rules currently run and the many any/suppression/debug patterns receive no automated review.

  • Add tests and CI. There is no test script, test framework/configuration, test/spec file, or tracked CI workflow. Minimum production coverage should include RLS tests executed as anon/user/owner/editor/viewer; Server Action authorization/object-binding tests; WebSocket handshake/event authorization and abuse tests; two-client convergence/reconnect tests for Yjs; room/file/message/version lifecycle tests; and build/type/lint/security checks in CI.

  • Make Proxy failure closed and keep authorization at the data boundary. proxy.ts:5-11 catches every session-refresh error and continues the request. lib/supabase/middleware.ts:13-47 also substitutes a fake client on configuration/runtime failure. Page-level checks reduce exposure on some pages, but a transient auth/config failure must not silently weaken the network boundary. Proxy should remain an optimistic redirect layer; every Server Action, route, Supabase policy, and socket event must independently authenticate and authorize.

  • Validate environment at process/build startup. Supabase URL/key failures are caught and replaced with broad any fake clients in browser, server, and Proxy code (lib/supabase/client.ts:3-40, lib/supabase/server.ts:4-68, lib/supabase/middleware.ts:5-47), masking deployment mistakes as empty data or redirects. NEXT_PUBLIC_SITE_URL and ANTHROPIC_API_KEY are not documented in README. Add a typed server/client environment schema, validate URL formats and required production values once, and fail startup with a clear error. Do not expose server-only values through NEXT_PUBLIC_*.

  • Fix public-room and invitation UX, which is currently nonfunctional. Public room cards link directly to /room/:id, but RoomPage rejects non-members (app/room/[roomId]/page.tsx:20-31); users must separately copy the visible code and use Join. InviteDialog builds /rooms?join=... and offers a selected default role, but the rooms page never reads that query and the role is never submitted (components/permissions/InviteDialog.tsx:33-80). Both Invite and Import dialog state is permanently false with no opener (components/editor/RoomEditor.tsx:39-40, :134-149). The empty-room “Create a Room” event has no listener (components/rooms/RoomList.tsx:69-81).

  • Fix chat real-time edge cases. Typing events are emitted but the server has no typing/stop-typing handlers and no UI consumes them (components/chat/MessageInput.tsx:33-45). “Delete” is a soft-delete UPDATE, but the realtime UPDATE handler merges the row instead of removing rows with deleted_at, so deleted messages remain visible until reload (actions/chat-actions.ts:70-96; components/chat/MessageList.tsx:85-102). Pagination has no error UI, and the Radix ScrollArea ref/onScroll are attached to the Root rather than its scrolling Viewport (components/ui/scroll-area.tsx:8-22), so auto-scroll and bottom detection are unreliable.

  • Make execution rate limiting atomic and fail closed. checkRateLimit counts rows, then executeCode inserts later (actions/execute-actions.ts:36-75, :92-119), so concurrent calls can all pass. Insert errors are ignored and execution proceeds, allowing unlimited execution if the rate-limit table/policy is missing or broken. Add an atomic database function/transaction, check its result, bound code/stdin sizes, validate limit, add an outbound request timeout, and separately rate-limit by user/IP/room.

  • Add framework-native error/loading/not-found states. A client class boundary exists (components/error-boundary.tsx) but there is no App Router error.tsx/global-error.tsx and it cannot catch errors from the parent server layout or all server rendering. Only /rooms has loading.tsx; room/editor/profile/settings/auth routes have no route loading state. Database query errors are frequently ignored and presented as empty data (app/(dashboard)/rooms/page.tsx:20-69, app/room/[roomId]/page.tsx:33-38). There is no deliberate not-found.tsx, retry policy, socket connect_error UI, or production error reporting.

  • Add request/input validation to every Server Action. Most actions accept raw strings/objects without Zod validation or length/shape bounds. Examples include blank/oversized room names and messages, arbitrary roles, unbounded code/stdin/AI prompts, unbounded pagination values, and arbitrary editor metadata. UI validation is bypassable. Database checks are inconsistent across the competing schemas; SETUP.sql, for example, lacks the message length constraint present in create_messages_table.sql.

  • Fix framework/auth route inconsistencies. Unauthenticated RoomsPage redirects to nonexistent /auth (app/(dashboard)/rooms/page.tsx:16-18), and a failed auth callback redirects to nonexistent /auth/auth-code-error (app/auth/callback/route.ts:32). The callback trusts x-forwarded-host in production and accepts an unconstrained next string (:5-27); normalize next to an allowlisted internal path and use a trusted configured origin. Signup collects fullName but discards it (app/(auth)/signup/page.tsx:44-51), and “remember me” is never used.

  • Align React runtime and type packages and stop hiding peer conflicts. Runtime packages are React/React DOM 19.0.0 while @types/react and @types/react-dom are version 18 (package.json:55-57, :73-74). .npmrc sets legacy-peer-deps=true, which can conceal incompatible trees. Current npm outdated reports React 19.2.8 and React type packages 19.x. Upgrade as a tested set with Next and eslint-config-next; do not mix majors. No specific hydration defect was proven in this review, but reactStrictMode: false and root suppressHydrationWarning reduce detection rather than solving mismatches (next.config.ts:3-5, app/layout.tsx:51-54).

  • Add operational controls. There is no graceful shutdown that kills PTYs/closes Socket.IO, health/readiness endpoint, structured logging, request/correlation IDs, metrics, alerting, audit log for membership/role changes, retention/cleanup policy for execution history and uploads, backup/restore procedure, or collaboration-room capacity/rate limit. cleanup_old_rate_limits() is defined but never scheduled (supabase/migrations/create_execution_tables.sql:63-71).

  • Add baseline HTTP security policy. next.config.ts only disables Strict Mode. There is no Content Security Policy, clickjacking protection, Referrer/Permissions policy, explicit HSTS strategy, or protection for remote assets. The application loads texture images from third-party origins in auth/home layouts, leaking client requests and complicating CSP. This is defense-in-depth, but particularly important around auth and user-authored code.

Low

  • Remove tracked scratch/debug artifacts. inspect_exports.js only requires react-resizable-panels and logs its exports. The numerous check_*, debug_*, deep_debug_*, trigger-disable, one-off repair, and hard-coded room SQL files are operational notes/scratch work, not migrations. Move any still-useful diagnostics into documented, non-mutating admin tooling; delete or archive the rest outside the migration path.

  • Prune unreachable/dead UI. Confirmed unreferenced components include components/auth-status.tsx, user-auth-form.tsx, theme-toggle.tsx, landing/Testimonials.tsx, layout/AnimatedBackground.tsx, ui/GlassComponents.tsx, editor/EditorSettings.tsx, editor/LanguageSelector.tsx, and the entire ExecutionPanel chain. The legacy Radix toast/toaster/use-toast chain is also disconnected because the app uses Sonner. ActivityBar exposes history, but SideBar has no matching case and renders blank; search/settings are “coming soon.” Title-bar layout/settings buttons and AI quick-action buttons have no behavior. Decide which features are in scope and remove or wire them.

  • Prune unused or accidentally satisfied dependencies. No code imports cross-env, @radix-ui/react-toggle-group, vaul, direct ws, or any Yjs package; @types/ws is unused. @radix-ui/react-collapsible, hover-card, and toast are only referenced by otherwise unused wrapper files. @types/jszip is redundant with modern JSZip's bundled types. room-actions.ts imports nanoid but does not use it and does not declare it; the import happens to resolve only because a vulnerable Nano ID 3.x is hoisted transitively through PostCSS (actions/room-actions.ts:6-9). Remove the import or declare a current direct dependency if genuinely needed.

  • Reduce dependency drift deliberately rather than bulk-upgrading. npm outdated reports many stale packages, notably Supabase JS 2.93.3 → 2.112.3, Supabase SSR 0.8.0 → 0.12.4, Anthropic SDK 0.71.2 → 0.117.1, Next 16.1.6 → 16.3.1, and React 19.0.0 → 19.2.8. Upgrade security/framework/auth packages first with focused tests; major upgrades such as Tailwind 3 → 4, ESLint 9 → 10, Xterm 5 → 6, and Framer Motion 12 → 13 require explicit migration work.

  • Make builds hermetic. app/layout.tsx uses two next/font/google families despite bundling local Geist fonts. A clean production build fails when fonts.googleapis.com is unavailable and succeeds when network access is granted. Self-host the required font files or explicitly guarantee/cache build-time network access.

  • Clean up misleading debug and product copy. Production code contains many console.log/console.debug calls for socket IDs, room IDs, database realtime payloads, and terminal process details. The terminal labels an unauthenticated connection “Secure Link”; the panel displays hard-coded “Database connected”/“Workspace hydrated successfully”; README claims Yjs, multi-cursor presence, zero latency, and extreme robustness that the implementation does not provide. Logs should be structured and redacted, and product documentation should match deployed behavior.

Recommended remediation order

  1. Disable network access to the custom Socket.IO/PTY server; rotate any secrets that may have been exposed through it.
  2. Establish one canonical Supabase migration state, test every RLS policy as each role, and close self-join/storage/profile leaks.
  3. Upgrade vulnerable Next/Socket.IO/WebSocket dependencies and protect all Server Actions, especially AI.
  4. Choose and implement the production collaboration architecture (authenticated Yjs service plus persistence/rate limits), then align production start/deployment scripts.
  5. Fix data-loss/state bugs and broken room/invite/chat/AI flows.
  6. Add CI, tests, framework-native failure states, observability, and dependency maintenance.