---
## 🌍 What is Echoboard?
Echoboard is a feedback platform you host yourself. People visit your board, submit feature requests or bug reports, vote on what matters to them, and leave comments - all without creating an account, handing over an email address, or agreeing to be tracked.
It's built on a simple idea: feedback should be accessible to everyone, not just people willing to hand over personal data to yet another platform. The barrier to participation is zero. Show up, speak, leave. Your voice counts the same whether you're anonymous or not.
If someone wants more permanence - to keep their posts across devices or browsers - they can optionally register a passkey. No password, no email. Just a biometric or device PIN. Their identity belongs to them, stored on their own device, not in your database.
## 🔒 Identity, privacy, and why we don't want your email
Most platforms demand an email address before you're allowed to speak. That email becomes a leash - used for marketing, sold to data brokers, leaked in breaches, and weaponized for account recovery attacks. Echoboard doesn't ask for it because Echoboard doesn't need it. Nobody needs it. It was never about verification - it was about control.
Identity in Echoboard works as a spectrum. You choose how much of yourself to reveal, and you can change your mind at any time:
**Just show up** - the moment you visit, a random token is stored in your browser cookie. That's your identity. No form, no click-through, no consent banner for data you never gave. You can vote, comment, and submit posts immediately. If you clear your cookies, that identity is gone - and that's fine. The feedback you left behind still stands on its own. Your words matter more than your name.
**Save a recovery phrase** - if you want a safety net without committing to anything permanent, you can generate a six-word recovery phrase from the settings page. Write it on a sticky note, put it in a password manager, tattoo it on your arm - your call. If cookies get wiped, you type those six words and you're back. The phrase is hashed with bcrypt and looked up via a blind index - even we can't read it. It's single-use and expires after 90 days, so there's no permanent token sitting in a database waiting to be stolen.
**Register a passkey** - for people who want real persistence across browsers and devices. Passkeys use WebAuthn - your phone's fingerprint reader, your laptop's face unlock, or a hardware security key. The private key never leaves your device. We store a public key that can verify you, but can't impersonate you. No password to forget, phish, or leak. No email to harvest.
At every level, display names are encrypted at rest with AES-256-GCM. Lookups happen through blind indexes (HMAC-SHA256) so the database never stores plaintext names alongside user records. There is no email column in the users table. There is no phone number column. There is no "real name" column. These fields don't exist because we made a deliberate choice not to build the infrastructure of surveillance, even if we promise not to use it. The safest data is data that was never collected.
The people who use your feedback board are helping you build better things. The least you can do is not make them pay for that privilege with their personal information.
## 🗳️ How people use it
**As a visitor**, you land on a board and immediately see what others have submitted. You can vote on things you care about, leave comments, file your own feature requests or bug reports - all without signing up for anything. A browser cookie ties your activity together for the session. Close the tab and come back later, you're still you (as long as cookies persist).
**If you want persistence**, you can register a passkey from the settings page. This lets you keep your identity across browsers and devices. You get a username, a profile, and the ability to upload an avatar. Or if your browser doesn't support passkeys, you can generate a recovery phrase - six words you write down somewhere safe that let you get back to your account if cookies get cleared.
**As an admin or team member**, you manage boards, respond to feedback, track statuses, merge duplicates, and keep things organized. The admin dashboard gives you full control over every aspect of the platform without touching a terminal.
## ✊ Why self-host?
Because your community's feedback shouldn't live on someone else's server, feeding someone else's business model. When you self-host Echoboard:
- The data lives on your infrastructure, under your control
- No third party reads, mines, or monetizes the feedback
- No vendor lock-in - the code is CC0, do whatever you want with it
- No "free tier" that mysteriously degrades when you need it most
- No dark patterns nudging people toward paid plans
- Your users interact with YOUR instance, not a platform that treats them as product
The tools people use to communicate should belong to the people using them. Not to shareholders, not to VCs, not to ad networks. Echoboard is a small step in that direction.
## 📦 Features
### For everyone
- Submit feature requests and bug reports without any account
- Vote on posts with configurable budgets (so no one person dominates)
- Comment with markdown, @mentions, emoji reactions, and file attachments
- Dark and light themes that follow system preference
- Full-text search across all boards
- Similar post detection (so you find existing requests before duplicating)
- RSS feeds for every board
- Push notifications for status changes and new posts
- Recovery codes for people who can't use passkeys
- Keyboard accessible and screen reader friendly throughout
### For admins and teams
- Dashboard with stats, post management, and bulk actions
- Custom statuses per board (not just open/closed - whatever fits your workflow)
- Roadmap and changelog pages with scheduled publishing
- Team system - invite admins and moderators without them needing email accounts
- Granular locking - lock post edits, individual comments, entire threads, or voting
- Post merging for duplicates with vote consolidation
- Edit history with rollback for both posts and comments
- Embed widget you can drop into any external site
- Webhooks for status changes, new posts, and comments
- View counts on every post
- Data export in CSV and JSON
### Infrastructure
- Runs in Docker with a single `docker compose up -d`
- PostgreSQL for storage, no external services needed
- Plugin system - upload zip plugins through the dashboard, no restart required
- ALTCHA proof-of-work spam protection (no CAPTCHAs bothering your users)
- Field-level encryption for personally identifiable data
- HSTS, CSP, and all the security headers you'd expect
- Automatic database migrations on startup
## 🛡️ Security
Letting people participate anonymously doesn't mean letting them run wild. Echoboard takes security seriously at every layer - protecting the people who give feedback, the people who manage it, and the data in between.
**Spam protection without surveillance.** Instead of CAPTCHAs - which are inaccessible, annoying, and train someone else's machine learning models with your users' labor - Echoboard uses ALTCHA proof-of-work challenges. The user's browser solves a small computational puzzle before submitting. It's invisible to real people and expensive for bots. No third-party scripts, no tracking pixels, no "select all the traffic lights."
**Encrypted at rest.** Display names and other personal data aren't stored as plain text in the database. Each field is individually encrypted with AES-256-GCM - even if someone gets access to your database, they can't read user identities without the encryption key. Lookups happen through blind indexes (HMAC-SHA256), so searches work without ever exposing the plaintext.
**Passwords never touch the wire in the clear.** Admin passwords are hashed with bcrypt (cost factor 12) before storage. Timing-safe comparisons prevent attackers from figuring out whether an email exists based on how long a login attempt takes. Failed login attempts trigger exponential backoff, making brute force impractical.
**Passkeys are phishing-proof.** Unlike passwords, passkeys use public-key cryptography. The private key never leaves the user's device - not to your server, not to anyone. Even if someone intercepts the authentication exchange, they can't replay it. There's nothing to phish, nothing to leak, nothing to stuff into a credential database.
**Recovery codes are single-use and hashed.** When a user generates a recovery phrase, the plaintext is shown once and never stored. What goes into the database is a bcrypt hash and a blind index. Even someone with full database access can't reverse the phrase. Each code works exactly once and expires after 90 days.
**Token blocklisting.** When someone logs out, changes their identity, or has their account deleted, their session tokens are immediately blocked. The blocklist is persistent (stored in the database, not in memory), so a server restart doesn't magically revalidate revoked sessions.
**Role-based access for the team.** Not every team member needs the same level of access. Super admins, admins, and moderators have different permissions, enforced server-side on every single request. A moderator can't escalate to admin by calling API endpoints directly - the backend checks your role before doing anything, not just the frontend.
**Invite links are single-use and expiring.** Team invites are hashed before storage, expire after a configurable window, and can only be claimed once. The claim happens inside a serializable database transaction, so even two simultaneous requests can't both succeed.
**Webhooks can't reach your internal network.** Outbound webhook delivery validates the destination URL against a blocklist of private IP ranges, resolves DNS before connecting (preventing rebinding attacks), and connects directly to the resolved IP with TLS verification. Your internal services stay invisible.
**Content security headers everywhere.** HSTS with a two-year max-age, strict CSP, X-Content-Type-Options, X-Frame-Options, Referrer-Policy set to no-referrer. The embed widget runs in a sandboxed iframe. Custom CSS input is decoded and scanned before storage to prevent injection.
**File uploads are validated, not trusted.** Uploaded images are checked against magic byte signatures (not just file extensions or MIME types). Path traversal is prevented with realpath resolution. If a database insert fails after a file is written, the orphaned file is cleaned up automatically.
**Rate limiting on everything.** Every endpoint has a rate limit appropriate to its sensitivity - from 100/minute for general browsing down to 3/15 minutes for recovery code attempts. This isn't just a global throttle; each endpoint is tuned individually.
Security isn't a feature you bolt on at the end. It's a set of choices you make from the beginning about what data to collect (as little as possible), how to store it (encrypted), how to verify identity (without passwords when possible), and how to fail (safely, loudly, and without leaking information). Echoboard makes those choices so you don't have to think about them.
## 🚀 Getting started
### 1. Clone and configure
```bash
git clone https://git.lashman.live/lashman/echoboard.git
cd echoboard
cp .env.example .env
```
Open `.env` in your editor and fill in:
**Database password** - set `POSTGRES_PASSWORD` to something random.
**Encryption keys** - generate five secrets, one for each of `APP_MASTER_KEY`, `APP_BLIND_INDEX_KEY`, `TOKEN_SECRET`, `JWT_SECRET`, and `ALTCHA_HMAC_KEY`:
```bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```
Run that five times, paste each output into the corresponding variable.
**Push notification keys** - generate VAPID keys:
```bash
npx web-push generate-vapid-keys
```
Paste the public and private keys into `VAPID_PUBLIC_KEY` and `VAPID_PRIVATE_KEY`.
**Domain settings** - set `WEBAUTHN_RP_ID` to your domain (e.g. `feedback.example.com`) and `WEBAUTHN_ORIGIN` to the full URL (e.g. `https://feedback.example.com`).
### 2. Start it
```bash
docker compose up -d
```
That's it. Two containers come up - the app and a PostgreSQL database. Migrations run automatically.
### 3. Create your admin account
Visit `/admin` in your browser. Since no admin exists yet, you'll see a setup screen where you pick an email and password. This screen disappears forever after the first admin is created.
### 4. Set up HTTPS
Passkeys require HTTPS to work. Put a reverse proxy in front of Echoboard. With nginx:
```nginx
server {
listen 443 ssl;
server_name feedback.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 50m;
}
}
```
## 🔄 Updating
```bash
git pull
docker compose up -d --build
```
Database migrations run automatically on startup. Your data, uploads, and plugins are preserved.
## 💾 Data and backups
All persistent data lives in directories next to the compose file:
| Directory | Contents |
|---|---|
| `./data/postgres/` | Database files |
| `./uploads/` | User avatars and file attachments |
| `./plugins-installed/` | Uploaded plugin files and assets |
Back these up however you normally back things up. The database can also be dumped with `docker compose exec db pg_dump -U echoboard echoboard > backup.sql`.
## 👥 Team system
Echoboard supports multiple team members without anyone needing an email account.
**Super admin** - the person who set up the instance. Full access to everything. Can invite admins and moderators, manage branding, configure webhooks, install plugins. Cannot be removed.
**Admin** - can do most things the super admin can, except change branding or install plugins. Can invite moderators. Sees their own invitees in the team list.
**Moderator** - can respond to posts, change statuses, pin, merge, lock, and manage tags and categories. Cannot configure boards, webhooks, or invite others.
To add a team member: go to Team in the admin panel, click Invite, choose a role and expiry. Share the invite link. The person claims it, picks a display name, and secures their account with a passkey or recovery phrase. No email involved.
Each team member gets a display name and optional team title (like "Product Lead" or "Support") that shows alongside their responses in the public feed.
## 🧩 Plugin system
Plugins extend Echoboard without modifying the core code. They're zip files uploaded through the admin dashboard - no server restart, no command line.
### What plugins can do
- **Add API routes** - mounted under `/api/v1/plugins/