A bulletin board, where encrypted messages can be published. https://hoddmimes.com/cboard/index.html
  • Java 51.5%
  • HTML 48.5%
Find a file
Pär Bertilsson 5a80a4aaa8 Add SEO meta tags, og-image, and downgrade toolchain to Java 21
- 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>
2026-06-03 18:22:20 +02:00
.idea Add SEO meta tags, og-image, and downgrade toolchain to Java 21 2026-06-03 18:22:20 +02:00
gradle Add servlet-container deployment alongside standalone mode 2026-06-01 14:44:30 +02:00
src/main Refactor CboardServlet: extract request routing and message validation 2026-06-03 08:07:34 +02:00
WebContent Add SEO meta tags, og-image, and downgrade toolchain to Java 21 2026-06-03 18:22:20 +02:00
.gitattributes Initial commit: Pin-Board encrypted bulletin board 2026-06-01 09:30:56 +02:00
.gitignore Initial commit: Pin-Board encrypted bulletin board 2026-06-01 09:30:56 +02:00
build.gradle Add SEO meta tags, og-image, and downgrade toolchain to Java 21 2026-06-03 18:22:20 +02:00
gradle.properties Initial commit: Pin-Board encrypted bulletin board 2026-06-01 09:30:56 +02:00
gradlew Initial commit: Pin-Board encrypted bulletin board 2026-06-01 09:30:56 +02:00
gradlew.bat Initial commit: Pin-Board encrypted bulletin board 2026-06-01 09:30:56 +02:00
pinboard.json wip 2026-06-02 08:30:16 +02:00
README.md Document PWA installation support in README 2026-06-02 19:33:17 +02:00
settings.gradle Initial commit: Pin-Board encrypted bulletin board 2026-06-01 09:30:56 +02:00

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

  1. The author opens the Post an Encrypted Message page.
  2. The server sets a one-time session cookie (cboard_message_key) via a servlet filter.
  3. The browser encrypts the message text with the chosen password.
  4. The encrypted payload, the cookie key, and optional comma-separated tags are POSTed to the server.
  5. The server validates the one-time key (anti-spam), stores the ciphertext, and discards the key.

Reading a message

  1. The reader clicks a message in the list and sees the encrypted ciphertext.
  2. 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.