ucode-agent 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,31 +2,49 @@
2
2
 
3
3
  A coding agent that lives in your terminal. It reads your code, edits it, runs
4
4
  your commands, and keeps every conversation on disk. It runs on NVIDIA and
5
- Cohere models through OpenRouter, all of them free.
5
+ Cohere models, all of them free.
6
+
7
+ It opens on a quiet screen — the name, the place to type, and the version in the
8
+ corner:
9
+
10
+ ```
11
+ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗
12
+ ██║ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝
13
+ ██║ ██║██║ ██║ ██║██║ ██║█████╗
14
+ ██║ ██║██║ ██║ ██║██║ ██║██╔══╝
15
+ ╚██████╔╝╚██████╗╚██████╔╝██████╔╝███████╗
16
+ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
17
+
18
+
19
+ ╭──────────────────────────────────────────────────────────────────────────────╮
20
+ │ › Ask anything… │
21
+ │ │
22
+ │ ◆ Build · Nemotron 3 Ultra 0% │
23
+ ╰──────────────────────────────────────────────────────────────────────────────╯
24
+
25
+
26
+ v1.2.0
27
+ ```
28
+
29
+ and once you are talking, each message you send is boxed in the same blue as
30
+ the input, so your own words are easy to find in a long session:
6
31
 
7
32
  ```
8
33
  ╭──────────────────────────────────────────────────────────────────────────────────╮
9
- ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ dir ~/projects/notes-app
10
- │ ██║ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝ keys /help · esc interrupts │
11
- │ ██║ ██║██║ ██║ ██║██║ ██║█████╗ │
12
- │ ██║ ██║██║ ██║ ██║██║ ██║██╔══╝ │
13
- │ ╚██████╔╝╚██████╗╚██████╔╝██████╔╝███████╗ │
14
- │ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ made with ❤️ by om dixit │
34
+ build a notes dashboard
15
35
  ╰──────────────────────────────────────────────────────────────────────────────────╯
16
-
17
36
  ● Writing index.html
18
37
  └ created · 148 lines
19
38
  1 + <!doctype html>
20
39
  2 + <html lang="en">
21
- 3 + <head>
22
- 145 more lines
23
- Running npm test
24
- └ exit 0 · 12 lines
40
+ 146 more lines
41
+ Running npm run dev
42
+ ready · http://localhost:3000 · PID 4812
25
43
 
26
44
  ╭──────────────────────────────────────────────────────────────────────────────────╮
27
45
  │ › now add a dark mode toggle │
28
46
  │ │
29
- │ ◆ Build · Nemotron 3 Ultra (free) 4% │
47
+ │ ◆ Build · Nemotron 3 Ultra 4% │
30
48
  ╰──────────────────────────────────────────────────────────────────────────────────╯
31
49
  ```
32
50
 
@@ -47,7 +65,7 @@ Then put a key where it will survive upgrades:
47
65
 
48
66
  ```bash
49
67
  mkdir -p ~/.ucode
50
- echo "OPENROUTER_API_KEY=sk-or-..." > ~/.ucode/.env
68
+ echo "UCODE_API_KEY=sk-or-..." > ~/.ucode/.env
51
69
  ```
52
70
 
