- Java 51.5%
- HTML 48.5%
- Add Open Graph, Twitter Card, canonical, robots, author, keywords, and JSON-LD structured data to index.html - Generate og-image.png (1200x630) for social link previews - Downgrade Java toolchain from 25 to 21 (highest version available locally; bytecode target remains Java 17) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| .idea | ||
| gradle | ||
| src/main | ||
| WebContent | ||
| .gitattributes | ||
| .gitignore | ||
| build.gradle | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| pinboard.json | ||
| README.md | ||
| settings.gradle | ||
Chaos Board
Chaos Board is a public encrypted bulletin board for publishing and reading messages. All messages are publicly visible, but each message is encrypted — to read one you must know the encryption key (password) used when it was posted. The server stores only ciphertext and never sees the plaintext content of any message.
Source code: https://git.hoddmimes.com/bertilsson/CBoard
How it works
Client-side encryption
All encryption and decryption happen entirely in the browser using CryptoJS. The server is never involved in either operation.
- Algorithm: AES-256-CBC
- Key derivation: PBKDF2-SHA256, 10 000 iterations, random 16-byte salt per message
- Storage format:
Base64( salt(16 bytes) || IV(16 bytes) || ciphertext )
The encryption key is a plain-text password chosen by the author at the time of posting. It is never transmitted to or stored by the server. To decrypt a message the reader must enter the same password in the browser.
Publishing a message
- The author opens the Post an Encrypted Message page.
- The server sets a one-time session cookie (
cboard_message_key) via a servlet filter. - The browser encrypts the message text with the chosen password.
- The encrypted payload, the cookie key, and optional comma-separated tags are POSTed to the server.
- The server validates the one-time key (anti-spam), stores the ciphertext, and discards the key.
Reading a message
- The reader clicks a message in the list and sees the encrypted ciphertext.
- After entering the password the browser decrypts it locally — no server round-trip.
Features
| Feature | Detail |
|---|---|
| Client-side encryption | AES-256 + PBKDF2-SHA256, never exposed to server |
| Public message list | All messages visible; unreadable without the password |
| Tag filtering | Messages can carry comma-separated plaintext tags for filtering |
| Anti-spam one-time key | Cookie-based key issued per page load, consumed on POST |
| Per-IP rate limiting | Configurable max submissions per hour (default 30) |
| IP ban list | Persisted in DB; auto-revoked after configurable period |
| Message retention | Messages older than a configurable number of days are purged hourly |
| Dual deployment | Runs standalone (Javalin + embedded Jetty) or as a Tomcat WAR |
| Progressive Web App | Installable on iOS and Android with app icon and offline support |
Progressive Web App (PWA)
Chaos Board can be installed as a native-like application on your smartphone:
- iOS: Open in Safari, tap the Share button, and select "Add to Home Screen"
- Android: Open in Chrome/Firefox, tap the menu, and select "Install app"
The app runs full-screen without the browser address bar, displays a custom app icon on your home screen, and supports offline viewing of cached messages.
Learn more about Progressive Web Apps: https://web.dev/progressive-web-apps/
Tech stack
| Component | Technology |
|---|---|
| Backend (standalone) | Java 17+, Javalin 6, embedded Jetty, HTTPS via SSL plugin |
| Backend (servlet) | Jakarta Servlet 6.0, Tomcat 10.1+ |
| Database | SQLite via org.xerial:sqlite-jdbc |
| Serialisation | Jackson |
| Frontend encryption | CryptoJS (AES, PBKDF2) |
| Build | Gradle 9 (Groovy DSL) |
Deployment
Standalone
Build and run directly with an embedded HTTPS server:
./gradlew run
# or build the installer:
./gradlew makeInstaller # produces build/cboard.run
The self-extracting cboard.run installer prompts for the installation directory (default /usr/local/cboard), HTTPS port (default 443), and max message rate (default 30/hour), then extracts the application and writes a pinboard.json configuration file.
After installation, place your SSL certificate and private key at:
$INSTALL_DIR/cert.pem
$INSTALL_DIR/privatekey.pem
Start with:
$INSTALL_DIR/cboard
Tomcat WAR
./gradlew war # produces build/libs/cboard.war
Copy cboard.war to Tomcat's webapps/ directory. The application deploys at /cboard/. All parameters are configured via <init-param> entries in WEB-INF/web.xml inside the WAR.
The database directory must exist and be writable by the Tomcat user before first start:
sudo mkdir -p /var/lib/cboard
sudo chown tomcat:tomcat /var/lib/cboard
Configuration
Standalone — pinboard.json
{
"use_ipv4": true,
"https_port": 443,
"interface": "0.0.0.0",
"ssl": {
"cert": "/usr/local/cboard/cert.pem",
"key": "/usr/local/cboard/privatekey.pem"
},
"web_content": "/usr/local/cboard/webcontent",
"db_file": "/usr/local/cboard/data/pinboard.sqlite",
"max_messages_per_hour": 30,
"message_retention_days": 28,
"max_messages_per_retention": 2000,
"ban_duration_hours": 48
}
Tomcat WAR — WEB-INF/web.xml init-params
| Parameter | Default | Description |
|---|---|---|
db_file |
/var/lib/cboard/pinboard.sqlite |
Path to the SQLite database file |
max_messages_per_hour |
30 |
Max posts per IP per hour before the IP is banned |
message_retention_days |
28 |
Days after which messages are purged |
max_messages_per_retention |
2000 |
Max total posts per IP within the retention window before ban |
ban_duration_hours |
48 |
Hours until a ban is automatically revoked |
Anti-spam and ban system
Rate limiting: each IP is allowed at most max_messages_per_hour posts per rolling hour. Exceeding this limit immediately bans the IP.
Retention limit: if an IP accumulates max_messages_per_retention or more posts within the active retention window the IP is banned before the next post is accepted.
Bans are stored in the banned_ips database table and automatically revoked after ban_duration_hours hours. Expired bans are purged by the same hourly scheduler that purges old messages.
Database schema
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
tags TEXT,
encrypted_content TEXT NOT NULL,
ip TEXT
);
CREATE TABLE banned_ips (
ip TEXT PRIMARY KEY,
banned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
reason TEXT
);
Privacy notice
All messages are publicly readable (as ciphertext) by anyone with access to the server. Client IP addresses are recorded in the database and may also appear in HTTP server logs. If anonymity matters, access the service through a trustworthy VPN or the Tor network.