Conspire is a web-based chat for radical exchange: peer to peer, ephemeral, anonymous, and synchronous.
Jump into instant rooms where voices and files move peer-to-peer, leaving no footprints. Conspire is built for privacy and digital autonomy.
Go to conspire.dyne.org and bring your friends.
The image is a runtime-only image: it has no compiler, development packages,
or certificate-generation command, and runs as the conspire user. Operators
must mount certificate files read-only; never copy a private key into an image.
The Docker build context is deliberately produced from the same CMake build
that review verifies, rather than expecting an untracked ./conspire binary:
./scripts/build-vendored-oatpp.sh
CONSPIRE_DEPS_PREFIX="$PWD/build/deps" ./scripts/build-container.shIt writes only ignored build/ and dist/container-* outputs and builds the
runtime image from that deterministic context. The complete front/ tree is
embedded into the executable's read-only data at build time, so the runtime
image cannot contain stale or mismatched web assets. The committed oatpp 1.4
sources are built locally; the incompatible public oatpp 1.3 line is never
substituted.
docker run --read-only -v conspire-state:/run/conspire \
-v "$PWD/cert:/run/certs:ro" -p 8443:8443 ghcr.io/dyne/conspire:latestNative development uses CMake 3.20+, Ninja, GCC or Clang, and OpenSSL
development headers. Plain make builds the committed oatpp 1.4.0 sources into
a toolchain-specific local prefix before building Conspire; ccache is used when
available. The public oatpp 1.3.x releases are intentionally not fetched because
their API/layout is not compatible with this source tree.
makeWhen /opt/dyne/gcc-musl/settings.cmake is installed, make produces the
musl-linked conspire-$(uname -m) artifact. Otherwise it performs a native
build. Set CMAKE_TOOLCHAIN_FILE, TARGET, or CONSPIRE_DEPS_PREFIX explicitly
to override those defaults.
The equivalent native development and test commands are:
./scripts/build-vendored-oatpp.sh
cmake --preset native-gcc -DCONSPIRE_DEPS_PREFIX="$PWD/build/deps"
cmake --build --preset native-gcc
ctest --preset native-gcc
cmake --preset native-clang -DCONSPIRE_DEPS_PREFIX="$PWD/build/deps"
cmake --build --preset native-clang
ctest --preset native-clangShared-file descriptors may carry an advisory mediaType hint. Version 1 forwards
only the exact ASCII values image/jpeg, image/png, and image/webp; absent,
malformed, or unsupported hints are treated as ordinary generic files. The hint is
never inferred from an extension and never authorizes rendering. Inline preview
admission is limited to 8 MiB encoded bytes, 16 megapixels, and 8192 pixels per
axis; a tab may retain at most three previews, 24 MiB of blobs, and 24 megapixels.
The existing 100 MiB generic file limit remains unchanged.
Recipients must explicitly select Load image for each candidate; no candidate
bytes are requested beforehand. Downloaded bytes undergo bounded container and
dimension checks before a browser decoder sees them. Only verified, non-animated
JPEG, PNG, or WebP data is assigned through a revocable blob: URL; any failure
or eviction leaves the ordinary explicit file-download action available.
SVG is excluded because it is an active-document format rather than a bounded raster container. GIF and animated WebP are excluded because animation adds unbounded frame, timing, and retained-decoder-lifetime behavior. AVIF is not enabled in this release. Before any of GIF, animated WebP, or AVIF can be added, the change needs a bounded format inspector, explicit animation/frame and memory ceilings where applicable, malformed-corpus coverage, and a cross-browser decoder security review. Browser-native image decoders remain a residual risk even after the pre-decode container checks, so invalid or failed decodes fail closed to the ordinary download action.
| Engine | Current workspace evidence | Release/manual expectation |
|---|---|---|
| Chromium (Playwright) | Automated consent, malformed-image, cancellation, 375px/1440px containment, reduced-motion, forced-colors, and 200% zoom checks. | Run in CI on the bundled Chromium channel. |
| Chrome current and previous stable | Not separately provisioned in this workspace. | Manual release smoke: Load, Cancel, Retry, Unload, and generic Download. |
| Firefox current and previous stable | Not provisioned in this workspace. | Manual release smoke with the same cases. |
| Safari current and previous stable | Not available on this Linux workspace. | Manual macOS release smoke with the same cases. |
| Edge current and previous stable | Not provisioned in this workspace. | Manual release smoke with the same cases. |
The deterministic fault coverage also verifies no request before consent, cancellation and late-response suppression, malformed/truncated rejection, reconnect-safe file transfer, and LRU cleanup. No engine-specific blocking discrepancy is currently known; a new discrepancy blocks promotion until it has an automated regression or an explicit documented browser limitation.
For a certificate-free local run, start the native build without --tls and
open http://localhost:8080:
./build/native-gcc/server/conspire-exeThe executable serves its build-time frontend from embedded read-only data and
does not need a front/ directory at runtime. Rebuild the binary after changing
anything under front/.
TLS is opt-in. Pass --tls together with certificate paths when it is needed:
./build/native-gcc/server/conspire-exe --tls --port 8443 \
--tls-key cert/privkey.pem --tls-chain cert/fullchain.pemAt startup Conspire also probes /run/tor/control, then the loopback control
port 127.0.0.1:9051. If Tor offers SAFECOOKIE (or permission-protected NULL
authentication), Conspire registers a v3 onion service on public port 80. The
homepage then exposes a Tor hidden service button and requests through that
hostname use an onion-safe WebSocket origin. The returned ED25519 key is stored
at --tor-key; by default it is onion.key beside --stats-state, or
conspire-onion.key in the working directory when no state path is configured.
Use --no-tor to disable probing. With clearnet TLS, --tor-backend-port
(default 8080) is a separate plaintext listener bound only to 127.0.0.1.
Tor registration is best-effort: an absent or inaccessible daemon is logged and does not prevent the clearnet server from starting. See the deployment guide for ControlSocket and service-account configuration.
The real-process tests start this native binary on isolated localhost ports. The protocol test connects three WebSocket clients, verifies broadcast and room history, exercises SAFECOOKIE/ADD_ONION against a fake Tor controller, verifies stable onion identity across restart, and checks clean shutdown. The Playwright test drives the served UI in three independent browser contexts and verifies the versioned title, participants, message delivery, and history without TLS:
npm ci
npx playwright install chromium
npm run test:e2e
npm run test:e2e:browserWithout the prefix, configure stops immediately with the exact prerequisite and
does not fetch a dependency. Release CI creates it from vendor/ with the
same musl toolchain used for the release artifact.
When installed, ccache is detected automatically for native builds; its
absence is safe and never changes build output. Coverage and sanitizer builds
retain their compiler flags in the cache key, so never reuse a cache entry by
manually stripping instrumentation flags. Set -DCONSPIRE_USE_CCACHE=OFF to
diagnose a build without the cache.
The dependency-independent configuration tests remain runnable while the compatible prefix is unavailable:
cmake --preset core-tests-gcc
cmake --build --preset core-tests-gcc
ctest --preset core-tests-gcc
cmake --preset core-coverage-gcc
cmake --build --preset core-coverage-gcc
ctest --preset core-coverage-gcc
npm run coverage:browserBrowser coverage enforces 70% lines and branches (the protocol helper must
remain at 85% lines); Node prints the report and fails below either threshold.
The checked-in front/chat/coverage-fixture.js deliberately leaves one export
uncovered, so this control must fail and demonstrates the gate:
node --experimental-test-coverage --test-coverage-include=front/chat/coverage-fixture.js \
--test-coverage-lines=80 --test test/coverage-fixture.test.mjsThe GCC/Clang project-target 70% line / 60% branch gate is deliberately
deferred until a compatible oatpp 1.4 prefix is supplied. The core preset is a
non-decreasing test seam, not a substitute for that future project report.
Its CTest coverage gate enforces 70% lines / 60% branches across the offline
production helpers and 85% lines per helper; a WILL_FAIL fixture proves the
same C++ gate rejects an under-covered source. CI runs and uploads these gcov
reports through that CTest preset.
Until then, the offline core gate covers the production configuration parser
and URL builders, room-history trimming and ID lookup seams, and download
filename/file-descriptor boundaries. Oatpp-dependent peer lifecycle, streaming
subscriber, DTO serialization, statistics-loop retention, and PID integration
remain explicitly deferred; the native preset refuses to configure rather than
silently reporting partial project coverage.
The following opt-in presets keep release builds free of sanitizer flags while testing the dependency-independent lifecycle seam with a fixed, recorded seed and a 20-second timeout:
cmake --preset core-asan-ubsan && cmake --build --preset core-asan-ubsan && ctest --preset core-asan-ubsan
cmake --preset core-tsan && cmake --build --preset core-tsan && ctest --preset core-tsanCONSPIRE_STRESS_SEED defaults to 424242; override it at configure time to
reproduce or extend a failure. Full server sanitizer and websocket stress runs
remain blocked until a compatible oatpp 1.4 prefix is supplied; the native
presets intentionally fail with the existing actionable prefix diagnostic
rather than silently downgrading to oatpp 1.3.
docker run -p8443:8443 ghcr.io/dyne/conspire:latest
For a local demonstration only, generate a certificate outside the image and
mount it as above:
mkdir cert
&& openssl req -x509 -nodes -days 365 -newkey rsa:2048
-keyout cert/privkey.pem -out cert/test_cert.crt
-subj "/C=NL/ST=Netherlands/L=Amsterdam/O=Dyne.org/CN=dyne.org"
&& cat cert/test_cert.crt cert/privkey.pem > cert/fullchain.pem
&& docker run --read-only -v conspire-state:/run/conspire
-v "$PWD/cert:/run/certs:ro" -p 8443:8443 ghcr.io/dyne/conspire:latest
Conspire needs reachable WebSockets and a dedicated externally reachable port.
Container deployment is supported with the mounted-certificate contract above.
## Release and maintenance policy
Every pull request and `master` push runs the same CMake/CTest coverage,
browser, static-analysis, input-pin, and runtime-contract gates. Release jobs
depend on all of those gates and default to a non-publishing dry run. A
successful `master` push with a Conventional Commit version bump publishes a
release; maintainers can also use the protected publishing dispatch.
`scripts/check-release-inputs.sh` emits SHA-256 checksums, a source SPDX SBOM,
and provenance under `dist/metadata/`. Inputs must use immutable GitHub Action
commits and image digests. Critical or high dependency/image findings block a
release; an exception must name an owner, expiry (at most 30 days), and tracking
issue in the protected release record. CI never inherits release secrets outside
the protected publish job.
Dependency updates are reviewed weekly and after security advisories. The
committed oatpp 1.4 source snapshots make native, container, and release builds
self-contained; builds deliberately refuse to fetch or substitute oatpp 1.3.x.
Before contributing: run `npm run check:web`, `cmake --preset core-coverage-gcc`,
`cmake --build --preset core-coverage-gcc`, `ctest --preset core-coverage-gcc`,
and `./scripts/run-static-analysis.sh`. See the deployment guide for mounted
certificate troubleshooting and runtime variables, and
[maintenance evidence](docs/MAINTENANCE.md) for the complete release checklist.
## π Production Deployment
For deploying Conspire on a server with TLS certificates and a custom landing page, see the [Deployment Guide](docs/DEPLOYMENT.md).
## π Monitoring
Conspire exposes statistics at `/admin/stats.json` and serves its embedded
[dashboard](dashboard/) at `/dashboard`. The dashboard uses the running
server's configured statistics endpoint and visualizes peer activity, room
usage, and system metrics.
Native runs keep statistics in memory unless `--stats-state <path>` or the
equivalent `STATS_STATE_PATH` environment variable is configured; the container
image enables it at `/run/conspire/stats.json`. With persistence enabled,
Conspire validates and restores the retained history at startup, checkpoints it
atomically every minute, and saves once more on graceful shutdown. The state
directory must be writable by the service user.
WebSocket liveness probes every 30 seconds but expires a transport only after
120 seconds without confirmed inbound traffic. Protocol v2 then retains the
logical peer, hosted files, and accepted-command IDs in memory for exactly five
minutes. A replacement transport must send `SESSION_HELLO` first; a valid
resume preserves the peer identity and replays sequenced room history. Tokens
are 256-bit bearer credentials delivered only in private `SESSION_READY`
frames, never persisted by the server, logged, or exposed to peers. Restarting
the process invalidates every session. The dashboard distinguishes heartbeat
timeouts, classified transport closures, resumptions, and final session expiry.
The browser speaks protocol v2 directly. It stores only the private resume
token and latest server sequence in per-tab `sessionStorage`; command bodies,
file capability, and its bounded unacknowledged outbox remain memory-only. On
an interrupted transport it uses full-jitter exponential reconnect (500 ms to
30 s), sends `SESSION_HELLO` before any room command, and retries a queued
chat/file-share command only after the same session resumes. A `MESSAGE_ACK`
is the delivery confirmation. A terminal command error echoes its
`clientMessageId`, removes that command from automatic retry, and marks it for
manual retry. An expired credential starts a new session and
marks the old pending command for manual retry rather than replaying it under a
different identity.
File downloads use one serialized, subscriber-scoped chunk request at a time.
After a same-page reconnect the request is repeated with its original request
ID and offset, so a lost request or response cannot advance the download twice.
A browser reload keeps chat identity but deliberately changes its in-memory file
capability: hosted `File` objects are gone, so their offers are withdrawn and
active downloads fail promptly instead of waiting indefinitely. This is not a
file-resume service; neither files nor sessions survive a server restart.
Tor onion services do not use an exit relay, but an existing client stream can
still break when a relay or rendezvous path fails. Tor does not routinely move
an established WebSocket to a new circuit. Conspire therefore treats the
WebSocket as replaceable and relies on the bounded application-level resume
described above; it does not promise uninterrupted transport.
## πΌ License
Conspire is based on [can-chat](https://github.com/lganzzzo/canchat) by Leonid
Stryzhevskyi, it is written in C++ and built with [Oat++ Web Framework](https://oatpp.io/).
This project is released under [Apache License 2.0](LICENSE).