diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index bffb357a7..000000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "next/core-web-vitals"
-}
diff --git a/SECURITY_REVIEW.md b/SECURITY_REVIEW.md
new file mode 100644
index 000000000..12b7b6a32
--- /dev/null
+++ b/SECURITY_REVIEW.md
@@ -0,0 +1,240 @@
+# Security Vulnerability Review - LlamaCoder
+
+**Repository:** Nutlope/llamacoder
+**Reviewer:** Automated Security Review
+**Date:** 2026-05-01
+**Branch:** orchestrator/security-vulnerability-review-jx71ja9p
+
+---
+
+## Executive Summary
+
+Three exploitable vulnerabilities were identified. The most critical is an unauthenticated S3 upload endpoint that allows arbitrary file uploads. A medium-severity SSRF vector exists via the screenshot URL parameter. A medium-severity prompt injection / stored XSS risk exists through AI-generated markdown rendered on public share pages.
+
+---
+
+## Finding 1: Unauthenticated S3 File Upload (HIGH)
+
+**Severity:** HIGH
+**Type:** Broken Access Control / Unrestricted File Upload
+**File:** `app/api/s3-upload/route.ts`
+
+### Attack Path
+
+```
+Attacker → POST /api/s3-upload (no auth) → Presigned S3 URL → Upload arbitrary file to bucket
+```
+
+### Evidence
+
+`app/api/s3-upload/route.ts:1-2`:
+```ts
+// app/api/s3-upload/route.js
+export { POST } from "next-s3-upload/route";
+```
+
+The S3 upload route is a bare re-export of `next-s3-upload`'s route handler with zero authentication or authorization checks. The client-side usage in `app/(main)/page.tsx:56` calls `useS3Upload()` directly:
+
+```ts
+const { uploadToS3 } = useS3Upload();
+// ...
+const { url } = await uploadToS3(file);
+```
+
+There is no middleware, session check, or API key validation protecting this endpoint.
+
+### Exploitation
+
+Any unauthenticated user (or scripted attacker) can:
+1. Call `POST /api/s3-upload` to obtain a presigned upload URL
+2. PUT any file content to that URL (SVG with embedded JS, HTML files, executables)
+3. If the S3 bucket has public read access, the uploaded file is accessible at a predictable URL
+
+### Impact
+- **Storage abuse / Denial of Wallet:** Unlimited file uploads at the owner's expense
+- **XSS via uploaded files:** If the bucket serves files publicly, an attacker can upload an SVG or HTML file with embedded JavaScript and serve it from the same domain context
+- **Malware distribution:** Bucket used as hosting for malicious files
+
+### Why Existing Defenses Fail
+- No authentication middleware on the route
+- `next-s3-upload` by default does not require auth — it only generates presigned URLs
+- No file type validation server-side (client-side `accept="image/png, image/jpeg, image/webp"` in `page.tsx:382` is trivially bypassed)
+- No file content scanning
+
+---
+
+## Finding 2: SSRF via Screenshot URL Parameter (MEDIUM)
+
+**Severity:** MEDIUM
+**Type:** Server-Side Request Forgery (SSRF)
+**File:** `app/api/create-chat/route.ts`
+
+### Attack Path
+
+```
+Attacker → POST /api/create-chat with malicious screenshotUrl → Together AI fetches URL → Internal network access
+```
+
+### Evidence
+
+`app/api/create-chat/route.ts:13,62-89`:
+```ts
+const { prompt, model, quality, screenshotUrl } = await request.json();
+// ...
+if (screenshotUrl) {
+ const screenshotResponse = await together.chat.completions.create({
+ // ...
+ messages: [
+ {
+ role: "user",
+ content: [
+ { type: "text", text: screenshotToCodePrompt },
+ {
+ type: "image_url",
+ image_url: {
+ url: screenshotUrl, // ← User-controlled URL passed directly
+ },
+ },
+ ],
+ },
+ ],
+ });
+}
+```
+
+The `screenshotUrl` field comes directly from the JSON body of the POST request with no validation, allowlisting, or URL scheme restriction. It is passed to the Together AI API's `image_url` parameter, which causes the AI provider's servers to fetch the provided URL.
+
+### Exploitation
+
+An attacker can supply URLs targeting:
+- Cloud metadata endpoints: `http://169.254.169.254/latest/meta-data/` (AWS), `http://metadata.google.internal/` (GCP)
+- Internal services: `http://localhost:6379`, `http://10.0.0.5:8080`
+- File protocol (if supported): `file:///etc/passwd`
+
+The AI provider (Together AI) acts as the requestor, so the attacker can probe internal network services accessible from Together AI's infrastructure.
+
+### Why Existing Defenses Fail
+- No URL validation (no scheme check, no hostname allowlist, no IP range exclusion)
+- No input sanitization on `screenshotUrl`
+- The client-side file upload flow sets `screenshotUrl` to an S3 URL returned by `uploadToS3`, but the API accepts any string
+
+### Mitigating Factors
+- The actual fetch is performed by Together AI's servers, not the application server, limiting direct internal network access to the app's own infrastructure
+- The response from the URL is consumed by an AI vision model, not returned directly to the attacker, so classic SSRF data exfiltration is limited but not impossible (the AI could be prompted to describe sensitive content)
+
+---
+
+## Finding 3: Prompt Injection Leading to Stored XSS on Share Pages (MEDIUM)
+
+**Severity:** MEDIUM
+**Type:** Prompt Injection / Stored XSS
+**Files:** `app/share/v2/[messageId]/page.tsx`, `app/(main)/chats/[id]/chat-log.tsx`
+
+### Attack Path
+
+```
+Attacker → Craft malicious prompt → AI generates malicious markdown/HTML → Content stored in DB → Rendered on public share page via Streamdown
+```
+
+### Evidence
+
+The share page at `app/share/v2/[messageId]/page.tsx:50` extracts code blocks from a message and renders them:
+
+```tsx
+const files = extractAllCodeBlocks(message.content);
+```
+
+The chat log at `app/(main)/chats/[id]/chat-log.tsx` uses `Streamdown` to render AI-generated markdown:
+
+```tsx
+{seg.content}
+```
+
+`Streamdown` (v2.1.0 per package.json) by default includes `rehype-raw` which processes raw HTML in markdown, and uses `rehype-sanitize` with a schema that allows many HTML tags. The AI-generated content stored in `Message.content` flows through this renderer.
+
+### Exploitation
+
+An attacker crafts a prompt that uses prompt injection to cause the AI to generate markdown containing HTML that survives sanitization. For example:
+
+1. The prompt asks the AI to "output the following HTML as part of your response"
+2. The AI includes HTML in its markdown response
+3. `Streamdown` with `rehype-raw` processes the HTML
+4. While `rehype-sanitize` strips `