53
71
  Keys are free at [openrouter.ai/keys](https://openrouter.ai/keys). A `.env` in
@@ -65,7 +83,7 @@ Needs Node 22 or newer.
65
83
  ## The models
66
84
 
67
85
  Five, and no picker full of names nobody recognises. NVIDIA and Cohere both
68
- serve capable models free through OpenRouter, and both handle tool calling
86
+ serve capable models free, and both handle tool calling
69
87
  properly, which is the thing an agent actually depends on.
70
88
 
71
89
  | Model | Context | For |
@@ -128,10 +146,15 @@ when it is wanted, so the prompt stays the same size however many you add.
128
146
  | `debug` | finding the real cause instead of the first plausible one |
129
147
  | `code-review` | reviewing a change the way a careful colleague would |
130
148
  | `write-tests` | tests that fail for the right reason |
131
-
132
- **`ui-ux` loads itself.** Ask for an app, a dashboard, a landing page, or say
133
- the UI is ugly, and the whole skill is in context before the model takes its
134
- first step. Waiting for the model to decide it needs design guidance means
149
+ | `ai-features` | model-backed features: prompts with rules, validated JSON, images, failure handling |
150
+ | `security` | secrets, auth, ownership checks, injection, XSS, CSRF, SSRF, uploads |
151
+ | `performance` | measure first, find the real bottleneck, prove the win with numbers |
152
+ | `refactor` | change the shape of code without changing what it does |
153
+
154
+ **Every skill loads itself** when the request calls for it — an app pulls in
155
+ `ui-ux` and `build-app`, "it crashes" pulls in `debug`, an AI feature pulls in
156
+ `ai-features`, an API key pulls in `security` — so the whole skill is in
157
+ context before the model takes its first step. Waiting for the model to decide it needs design guidance means
135
158
  finding out it did not after the app is built.
136
159
 
137
160
  Add your own in `.ucode/skills/<name>/SKILL.md` inside a project. A project
@@ -183,7 +206,7 @@ ucode [options]
183
206
 
184
207
  | | |
185
208
  | --- | --- |
186
- | `~/.ucode/.env` | `OPENROUTER_API_KEY`, and `TAVILY_API_KEY` for web search |
209
+ | `~/.ucode/.env` | `UCODE_API_KEY`, and `TAVILY_API_KEY` for web search |
187
210
  | `~/.ucode/sessions/` | one JSON per conversation |
188
211
  | `.ucode/skills/` | skills belonging to a project |
189
212
 
@@ -199,7 +222,7 @@ one, ucode answers from what it knows and says that it could not check.
199
222
  ```
200
223
  ucode.js the command: arguments in, Agent out
201
224
  src/core/loop.js the agent loop, the system prompt, the slash commands
202
- src/core/provider.js the only file that knows OpenRouter exists
225
+ src/core/provider.js the only file that knows which provider answers
203
226
  src/core/history.js sessions on disk
204
227
  src/core/window.js folding a long conversation to fit
205
228
  src/core/skills.js loading skills, and deciding which load themselves
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ucode-agent",
3
- "version": "1.1.0",
4
- "description": "ucode - a terminal coding agent that reads, edits and runs your code. NVIDIA and Cohere models over OpenRouter.",
3
+ "version": "1.2.0",
4
+ "description": "ucode - a terminal coding agent that reads, edits and runs your code, on NVIDIA and Cohere models.",
5
5
  "type": "module",
6
6
  "main": "ucode.js",
7
7
  "bin": {
@@ -39,8 +39,7 @@
39
39
  "llm",
40
40
  "nvidia",
41
41
  "nemotron",
42
- "cohere",
43
- "openrouter"
42
+ "cohere"
44
43
  ],
45
44
  "author": "om dixit",
46
45
  "license": "ISC",
@@ -0,0 +1,140 @@
1
+ ---
2
+ name: ai-features
3
+ description: Build features on top of language and vision models that behave like product, not demos — prompts with explicit rules, structured output validated in code, images, streaming, timeouts, retries, cost and failure handling.
4
+ auto: ai, llm, llms, gpt, chatgpt, claude, gemini, nemotron, chatbot, chat bot, embeddings, rag, vision model, image recognition, ocr, structured output, ai feature, ai-powered, ai powered, prompt engineering, analyze image, analyse image, reads the image, read the label
5
+ ---
6
+
7
+ # AI features
8
+
9
+ A model call that works once in a demo is easy. A feature that gives a
10
+ consistent, correct, well-formatted answer every time, fails gracefully, and
11
+ does not leak a key is the actual job. Treat the model as an unreliable
12
+ upstream service with a very good API.
13
+
14
+ ## 1. Architecture
15
+
16
+ - **Server only.** The call and the key live in a server route or server action.
17
+ The browser sends the input to your route; your route calls the model.
18
+ - **One module per model integration** (`lib/server/analyze.ts`): builds the
19
+ prompt, calls the API, parses and validates the reply, returns a typed result
20
+ or a typed error. Routes and UI never touch raw model output.
21
+ - **Types first.** Define the result with zod before writing the prompt. The
22
+ schema is the contract the prompt has to satisfy.
23
+
24
+ ```ts
25
+ const Finding = z.object({
26
+ nutrient: z.string(),
27
+ amount: z.string(), // "820 mg", as printed
28
+ severity: z.enum(['high', 'low']),
29
+ why: z.string().max(160),
30
+ });
31
+ export const Analysis = z.object({
32
+ isNutritionLabel: z.boolean(),
33
+ score: z.number().min(1).max(10),
34
+ summary: z.string().max(400),
35
+ findings: z.array(Finding).max(8),
36
+ });
37
+ ```
38
+
39
+ ## 2. Prompts that produce the same answer twice
40
+
41
+ - **Role and task in the first line**, then the rules, then the output format.
42
+ - **Make every judgement explicit.** "Flag sodium if it is over 20% of daily
43
+ value per serving (≈460 mg)" is reproducible; "flag unhealthy things" is not.
44
+ Write the thresholds, the scale anchors ("10 = whole food with no concerns,
45
+ 5 = fine occasionally, 1 = mostly sugar or salt"), and what to leave out
46
+ ("if no nutrient crosses a threshold, return an empty findings array — never
47
+ pad it").
48
+ - **Handle the wrong input inside the prompt too**: "If the image is not a
49
+ nutrition facts label, set isNutritionLabel to false and leave the rest empty."
50
+ - **Specify the JSON exactly** — field names, types, units, lengths — and say
51
+ "Reply with the JSON object only. No prose, no code fences."
52
+ - **Put the unchanging instructions in the system message** and the per-request
53
+ input in the user message.
54
+ - Temperature 0–0.3 for extraction and scoring; higher only for creative text.
55
+
56
+ ## 3. Calling the API
57
+
58
+ OpenAI-compatible chat completions (most providers, including model routers):
59
+
60
+ ```ts
61
+ const res = await fetch(`${BASE_URL}/chat/completions`, {
62
+ method: 'POST',
63
+ headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
64
+ body: JSON.stringify({
65
+ model: MODEL_ID,
66
+ temperature: 0.2,
67
+ messages: [
68
+ { role: 'system', content: SYSTEM_PROMPT },
69
+ { role: 'user', content: [
70
+ { type: 'text', text: 'Analyse this label.' },
71
+ { type: 'image_url', image_url: { url: dataUrl } },
72
+ ] },
73
+ ],
74
+ }),
75
+ signal: AbortSignal.timeout(90_000),
76
+ });
77
+ if (!res.ok) throw new ModelError(res.status, await res.text());
78
+ const text = (await res.json()).choices?.[0]?.message?.content ?? '';
79
+ ```
80
+
81
+ - **Timeout on every call.** Reasoning models can think for 30–60s; set the route's
82
+ own limit (`export const maxDuration = 90` in Next.js) above the call's.
83
+ - **Retry only what is safe to retry**: 429 (after `retry-after`), 5xx, network
84
+ errors, timeouts — at most 2 retries with backoff. Never retry a 400 or 401.
85
+ - **Map errors to user language** at the boundary: rate limited → "busy, try
86
+ again in a moment"; timeout → "took too long"; invalid output → "could not
87
+ read the result"; not a label → "that does not look like a nutrition label".
88
+
89
+ ## 4. Parsing model output defensively
90
+
91
+ Models add prose, wrap JSON in fences, use trailing commas, or return numbers
92
+ as strings. Never `JSON.parse` the raw text directly.
93
+
94
+ ```ts
95
+ function extractJson(text: string): unknown {
96
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1];
97
+ const raw = fenced ?? text.slice(text.indexOf('{'), text.lastIndexOf('}') + 1);
98
+ return JSON.parse(raw);
99
+ }
100
+ const parsed = Analysis.safeParse(extractJson(text));
101
+ if (!parsed.success) throw new ModelError(422, 'unreadable model output');
102
+ ```
103
+
104
+ Then **enforce the rules in code as well**: clamp the score, drop findings that
105
+ do not meet the threshold, cap list lengths. The prompt asks; the code
106
+ guarantees.
107
+
108
+ ## 5. Images
109
+
110
+ - Accept jpeg, png, webp. Check type and size on the client before upload.
111
+ - Downscale in a canvas to ~1600px on the long edge and re-encode as JPEG at
112
+ 0.85 — faster upload, fewer tokens, same accuracy for text in the image.
113
+ - Send as a `data:image/jpeg;base64,...` URL. Handle HEIC from phones by
114
+ telling the user to export as JPEG if the browser cannot decode it.
115
+ - Check that the model you are calling accepts images at all before building
116
+ on it.
117
+
118
+ ## 6. The experience around the call
119
+
120
+ - A real loading state with words ("Reading the label…"), elapsed time if it can
121
+ exceed ~5s, and a way to cancel (`AbortController`).
122
+ - Stream text responses when the output is prose, so something appears at once.
123
+ For structured results, show a skeleton of the result shape instead.
124
+ - Show the result with its reasoning visible (the summary, the findings), not
125
+ just a number.
126
+ - Keep the user's input on failure so "try again" is one click.
127
+
128
+ ## 7. Cost, privacy and abuse
129
+
130
+ - Log the model, latency, token usage and outcome — never the key, and never
131
+ the user's content unless they agreed to it.
132
+ - Limit input size and request rate per user on public endpoints.
133
+ - Cache results for identical inputs where it is safe (hash of the image).
134
+
135
+ ## 8. Verify with real inputs
136
+
137
+ Test with at least: a clear typical input, a hard one (blurry photo, cropped
138
+ label), a wrong one (a photo of a cat), and an edge (a food that genuinely has
139
+ no concerns — the findings list must come back empty). Report what each
140
+ returned.
@@ -28,7 +28,8 @@ One line each:
28
28
  - **The file list** — the whole tree, before creating any of it.
29
29
 
30
30
  If there is a user interface, the `ui-ux` skill is already loaded. Decide the
31
- design direction now, not after the logic works.
31
+ design direction now, not after the logic works. If the app calls a model,
32
+ load `ai-features`; if it has accounts, keys or uploads, load `security`.
32
33
 
33
34
  ## 2. Scaffold without being asked questions
34
35
 
@@ -89,7 +90,7 @@ src/
89
90
  of the response status, and an error that says what failed — surfaced to the
90
91
  UI as a real message, never a silent `catch {}`.
91
92
 
92
- ### Calling an AI model (OpenRouter or any OpenAI-compatible API)
93
+ ### Calling an AI model (any OpenAI-compatible API)
93
94
 
94
95
  ```ts
