Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/LANDING-PAGE-PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ You can adapt this; the important part is that **brand = `^_`**, **loading = bli
1. **Hero**
- Main headline (e.g. “Your messenger. In the terminal.” or “1:1 chat for devs and agents.”).
- Subhead: one-liner (minimal agent-to-agent 1:1 DM, terminal-first, API-backed).
- Logo: `agentchat ^_` (and/or standalone `^_`).
- **Navbar (in hero):** Logo `agentchat ^_` — full name and face in the first hero section.
- **Navbar (after scroll):** When the navbar detaches and sticks/follows on scroll, show **only** `^_` (no "agentchat" text). Keeps the bar minimal while scrolling.
- Optional: very short loading moment with `^_` ↔ `^*` blink before hero content appears.
- Primary CTA: e.g. “Get started” or “Run the API” (link to repo or docs).

Expand All @@ -133,7 +134,7 @@ You can adapt this; the important part is that **brand = `^_`**, **loading = bli
- Short line about “developer messenger” and “homely”: e.g. “No enterprise bloat. Just you, the terminal, and the people you chat with.”

6. **Footer**
- Links: GitHub, docs, API reference (if you have URLs). Optional: “agentchat ^_” again. Keep it minimal.
- Links: GitHub, docs, API reference (if you have URLs). Use **only** `^_` in the footer (no "agentchat" text). Keep it minimal.

---

Expand Down
112 changes: 112 additions & 0 deletions docs/PRODUCTION-READINESS-PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Production readiness plan (from SECURITY-AND-SCALE)

## Current state

- **Already done**: Token secret fail-fast in production, input limits (username/password/message), rate limit 200/min, security headers, XSS sanitization in chat, Docker non-root, systemd with `NODE_ENV` and token-secret comment.
- **Gaps**: Login lacks same validation as register; `/users` unbounded; auth-specific rate limit may not apply (per-route config); no CORS/CSP/audit logging; Docker and hosting docs don't enforce checklist.

---

## 1. Checklist items (code)

### 1.1 Login validation (match register)

- **File**: `apps/api/src/index.ts` — `/auth/login` handler (around line 152).
- **Change**: Before `login(username, password)`, validate with same rules as register: `String(username).trim()` length ≤ `MAX_USERNAME_LEN`; `String(password)` length ≤ `MAX_PASSWORD_LEN`. Return `400` with a generic message (e.g. "Invalid request") for invalid length so we don't do Argon2 on huge passwords and we keep behavior consistent with register.

### 1.2 Auth-specific rate limit (guarantee 10/min)

- **File**: `apps/api/src/index.ts`.
- **Change**: The current `onRoute` sets `opts.config.rateLimit`; not all Fastify rate-limit plugins honor per-route config. To be sure without depending on plugin internals:
- Add a small in-memory store (e.g. `Map<string, { count: number; resetAt: number }>`) keyed by IP.
- Add a `preHandler` on `/auth/login` and `/auth/register` that:
- Gets IP from `request.ip` (or `request.headers['x-forwarded-for']` if behind proxy).
- Enforces 10 requests per minute per key; if over limit, reply `429` and return.
- Keep the global `@fastify/rate-limit` (200/min). Remove the `onRoute` block and the long comment block (lines 41–58) to avoid confusion.

### 1.3 `/users` cap (scale safeguard)

- **File**: `apps/api/src/index.ts` — `GET /users` handler.
- **Change**: Cap the returned list (e.g. first 2000 usernames). In `packages/core` either add a `listUsers(limit?: number)` or cap in the API: `listUsers().map(...).slice(0, 2000)`. Document in `docs/SECURITY-AND-SCALE.md` that at scale you should add pagination/search.

---

## 2. Optional hardening (from doc)

### 2.1 CORS (env-driven)

- **File**: `apps/api/src/index.ts`.
- **Change**: Register `@fastify/cors` when `CORS_ORIGIN` (or `CORS_ORIGINS`) is set. Allow only that origin(s). If unset, skip CORS (same-origin). Add dependency `@fastify/cors` in `apps/api/package.json`.

### 2.2 CSP for web chat

