From 5b89c370d5974f057306a52089f14c337565fd1d Mon Sep 17 00:00:00 2001 From: Doan Luu Duc Tai Date: Sat, 31 Oct 2026 17:54:29 -0500 Subject: docs: shorten guides and map repository files --- docs/CHAT-APPLICATION.txt | 298 ++++++++++++++++++++-------------------------- 1 file changed, 128 insertions(+), 170 deletions(-) (limited to 'docs/CHAT-APPLICATION.txt') diff --git a/docs/CHAT-APPLICATION.txt b/docs/CHAT-APPLICATION.txt index 7ebac77..388c677 100644 --- a/docs/CHAT-APPLICATION.txt +++ b/docs/CHAT-APPLICATION.txt @@ -16,34 +16,28 @@ along with this program. If not, see . EXPERIMENTAL PRIVATE CHAT - Available on main. Direct development and a prebuilt Podman/HTTPS - deployment path are implemented. See CHAT-PRODUCTION.txt for the release, - production setup, and recovery procedure; feature work does not itself merge - main or activate the VPS. - This is working experimental software, not an independently audited messenger. + Available on main: direct development and prebuilt Podman/HTTPS deployment. + See CHAT-PRODUCTION.txt for release, setup, and recovery. Feature work does + not merge main or activate the VPS. Experimental; no independent messenger audit. WHAT EXISTS - A Rust HTTP server and a Rust cryptographic command compiled to WASI. - Strict TypeScript implements the browser interface and a capability-limited - WASI host, compiled to small, readable, free JavaScript classic scripts. - Build configuration and source licensing are documented in TYPESCRIPT.txt. Text is displayed through textContent. - SearXNG and the other existing services are unchanged. - - Startup shows an ASCII download bar, percentage, and received/total MiB - while fetching the cryptographic module. If its size is unavailable, it shows - received MiB. A 45-second gap without download progress aborts with a reload - instruction; slow transfers continue while bytes arrive. After download, - a separate status identifies the connection to chat. No browser cache or - storage was added. - - Visitors choose CREATE NEW CHAT or OPEN CHAT FILE. Creation generates a - purpose-specific identity locally and downloads its protected file. Reopening - the exact downloaded file and entering its password is required before the - first authentication or message. No username/password account is created. - Returning users select their file and password again. The stable file does - not need updating as messages arrive. The owner has a separate chat identity - and inbox; the canonical RSA OpenPGP key is not used for chat encryption. + Rust HTTP server and Rust/WASI cryptography. Strict TypeScript compiles the + interface and capability-limited WASI host to small, readable, free classic + JavaScript. See TYPESCRIPT.txt for build/licensing. Text uses textContent. + Existing services, including SearXNG, are unchanged. + + Crypto download shows an ASCII bar, percentage, and received/total MiB; + unknown totals show received MiB. A 45-second stall aborts with reload + instructions; slow transfers continue while bytes arrive. Chat connection has + a separate status. No browser cache or storage was added. + + CREATE NEW CHAT generates a purpose-specific identity locally and downloads + its protected file. Reopen that exact file with its password before first + authentication/message. OPEN CHAT FILE restores access on later visits. + No username/password account; messages never require updating the stable file. + The owner has a separate identity/inbox. The canonical RSA OpenPGP key does + not encrypt chat. History defaults to zero messages. Authenticated counters show total, sent, and received messages. Choose 0 to N recent messages, where N is the current @@ -58,12 +52,10 @@ WHAT EXISTS LOCAL PREREQUISITES AND COMMANDS - Start in the root of the stack repository on main. Native - development needs Rust, libclang, OpenSSL 3.5 or newer development libraries, - a C compiler, pkg-config, make, Perl, curl, and tar. The checked configuration - uses OpenSSL 3.6.5. The WASI build script currently targets an x86_64 GNU/Linux - development host and downloads the hash-verified WASI SDK 34.0 and OpenSSL - source. General-purpose toolchains remain development tools, not VPS tools. + Start at the repository root on main. Native development needs Rust, libclang, + OpenSSL >=3.5 development libraries, C compiler, pkg-config, make, Perl, curl, + and tar. Checked OpenSSL: 3.6.5. WASI builds target x86_64 GNU/Linux and download + hash-verified WASI SDK 34.0/OpenSSL sources. Toolchains stay off the VPS. Build and initialize once: @@ -72,63 +64,54 @@ LOCAL PREREQUISITES AND COMMANDS sh infra/chat/build-source.sh cargo stack chat --init - Initialization appends CHAT_SETUP_TOKEN and CHAT_ORIGIN to the single root - .env, creating it privately if necessary. It refuses to overwrite an existing - setup token. The root .env.example lists both settings. Never commit .env. - Existing SearXNG settings stay in the same file. No crate has its own .env. - CHAT_BIND and public navigation now use the shared root configuration loader; - see CONFIGURATION.txt for the documented deployment keys. + Initialization privately creates/appends CHAT_SETUP_TOKEN and CHAT_ORIGIN + in the single root .env, preserving an existing token and SearXNG settings. + Root .env.example lists both; never commit .env or create crate-specific files. + CHAT_BIND and public navigation use the shared loader; see CONFIGURATION.txt. - For subsequent Rust-only edits, sh infra/chat/build-browser.sh reuses the - previously built, hash-verified OpenSSL/SDK inputs. Rebuild OpenSSL itself - after changing its source, version, configuration, or build flags. The WASI - module has a 256 MiB maximum linear-memory limit. + For Rust-only edits, sh infra/chat/build-browser.sh reuses verified OpenSSL/SDK + inputs. Source/version/configuration/flag changes require an OpenSSL rebuild. + WASI linear memory is capped at 256 MiB. Start: cargo stack chat - Open http://127.0.0.1:9080/ and choose OWNER SETUP, labeled for the owner - only. The direct http://127.0.0.1:9080/#setup link also works. - - Read CHAT_SETUP_TOKEN from your root .env locally and paste it into the owner - setup form. Choose an owner display name and a strong unique password. Save - the downloaded file, reopen it, and unlock it. That establishes the owner - public identity. The setup token stops authorizing setup once an owner exists. - It does not become an owner login or a key recovery mechanism. OWNER SETUP - stays visible; once an owner exists, it explains how to open the existing - owner file or reset after loss instead of silently replacing the key. Never publish - the owner private file or its password. Keep both in your private KeePassXC - database, along with a separately protected backup of that database. - - Open another browser window at http://127.0.0.1:9080/ to create a visitor chat. - Every page holds its own identity in memory; there is no shared browser login. - Stop the development server with Control-C. A server restart invalidates all - sessions but preserves identities, conversations, counts, and ciphertext. - - The direct development server refuses a non-loopback bind or an origin that - does not exactly match its development bind address. Packaged production uses - a private container listener behind Caddy HTTPS, with an explicit external - CHAT_HOST/HTTPS_PORT origin. Forwarded request headers do not choose that - origin. Do not expose the direct development server through a public proxy. + Open http://127.0.0.1:9080/ and choose owner-only OWNER SETUP, + or use http://127.0.0.1:9080/#setup. + + Read CHAT_SETUP_TOKEN from root .env locally; paste into setup. Choose a + display name and strong unique password; save, reopen, and unlock the file. + This registers the owner public identity and disables token-based setup. + The token grants neither login nor key recovery. OWNER SETUP remains visible + with existing-file/reset instructions; it never silently replaces a key. + Never publish private file/password. Keep both in KeePassXC with a separately + protected database backup. + + Create a visitor chat in another window at http://127.0.0.1:9080/. + Identity stays in each page's memory; browser login is not shared. Control-C + stops development. Restart invalidates sessions, preserving identities, + conversations, counts, and ciphertext. + + Direct development requires loopback and an exactly matching bind/origin. + Production uses a private listener behind Caddy HTTPS, with explicit + CHAT_HOST/HTTPS_PORT origin; forwarded headers cannot choose it. Never expose + direct development through a public proxy. OWNER FILE LOSS AND BACKEND RESET - A setup token grants registration permission. It cannot recover the private - key inside a lost owner file. If that file and its backups are gone, existing - messages cannot be decrypted with a newly generated identity. The reset below - intentionally starts a fresh chat service and clears all stored messages, - encrypted profiles, conversations, counters, and the owner registration. - It preserves the root .env and any previously exported backup archives. - It does not claim forensic erasure of backups, snapshots, or OS copies. - - Run these commands locally at the stack root for the current development - build. There is no reset operation in the HTTP API. Filesystem access to - the private chat directory is the administrative boundary. Production uses - ./bin/stack chat --owner-fingerprint and ./bin/stack chat --reset-owner - through an isolated maintenance container. Stop production with ./bin/stack - down first; restart with ./bin/stack up --production. No Rust is needed on - the VPS. See CHAT-PRODUCTION.txt before a destructive reset. + Setup tokens grant registration, not private-key recovery. A new identity + cannot decrypt old messages if the owner file/backups are lost. Reset clears + all messages, encrypted profiles, conversations, counters, and owner registration. + Root .env and exports survive. This does not forensically erase backups, + snapshots, or OS copies. + + Run development commands at the stack root. Reset has no HTTP API; private + chat-directory access is the administrative boundary. Production runs + ./bin/stack chat --owner-fingerprint and ./bin/stack chat --reset-owner in an + isolated maintenance container. First stop with ./bin/stack down; restart + with ./bin/stack up --production. No VPS Rust required. Read CHAT-PRODUCTION.txt + before destructive reset. Read the current public fingerprint, even if you have lost the private file: @@ -139,26 +122,23 @@ OWNER FILE LOSS AND BACKEND RESET cargo stack chat --reset-owner PASTE_CURRENT_OWNER_FINGERPRINT_HERE - A fingerprint mismatch refuses without deleting data. The server and reset - process share a filesystem lock. Direct development additionally requires - the loopback service port to be free. This prevents resetting a running service, including a - development process launched before the lock was added. Online snapshots and - reading the public owner fingerprint remain available while chat is running. + Mismatched fingerprints refuse without deletion. Server/reset share a lock; + direct development also requires a free loopback service port, protecting + older running processes without the lock. Online snapshots and fingerprint + reads remain available during operation. Restart: cargo stack chat - Choose OWNER SETUP and use the existing CHAT_SETUP_TOKEN from the same root - .env to generate, save, and reopen a fresh owner file. Back that file and its - password up in KeePassXC. Visitors must create fresh chat files too: their - previous files pin the former owner key and are correctly rejected after - replacement. A reset cannot prove continuity with the lost key. + Use OWNER SETUP and existing root .env CHAT_SETUP_TOKEN; generate, save, and + reopen a fresh file. Back up file/password in KeePassXC. Visitors also need + fresh files: old owner-key pins correctly reject replacement. Reset cannot + prove continuity with the lost key. - If an old owner file is found later, an old encrypted backup may still allow - recovering its original conversations in an isolated restored installation. - The reset does not remove existing exported archives, and it never substitutes - a new key into old ciphertext or old visitor files. + An owner file found later may recover old conversations from an encrypted + backup in isolation. Reset retains exports and never substitutes new keys + into old ciphertext or visitor files. FILE AND MESSAGE CRYPTOGRAPHY @@ -168,12 +148,10 @@ FILE AND MESSAGE CRYPTOGRAPHY a fixed purpose-only user ID. Names are not placed in public certificate IDs. The server rejects uploaded secret-key material and unsupported certificates. - New JSON identity files are indented and use an array of 80-character - Base64 ciphertext chunks; the final chunk may be shorter. JSON indentation, - quotes, and commas are additional to the ciphertext width. The reader joins - the chunks before Base64 decoding. No private fields become plaintext. - This is the application's own JSON envelope, not OpenPGP ASCII armor. - The new file-format identifier is authenticated as associated data: + New identity JSON uses indentation and 80-character Base64 ciphertext chunks; + the last may be shorter. JSON punctuation/indentation adds width. Readers join + chunks before decoding. Private fields stay encrypted. This application envelope + is not OpenPGP ASCII armor. Its identifier is authenticated as associated data: stack-chat-file/v2;argon2id-m65536-t3-p1;aes256-gcm @@ -181,10 +159,9 @@ FILE AND MESSAGE CRYPTOGRAPHY stack-chat-file/v1;argon2id-m65536-t3-p1;aes256-gcm - v1 uses its original identifier as associated data. Changing a version label - or converting its field shape does not convert a file: the authentication tag - binds the version. No existing file needs rewriting; old readers do not support - new v2 exports. Cryptographic algorithms and KDF parameters are unchanged. + v1 retains its original associated data. The tag binds versions: relabeling + or reshaping fields cannot convert files. Existing files need no rewrite; + old readers reject v2. Algorithms/KDF parameters stay fixed. Argon2id version 0x13 uses 65536 KiB, three passes, one lane, and a 32-byte derived key. A fresh CSPRNG salt of 16 bytes and AES-256-GCM nonce of 12 bytes @@ -203,11 +180,10 @@ FILE AND MESSAGE CRYPTOGRAPHY formats return a generic error. Mutable KDF keys, plaintext envelopes, and private serialization buffers are zeroized where practical. - The browser checks the file's exact origin and pinned owner certificate on - unlock. Owner identity creation pins its own public certificate. Visitor - creation pins the public certificate currently advertised over the origin's - trusted connection. First-visit trust is consequently still trust in the - served application and TLS; it is not an out-of-band identity guarantee. + Unlock checks exact origin and owner pin. Owner creation pins its own public + certificate; visitors pin the certificate advertised over the trusted origin + connection. First visits still trust served application/TLS, without an + out-of-band identity guarantee. Each text message is signed, then encrypted for both participants. A signed plaintext JSON object binds v=1, kind=message, exact origin, conversation ID, @@ -246,12 +222,11 @@ AUTHENTICATION AND STORAGE keys or message plaintext. OS diagnostics and a future reverse proxy need their own logging review before deployment. - Sends are transactional. Retrying exactly the same ciphertext, message ID, - sender, and conversation returns its existing sequence without incrementing - counts. Reusing an ID with different data is rejected. This handles a response - lost after the message was stored. Keep the page unlocked to retry; reload - discards the pending ciphertext and draft, so inspect history before resending. - Send retries are deliberate; messages are not automatically resent. + Transactional sends deduplicate exact ciphertext/message-ID/sender/conversation + retries, returning the original sequence/counts. Reused IDs with changed data + are rejected. This handles lost responses after storage. Keep the page unlocked + to retry; reload loses pending ciphertext/draft, so inspect history before + resending. Retries are deliberate, never automatic. Limits: 8192 UTF-8 bytes per text, 16384 bytes per signed payload, 65536 base64 characters per stored ciphertext, 50 history rows per API request, 1000 visitor @@ -297,33 +272,28 @@ BROWSER STATE AND LIMITATIONS addresses during connections. Bearer-token theft permits API use until expiry or lock, though decrypting or signing still requires the private key. - A malicious server can replace JavaScript or WASM and steal passwords and - plaintext on the next visit. A pinned key and LibreJS do not prevent that. - Shared-host compromise, browser extensions, a compromised OS, browser bugs, - dependency attacks, malicious friends, and denial of service remain risks. - A purpose label advises separation; it cannot prevent exporting and reusing - a key elsewhere. This application is not a SimpleX equivalent. + Malicious servers can replace JavaScript/WASM and steal passwords/plaintext + on the next visit; key pins and LibreJS cannot prevent it. Shared-host/OS + compromise, extensions, browser bugs, dependency attacks, malicious friends, + and denial of service remain risks. Purpose labels cannot prevent key export + or reuse. This is not a SimpleX equivalent. BACKUP, EXPORT, AND RESTORE - The existing cargo stack backup creates a GPG-encrypted archive and now uses - SQLite's online backup API for chat.sqlite. It does not copy a live SQLite - file or journal with ordinary file-copy code. It can take a consistent chat - snapshot while the local chat server runs. Existing Podman data still follows - the stack's pause/copy/resume process; the chat container stays unpaused. - Separate snapshots cover both chat and chat-local. A snapshot is atomic - for chat data; unrelated services do not share a global transaction with it. - The backup includes the single root .env and must remain private/encrypted. + cargo stack backup uses SQLite online backups, not ordinary live database/ + journal copies. chat and chat-local get separate consistent snapshots while + running; chat stays unpaused. Other Podman services retain pause/copy/resume. + Chat snapshots are atomic, without a cross-service transaction. Exports + include root .env and must stay private/encrypted. For a chat-only consistent database export: cargo run --locked -p stack-chat-server -- --root . --snapshot var/exports/chat-snapshot.sqlite - Create var/exports privately first if it does not exist. The destination must - not already exist. This standalone snapshot is ciphertext plus public metadata, - not a GPG-encrypted full-stack archive; encrypt it before off-machine storage. - It does not contain browser private identities. A server-side backup cannot - recover a lost user or owner file/password. + Create var/exports privately first; destination must not exist. Standalone + snapshots contain ciphertext/public metadata, without full-stack GPG encryption. + Encrypt before off-machine storage. Browser private identities are absent; + server backups cannot recover lost identity files/passwords. Restore a full stack using the existing README.txt backup/restore instructions. For a chat-only restore, stop the chat server, put the verified snapshot at @@ -334,33 +304,27 @@ BACKUP, EXPORT, AND RESTORE origin deliberately makes old files fail their origin binding; there is no origin migration UI yet. Do not edit an encrypted file by hand. - Keep at least one encrypted backup away from the VPS. Test the owner identity, - visitor identity, correct counts, and decrypted messages after a restore. - Keep private identity files and passwords in KeePassXC with a protected - database backup. Never place them in source control or the public source - archive. Deleting a message locally or losing a file does not delete stored - ciphertext, old exports, or other participants' copies. + Keep an encrypted backup off-VPS. After recovery, test owner/visitor identities, + counts, and decryption. Keep private files/passwords in KeePassXC with a protected + database backup, never source control/public archives. Local deletion or file + loss does not delete stored ciphertext, exports, or participants' copies. INTERFACE AND SITE STYLE - The interface follows web/styles.css, using the red ASCII logo, - 80-column desktop width, - black/white light and dark themes, blue links, green prompts/actions, ASCII - form frames, and bracketed controls. Licensing links retain the main site's - amber and muted green accents. The LibreJS badge stays yellow with its - keyboard-accessible warning. Small screens keep readable form controls and - fit the ASCII rules to the available width instead of shrinking all text. - Scrolling belongs to the root viewport at the screen edge; the logo has no - inner scroll container. JavaScript licenses sit below the LibreJS badge. - No remote fonts, images, or additional scripts are required. + web/styles.css supplies red ASCII logo, 80-column desktop width, black/white + light/dark themes, blue links, green prompts/actions, ASCII frames, and bracketed + controls. License links retain amber/muted green. Yellow LibreJS badge has a + keyboard-accessible warning; JavaScript licenses sit below it. Small screens + retain readable controls and fit ASCII rules without shrinking all text. + Root viewport scrolls at the screen edge; no inner logo scrolling. + No remote fonts, images, or additional scripts. LICENSES, SOURCE, AND VERIFICATION - Portable root CLI builds now also need a musl C compiler because consistent - snapshots use bundled SQLite. The root CLI was built and executed as an - x86_64 static PIE with SQLite; no runtime SQLite package is needed on the VPS. - The local check used Alpine musl 1.2.6 headers with the host C compiler for - the C object build and Rust's self-contained musl runtime for final linking. + Bundled SQLite snapshots require a musl C compiler for portable CLI builds. + The root CLI ran as x86_64 static PIE; VPS SQLite packages are unnecessary. + Local checks compiled C with Alpine musl 1.2.6 headers/host compiler and + linked with Rust's self-contained musl runtime. The rendered dependency-license page is centralized on the main website at https://doan.luuduct.ai/THIRD-PARTY-LICENSES.html. Chat links point there; its @@ -399,16 +363,12 @@ LICENSES, SOURCE, AND VERIFICATION PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:chat PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:progress - Browser automation additionally needs Node and the root locked Playwright - development dependency, with browser downloads under .tools/browsers. See - TYPESCRIPT.txt for installation and the strict TypeScript build. These tools are - excluded from releases. The fixtures use temporary isolated installation roots - with one temporary .env each, and remove their private data on completion. - They listen on loopback port 9081 by default (override with CHAT_TEST_PORT), - leaving the normal local preview on 9080 available. The optional packaged - HTTPS variant is documented in CHAT-PRODUCTION.txt and retains its disposable - installation roots for inspection. Tests do not initialize the real owner - identity or touch the VPS. + Automation needs Node/root locked Playwright, with browsers in .tools/browsers; + see TYPESCRIPT.txt for installation/strict builds. Releases exclude these tools. + Fixtures isolate roots with one temporary .env each, deleting private data on + completion. Default loopback 9081 (CHAT_TEST_PORT overrides) leaves preview 9080 + free. Packaged HTTPS tests retain disposable roots; see CHAT-PRODUCTION.txt. + Tests neither initialize the real owner nor touch the VPS. LibreJS verification uses the Mozilla-distributed GNU LibreJS 7.21.1 in Playwright Firefox 157.0, without whitelisting the application. The full chat @@ -419,12 +379,10 @@ LICENSES, SOURCE, AND VERIFICATION https://addons.mozilla.org/firefox/downloads/file/4169076/librejs-7.21.1.xpi - Earlier tests exposed a LibreJS label mismatch: a combined GNU prefix and - or-later suffix was not recognized by that release. The table now uses its - recognized GNU-AGPL-3.0 identifier and the surrounding prose and legal headers - explicitly retain the version-3-or-later grant. Readable classic scripts and - workers are used instead of requiring module-script interception support. - This tests license recognition and operation, not the safety of delivered code. + LibreJS rejected a combined GNU prefix/or-later suffix. The table uses recognized + GNU-AGPL-3.0; prose/legal headers retain version-3-or-later. Readable classic + scripts/workers avoid module-script interception requirements. This tests + license recognition/operation, not delivered-code safety. The crypto browser probe fails closed without browser CSPRNG entropy and on unknown WASI imports. The application browser test uses real WASI/OpenSSL -- cgit v1.2.3