95
96
  // src/app/api/analyze/route.ts — runs on the server only
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: performance
3
+ description: Make software measurably faster — measure first, find the real bottleneck, fix it, and prove the improvement with numbers. Covers web vitals, bundles, React rendering, APIs, databases and Node.
4
+ auto: slow, slower, performance, perf, optimize, optimise, optimization, speed up, faster, lag, laggy, sluggish, bundle size, lighthouse, core web vitals, web vitals, lcp, cls, inp, memory leak, re-render, rerender, n+1, latency, takes too long
5
+ ---
6
+
7
+ # Performance
8
+
9
+ Guessing at performance is how people spend a day optimizing something that
10
+ was never slow. Measure, change one thing, measure again.
11
+
12
+ ## 1. Measure before touching anything
13
+
14
+ - **Define the slow thing precisely**: which page, action or endpoint, how slow
15
+ now, how fast it needs to be.
16
+ - **Get a baseline number** you can re-run:
17
+ - Web page: Lighthouse / PageSpeed (LCP, INP, CLS, total JS), the browser
18
+ Performance panel, `next build` output (per-route JS size).
19
+ - API: time the request (`curl -w "%{time_total}\n"`), log durations per step.
20
+ - Node/Python: a profiler (`node --cpu-prof`, `clinic`, `py-spy`), or timers
21
+ around the suspect code.
22
+ - Database: `EXPLAIN ANALYZE` on the slow query.
23
+ - Measure the production build, not dev mode — dev is deliberately slow.
24
+
25
+ ## 2. Find the actual bottleneck
26
+
27
+ It is almost always one of these, in roughly this order of likelihood:
28
+
29
+ 1. **Network waterfalls** — requests that wait on each other when they could run
30
+ in parallel; data fetched on the client that could be fetched on the server.
31
+ 2. **Too much JavaScript** — heavy dependencies, everything marked
32
+ `"use client"`, no code splitting.
33
+ 3. **Unoptimized images and fonts** — huge images, no dimensions (layout shift),
34
+ blocking font loads.
35
+ 4. **Database** — N+1 queries, missing indexes, fetching whole tables, no
36
+ pagination.
37
+ 5. **Rendering** — React re-rendering large trees on every keystroke, expensive
38
+ work inside render, long lists without virtualization.
39
+ 6. **Algorithmic** — nested loops over large data, repeated work that could be
40
+ cached or computed once.
41
+
42
+ ## 3. Fixes by area
43
+
44
+ **Web (Next.js / React)**
45
+ - Server components by default; `"use client"` at the leaves only.
46
+ - `next/image` with explicit sizes; `priority` on the LCP image; modern formats.
47
+ - `next/font` with `display: swap`, only the weights used.
48
+ - Dynamic `import()` for heavy, below-the-fold or rarely used components.
49
+ - Replace heavy libraries (moment → date-fns/Intl, lodash → native, big chart
50
+ libs → lighter ones) and check the per-route JS in the build output.
51
+ - Parallelize independent fetches with `Promise.all`; stream with `Suspense`.
52
+ - Cache: static where possible, `revalidate` for data that changes slowly.
53
+
54
+ **React rendering**
55
+ - Keep state as low in the tree as possible; lift only what must be shared.
56
+ - Stable props: memoize expensive values and callbacks passed to memoized
57
+ children — but only where the profiler shows a real cost.
58
+ - Virtualize lists over a few hundred rows.
59
+ - Debounce input-driven work (search, validation) at ~200–300ms.
60
+
61
+ **APIs and databases**
62
+ - Index columns used in `WHERE`, `JOIN` and `ORDER BY`; confirm with `EXPLAIN`.
63
+ - Batch or join instead of querying in a loop (N+1).
64
+ - Select only the columns needed; paginate everything user-sized.
65
+ - Cache expensive, repeatable results (in memory, Redis, HTTP caching) with a
66
+ clear invalidation rule.
67
+ - Move slow non-essential work (emails, analytics, thumbnails) to a background
68
+ job.
69
+
70
+ **Node**
71
+ - Never block the event loop with sync I/O or heavy CPU in a request handler.
72
+ - Stream large files instead of reading them whole.
73
+ - Reuse clients and connections (DB pools, HTTP keep-alive).
74
+
75
+ ## 4. Change one thing at a time, and prove it
76
+
77
+ After each change, re-run the same measurement. Keep changes that move the
78
+ number; revert ones that do not — complexity without a measured win is a cost.
79
+
80
+ ## 5. Report with numbers
81
+
82
+ Before and after for each metric that changed ("LCP 4.1s → 1.6s, route JS
83
+ 312 kB → 148 kB"), what caused it, and anything left that would need a bigger
84
+ change to fix.
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: refactor
3
+ description: Restructure code without changing what it does — a safety net first, small verified steps, clear boundaries and names, and no behaviour change slipped in along the way.
4
+ auto: refactor, refactoring, clean up, cleanup, clean this, restructure, reorganize, reorganise, simplify, tech debt, technical debt, extract, split this file, split up, dead code, duplicate code, duplication, messy code, spaghetti
5
+ ---
6
+
7
+ # Refactoring
8
+
9
+ A refactor changes the shape of code and nothing else. The moment behaviour
10
+ changes, it is a rewrite, and a rewrite hidden inside a refactor is how
11
+ regressions ship with nobody noticing.
12
+
13
+ ## 1. Know why, and where it stops
14
+
15
+ - State the goal in one line: *split the 700-line page into components*,
16
+ *remove the duplicated fetch logic*, *make the scoring rules testable*.
17
+ - State the boundary: which files are in scope. Resist improving everything you
18
+ pass on the way — note it and leave it.
19
+
20
+ ## 2. Build the safety net first
21
+
22
+ - Run the existing tests and record the result. If there are none around the
23
+ code you are changing, **write characterization tests first**: tests that pin
24
+ down what the code does now, including its odd behaviour. You are preserving
25
+ behaviour, so you must be able to detect when it changes.
26
+ - For UI with no tests, capture the current behaviour: what renders in each
27
+ state, what each control does.
28
+
29
+ ## 3. Read it all before moving anything
30
+
31
+ Read every file in scope in full, and find every caller of what you will change
32
+ (`grep` for the names, the imports, the routes). A rename that misses one
33
+ dynamic reference is a runtime error waiting for the one path nobody tested.
34
+
35
+ ## 4. Small steps, each one green
36
+
37
+ Do one kind of change at a time, and run the tests after each:
38
+
39
+ - **Rename** to say what things are: `data` → `analysis`, `handle()` →
40
+ `submitLabel()`. Names are most of readability.
41
+ - **Extract** a function or component for each distinct job; a function should
42
+ do one thing at one level of abstraction.
43
+ - **Move** code next to what uses it: feature folders over type folders.
44
+ - **Remove duplication** only when the copies really are the same concept —
45
+ two similar-looking pieces that change for different reasons should stay two.
46
+ - **Delete dead code** — unused exports, unreachable branches, commented-out
47
+ blocks. Confirm it is unused with a search first.
48
+ - **Simplify conditionals**: early returns over nesting, lookup tables over long
49
+ `if/else` chains, named booleans over complex expressions.
50
+ - **Push side effects to the edges**: pure logic in the middle (easy to test),
51
+ I/O at the boundary.
52
+
53
+ Use `multi_edit` for several changes in one file, and keep each step small
54
+ enough that a failing test points straight at the cause.
55
+
56
+ ## 5. Keep behaviour identical
57
+
58
+ - Same inputs, same outputs, same errors, same side effects, same order of
59
+ side effects.
60
+ - Public APIs and stored formats unchanged, or every caller and every stored
61
+ record updated in the same change.
62
+ - If you find a bug while refactoring, **do not fix it silently inside the
63
+ refactor**. Finish the refactor, then fix the bug as its own change — or
64
+ report it — so each can be reviewed and reverted on its own.
65
+
66
+ ## 6. Finish
67
+
68
+ - All tests pass, the build passes, the linter and type checker are clean.
69
+ - The code is measurably simpler: fewer lines, fewer branches, smaller files,
70
+ clearer names — say which.
71
+ - Report what moved where, anything you noticed but deliberately left alone,
72
+ and any behaviour you had to pin down with new tests.
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: security
3
+ description: Build and audit software so it is safe by default — secrets, authentication and authorization, input validation, injection, XSS, CSRF, SSRF, uploads, dependencies and headers, checked against how attacks actually happen.
4
+ auto: security, secure, vulnerability, vulnerabilities, auth, authentication, authorization, login, sign in, signup, sign up, password, passwords, jwt, oauth, session cookie, xss, csrf, ssrf, sql injection, injection, secrets, api key, api keys, owasp, harden, hardening, permissions, rate limit
5
+ ---
6
+
7
+ # Security
8
+
9
+ Assume every input is hostile and every secret will be looked for. Most real
10
+ breaches come from a short list of boring mistakes; this skill is that list,
11
+ with what to do about each.
12
+
13
+ ## 1. Secrets
14
+
15
+ - **Never in the browser.** Anything imported by client code ships to every
16
+ visitor — including a "temporary" hardcoded key. Keys live in server routes,
17
+ server actions or server-only modules (`import 'server-only'`).
18
+ - **Never in the repository.** Use `.env.local` / `.env` (gitignored) and commit
19
+ a `.env.example` with placeholders. If a key was ever committed or pasted
20
+ somewhere public, rotate it — deleting the line does not un-leak it.
21
+ - **Never in logs, errors or URLs.** Redact before logging; do not put tokens
22
+ in query strings.
23
+ - If the user insists on hardcoding a key for now, put it in one server-only
24
+ file, say exactly where it is, and recommend moving it to an env var.
25
+
26
+ ## 2. Authentication
27
+
28
+ - Use a proven library or provider (Auth.js/NextAuth, Clerk, Supabase Auth,
29
+ Lucia-style patterns) rather than hand-rolled sessions and hashing.
30
+ - Passwords: argon2id or bcrypt, never reversible, never logged. Rate-limit
31
+ login and reset endpoints. Same error message for "no such user" and "wrong
32
+ password".
33
+ - Sessions: `HttpOnly`, `Secure`, `SameSite=Lax` (or `Strict`) cookies. Rotate
34
+ the session on login. Expire idle sessions.
35
+ - JWTs: short expiry, verify signature and algorithm server-side, never trust
36
+ claims the client can edit.
37
+
38
+ ## 3. Authorization — the most common real hole
39
+
40
+ - **Check ownership on every request**, on the server, for every object:
41
+ `WHERE id = $1 AND user_id = $session.user`. Changing an ID in a URL or body
42
+ must never reveal someone else's data (IDOR).
43
+ - Deny by default. Every route states who may call it.
44
+ - Hiding a button is not authorization; the endpoint behind it must check too.
45
+
46
+ ## 4. Input validation and injection
47
+
48
+ - Validate every input at the boundary with a schema (zod, pydantic): type,
49
+ length, range, format. Reject, do not "clean".
50
+ - **SQL:** parameterized queries or an ORM only. Never string-build SQL with
51
+ user input.
52
+ - **Shell:** avoid it; if unavoidable, pass arguments as an array
53
+ (`execFile`, `spawn` without `shell: true`), never interpolate.
54
+ - **Paths:** resolve and verify the result stays inside the allowed directory;
55
+ reject `..` and absolute paths from users.
56
+ - **SSRF:** when fetching a user-supplied URL, allow-list hosts, block private
57
+ and link-local ranges (127.0.0.0/8, 10/8, 172.16/12, 192.168/16, 169.254/16,
58
+ ::1), and do not follow redirects blindly.
59
+
60
+ ## 5. Output: XSS
61
+
62
+ - Let the framework escape (React, templating engines). Treat
63
+ `dangerouslySetInnerHTML`, `innerHTML`, `v-html` and markdown-to-HTML as
64
+ red flags: sanitize with DOMPurify if you must render HTML.
65
+ - Never put user input into `href` without checking the scheme
66
+ (`javascript:` URLs), or into inline `<script>` or event handlers.
67
+ - Set a Content-Security-Policy where you can.
68
+
69
+ ## 6. Requests
70
+
71
+ - **CSRF:** SameSite cookies plus a CSRF token or origin check on state-changing
72
+ requests that use cookie auth. Next.js server actions check origin; custom
73
+ routes need it done.
74
+ - **CORS:** never `*` with credentials; allow-list the origins that need it.
75
+ - **Rate limiting** on login, signup, password reset, and any expensive or paid
76
+ endpoint (AI calls especially) — per IP and per user.
77
+
78
+ ## 7. File uploads
79
+
80
+ - Check type by content (magic bytes), not just the extension or MIME header.
81
+ - Enforce a size limit on the server, not only the client.
82
+ - Store outside the web root or in object storage with generated names; never
83
+ execute or serve uploads from the app's own origin as HTML.
84
+ - Strip metadata (EXIF location) from images when privacy matters.
85
+
86
+ ## 8. Errors, headers, dependencies
87
+
88
+ - Errors to users are generic; details go to server logs. No stack traces in
89
+ responses.
90
+ - Headers: `Strict-Transport-Security`, `X-Content-Type-Options: nosniff`,
91
+ `Referrer-Policy: strict-origin-when-cross-origin`, `frame-ancestors` via CSP.
92
+ - Dependencies: `npm audit` (or the ecosystem equivalent), remove unused
93
+ packages, pin versions with a lockfile, be wary of new packages with few
94
+ downloads or typo-like names.
95
+
96
+ ## 9. Auditing existing code
97
+
98
+ Search rather than read everything:
99
+
100
+ ```
101
+ grep for: api[_-]?key|secret|token|password (hardcoded secrets)
102
+ dangerouslySetInnerHTML|innerHTML|eval\(|new Function
103
+ exec\(|execSync|shell: true|child_process
104
+ \$\{.*\}.*(SELECT|INSERT|UPDATE|DELETE) (string-built SQL)
105
+ fetch\(.*req\.|axios\(.*req\. (user-controlled URLs)
106
+ ```
107
+
108
+ Then check every route for authentication and ownership checks. Report
109
+ findings ranked by severity with the file, the attack, and the fix — and fix
110
+ the critical ones first if asked to fix.