- **File**: `apps/api/src/index.ts`.
- **Change**: In the handlers for `GET /` and `GET /chat`, set a `Content-Security-Policy` header before sending `chatHtml`. Policy: allow `self`; scripts from `self` and `https://cdn.jsdelivr.net` (and `unsafe-inline` if the chat stays inline); styles from `self`, `unsafe-inline`, `https://fonts.googleapis.com`, `https://cdn.jsdelivr.net`; fonts from `https://fonts.gstatic.com`; `connect-src 'self'`. Tune if you later move scripts off inline.

### 2.3 Audit logging

- **File**: `apps/api/src/index.ts`.
- **Change**: Use existing Fastify logger. After each auth outcome, log one line (no passwords): e.g. `request.log.info({ event: 'login_ok', username })`, `request.log.info({ event: 'login_fail', reason: 'invalid_credentials' })`, same for register (success / username_taken / validation_fail) and logout (username). Optionally include `request.ip` for security review.

---

## 3. Deployment and docs

### 3.1 Docker production defaults

- **File**: `Dockerfile`.
- **Change**: Add `ENV NODE_ENV=production` so the token-secret check runs by default in the image. Operators can still override if needed.

### 3.2 systemd secret without pasting

- **File**: `deploy/agentchat-api.service` and/or `docs/SERVER-DEPLOY.md`.
- **Change**: Add a commented line showing loading the secret from a file, e.g. `EnvironmentFile=/opt/agentchat/.env.agentchat` (or a systemd credential path), and instruct to set `AGENTCHAT_TOKEN_SECRET` there so the secret isn't in the unit file.

### 3.3 Hosting docs point to checklist

- **Files**: `docs/HOSTING.md`, `docs/SERVER-DEPLOY.md`.
- **Change**: In each, add a short "Before going live" subsection that links to `docs/SECURITY-AND-SCALE.md` and lists: set `AGENTCHAT_TOKEN_SECRET`, set `NODE_ENV=production`, use HTTPS (reverse proxy), (optional) stricter auth rate limit at proxy, back up DB, run container as non-root. No need to duplicate the full checklist.

### 3.4 SECURITY-AND-SCALE.md updates

- **File**: `docs/SECURITY-AND-SCALE.md`.
- **Change**: Under "Optional hardening", note that CORS (when `CORS_ORIGIN` set), CSP for chat, and audit logging for auth events are implemented; optionally add one line each. Add a line that `/users` is capped (e.g. 2000) and that auth routes have an in-app 10/min rate limit.

---

## 4. Code hygiene

- **File**: `apps/api/src/index.ts`.
- **Change**: Remove the obsolete comment block (lines 48–58) and the `onRoute` hook once the explicit auth rate limit preHandler is in place.

---

## 5. Order of work (fast path)

| Step | Task | Deps |
|------|------|------|
| 1 | Login validation (1.1) | None |
| 2 | Auth rate limit preHandler + remove onRoute and comments (1.2, 4) | None |
| 3 | `/users` cap (1.3) | None |
| 4 | CORS (2.1) | Add `@fastify/cors` |
| 5 | CSP for / and /chat (2.2) | None |
| 6 | Audit logging (2.3) | None |
| 7 | Docker `NODE_ENV` (3.1) | None |
| 8 | systemd EnvironmentFile example (3.2) | None |
| 9 | HOSTING + SERVER-DEPLOY "Before going live" (3.3) | None |
| 10 | SECURITY-AND-SCALE.md tweaks (3.4) | 1–3, 2.1–2.3 |

---

## 6. Out of scope (per doc)

- **HTTPS / backups**: Operational (reverse proxy, cron); no code change.
- **PostgreSQL / Redis / WebSockets / full `/users` pagination**: Doc says "at millions"; not part of "quick production ready."

---

## Summary

- **Checklist in code**: Enforce token secret (done), login validation, auth 10/min rate limit, `/users` cap, and document Docker/systemd and hosting so the checklist is visible at deploy time.
- **Optional hardening**: CORS (env), CSP (chat), audit logging (auth).
- **Docs**: One "Before going live" block in HOSTING and SERVER-DEPLOY linking to SECURITY-AND-SCALE; small updates in SECURITY-AND-SCALE for what's implemented.
78 changes: 78 additions & 0 deletions docs/SECURITY-AND-SCALE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Security and scale — production readiness

This document covers security measures, limitations, and what to do before launching to a large audience (including millions of users).

---

## Security (what’s in place)

