Files
MIS-Contro-Tower/security_risks.md
2026-04-22 05:04:19 +00:00

114 lines
4.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Security Risks Review (mis-control-tower)
This review focuses on the risks highlighted in the tweet you shared: backend-first architecture, trusting the client, column/role escalation, and missing rate limits.
Good news: this project is backend-first and uses Prisma on the server, not direct-to-DB from the client. Prisma itself is not the core risk here. The main issues are around authorization scope, secret handling, and rate limiting.
## Critical: Cross-org reminder trigger + weak auth fallback
### What can go wrong
Any logged-in user can trigger the reminders job if the secret is not set, and the job queries across all orgs. This can spam email reminders to users in other orgs.
### Source
- Auth fallback to "any session" when secret missing: `app/api/downtime/actions/reminders/route.ts:40`
- Cross-org query (no `orgId` filter): `app/api/downtime/actions/reminders/route.ts:62`
### Why this maps to the tweet
This is a classic "missing backend guardrails" and "rate limits/abuse" problem.
### Fix ideas
- Require `DOWNTIME_ACTION_REMINDER_SECRET` in all environments (fail closed if missing).
- If you want session-based access, also require:
- role check (OWNER/ADMIN), and
- explicit `orgId` scoping in the `findMany` query.
- Consider also logging who triggered it.
---
## High: Invite token exposure + invite claim risk
### What can go wrong
A regular member can retrieve active invite tokens and then accept invites intended for other people.
### Source
- Members GET has no role check: `app/api/org/members/route.ts:23`
- Members GET returns raw invite tokens: `app/api/org/members/route.ts:52`
- Accepting an invite creates a user for the invite email and marks it verified based only on the token: `app/api/invites/[token]/route.ts:93`, `app/api/invites/[token]/route.ts:98`
### Why this maps to the tweet
This is a "hidden columns / privilege escalation" flavor of bug: sensitive fields (tokens) are being exposed to users who should not see them.
### Fix ideas
- Add a role check to `GET /api/org/members` (OWNER/ADMIN only).
- Do not return invite tokens from the API (or only return to OWNER/ADMIN).
- Optional hardening:
- Bind invites more tightly to identity (e.g., require proof of email ownership), or
- require the invite acceptance flow to complete a verification step before granting access.
---
## Medium: Pairing code brute force path to machine API keys
### What can go wrong
Pairing codes are short and the pairing endpoint returns the machine API key. Without rate limiting, attackers can attempt many codes and occasionally succeed.
### Source
- Pairing codes length = 5: `lib/pairingCode.ts:5`
- Pair endpoint returns `apiKey`: `app/api/machines/pair/route.ts:56`
### Why this maps to the tweet
This aligns with "rate limits are not optional anymore" and "dont trust defaults."
### Fix ideas
- Add rate limiting to `/api/machines/pair` (by IP and/or code prefix).
- Increase pairing code entropy (length and/or attempt tracking).
- Track failed attempts and temporarily disable pairing for a machine after too many failures.
---
## Medium: Missing rate limiting on high-abuse endpoints
### What can go wrong
Attackers can brute-force or abuse endpoints to consume resources and/or trigger unwanted actions.
### Source (representative endpoints)
- Login: `app/api/login/route.ts:20`
- Signup: `app/api/signup/route.ts:26`
- Pairing: `app/api/machines/pair/route.ts:12`
- Ingest: `app/api/ingest/kpi/route.ts:35`, `app/api/ingest/heartbeat/route.ts:33`, `app/api/ingest/event/route.ts:60`, `app/api/ingest/reason/route.ts:11`
### Why this maps to the tweet
This is directly item #10 in the tweet: rate limits at auth, API routes, and webhooks/ingest.
### Fix ideas
- Apply rate limiting to:
- auth endpoints (`/api/login`, `/api/signup`, invite acceptance),
- pairing (`/api/machines/pair`),
- ingest endpoints (especially if publicly reachable).
- Even a simple KV-based limiter or middleware-based limiter is a large improvement.
---
## Not the core risk: Prisma usage
### Observation
This project uses Prisma server-side via Next.js route handlers and server components. I did not see direct DB calls from the browser.
### Source (representative)
- Session enforcement in API routes: `lib/auth/requireSession.ts:42`
- Server-side data access in routes: `app/api/machines/route.ts:31`, `app/api/settings/route.ts:146`
### Why this matters
This avoids the tweets main direct-to-DB + RLS pitfalls, but you still need strong authorization and rate limiting in your own backend.
---
## Quick fix priority order
1. Lock down `POST /api/downtime/actions/reminders` (fail closed + org scoping).
2. Lock down `GET /api/org/members` and stop exposing invite tokens.
3. Add rate limiting to pairing and auth endpoints.
4. Consider increasing pairing code entropy + attempt tracking.
If you want, I can implement the first two fixes in small, safe patches.