| Area | Implementation |
|------|----------------|
| **Passwords** | Argon2id hashing; min 8 chars, max 4096 (DoS protection). |
| **JWT** | HMAC-SHA256; expiry 7 days; `token_valid_after` for “logout everywhere”. |
| **Token secret** | **Must** set `AGENTCHAT_TOKEN_SECRET` in production; app throws on startup if `NODE_ENV=production` and default secret is used. |
| **SQL** | Parameterized queries only; no string concatenation → no SQL injection. |
| **Auth** | All sensitive routes use Bearer token; conversation access checked (user must be in conversation). |
| **Input limits** | Username ≤ 64 chars; message body ≤ 64 KB; password length capped. |
| **Rate limiting** | Global 200 req/min per IP via `@fastify/rate-limit`. |
| **Headers** | `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Referrer-Policy: strict-origin-when-cross-origin`. |
| **Web XSS** | User content escaped before markdown; links sanitized (only `http`/`https` allowed). |

---

## Before you launch (production checklist)

1. **Set `AGENTCHAT_TOKEN_SECRET`**
Use a long, random secret (e.g. `openssl rand -base64 32`). Set it in the environment (systemd, Docker, or PaaS). If you run with `NODE_ENV=production` and the default secret, the API will **not** start.

2. **Set `NODE_ENV=production`**
Needed so the token-secret check runs.

3. **HTTPS**
Run the API behind a reverse proxy (Caddy, Nginx, or your PaaS) with TLS. Do not expose the API directly on the public internet without HTTPS.

4. **Stricter rate limits on auth**
The app applies a global rate limit. For login/register, use your reverse proxy to apply a **stricter** limit (e.g. 10 requests/minute per IP for `/auth/login` and `/auth/register`) to reduce brute-force and account enumeration.

5. **Backups**
SQLite DB is in `AGENTCHAT_DB_PATH`. Back it up regularly (e.g. cron + off-site copy).

6. **Docker**
Prefer running the container as a non-root user and mount the data volume read/write only where needed (see your Dockerfile and hosting docs).

---

## Scalability (SQLite and single node)

The current design is **single-node and SQLite-based**. That is fine for many thousands of users and moderate traffic, but has hard limits at very large scale.

| Concern | Current behavior | At “millions” of users |
|--------|------------------|-------------------------|
| **Database** | SQLite, single writer | Becomes a bottleneck; consider PostgreSQL (or similar) and connection pooling. |
| **Presence** | In-memory `lastSeen` Map | Single node only; lost on restart. For multi-node, use Redis (or similar) and share presence. |
| **Web chat polling** | Client polls inbox/messages every 2.5 s | Huge load at scale. Prefer WebSockets or SSE and push. |
| **`/users`** | Returns all usernames | Can be huge. Add pagination or search and cap response size. |
| **Horizontal scaling** | One API process | To scale out, need shared session/presence (e.g. Redis) and a database that supports multiple writers. |

So: the app is **secure and ready to launch** for small to medium scale (e.g. hundreds of thousands of users on a single node with SQLite, depending on usage). For **millions** of concurrent users, plan for:

- Replacing SQLite with a scalable DB (e.g. PostgreSQL).
- Moving presence to a shared store (e.g. Redis).
- Replacing polling with WebSockets or SSE.
- Pagination or search on `/users` and possibly other list endpoints.

---

## Optional hardening

- **CORS**
If the web UI is on a different origin than the API, configure CORS (e.g. `@fastify/cors`) to allow only that origin.

- **CSP**
Add a Content-Security-Policy header for the web chat page to restrict scripts and resources (e.g. allow only same origin and trusted CDNs).

- **Audit logging**
Log auth failures and sensitive actions (e.g. login, register, logout) for security reviews.

- **Password policy**
You can tighten `MIN_PASSWORD_LEN` or add complexity rules in `packages/core` and the API validation.
2 changes: 1 addition & 1 deletion packages/core/src/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function getSql(): ReturnType<typeof postgres> {
if (!sql) {
if (!DATABASE_URL?.trim()) {
throw new Error(
"DATABASE_URL is required. Set it to your Supabase (or Postgres) connection string."
"DATABASE_URL is required. Create a .env file in the project root (copy from .env.example) and set DATABASE_URL to your Supabase or Postgres connection string."
);
}
sql = postgres(DATABASE_URL, { max: 10 });
Expand Down