A supported single-process Python asyncio broadcast server for newline-delimited JSON (NDJSON) chat messages. The server provides bounded framing, optional TLS, first-frame token authentication, input validation, audit logging, and backpressure-aware fan-out.
The supported deployable is server.py: a process-local asyncio broadcast server. It is not a horizontally scaled chat service, durable message store, or an end-to-end production deployment. Files under experiments/ are unsupported work in progress and are outside the supported runtime and verification scope.
- Python 3.11 or later
- An
AUTH_TOKENvalue for the default authenticated mode - Docker, optionally, to build the repository image
git clone https://github.com/CoreyLeath-code/TrojanChat.git
cd TrojanChat
python -m venv .venv
# Linux/macOS
source .venv/bin/activate
# PowerShell
# .venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt
# Required by default. Choose a strong value outside source control.
export AUTH_TOKEN='replace-me'
# PowerShell: $env:AUTH_TOKEN = 'replace-me'
python server.pyBy default the server listens on tcp://0.0.0.0:8888. Configure HOST, PORT, LOG_LEVEL, MAX_MESSAGE_BYTES (default 65536), and DRAIN_TIMEOUT_S (default 5) through the environment. TLS is enabled only when both TLS_CERT_FILE and TLS_KEY_FILE are configured; otherwise the server logs that TLS is disabled.
Every message is one UTF-8 JSON object terminated by \n. With the default REQUIRE_AUTH=true, the first frame must contain the configured token:
{"token":"replace-me"}Subsequent client frames must include user and text fields, for example:
{"user":"display-name","text":"hello"}The server validates and sanitizes the payload, but does not trust the client-provided user value for broadcasts. Broadcast events use the server-bound identity (AUTH_IDENTITY, default authenticated) and include a UTC ISO-8601 timestamp. Malformed frames are audited and skipped; oversized frames, failed authentication, or slow/disconnected writers are disconnected.
flowchart LR
Client["TCP client"] --> TLS{"TLS certificate and key configured?"}
TLS -->|yes| TLSListener["TLS asyncio listener"]
TLS -->|no| PlainListener["TCP asyncio listener"]
TLSListener --> Auth["First-frame token check"]
PlainListener --> Auth
Auth -->|valid| Frame["Bounded NDJSON frame reader"]
Auth -->|invalid| Reject["Audit and close"]
Frame --> Validate["Validate and sanitize payload"]
Validate --> Broadcast["Snapshot fan-out with drain timeout"]
Validate --> Audit["Security audit log"]
Broadcast --> Peers["Authenticated peer writers"]
sequenceDiagram
participant C as Client
participant S as Asyncio server
participant A as Security manager
participant P as Peer clients
C->>S: {"token":"..."}\n
S->>S: constant-time token comparison
alt token invalid
S->>A: audit authentication rejection
S-->>C: close connection
else token valid
S->>S: bind server-side identity
C->>S: {"user":"...","text":"..."}\n
S->>A: validate and sanitize
S->>S: add UTC timestamp and bound identity
S->>P: NDJSON broadcast (drain timeout)
opt slow or disconnected peer
S->>S: remove and close peer
end
end
Run the supported checks from a clean checkout after installing requirements-dev.txt:
ruff check .
pytest -q
python -m benchmarks.run_benchmark --output benchmarks/latest.json
python -m pytest tests/test_benchmark.py -q
python -m json.tool benchmarks/latest.json
docker build -t trojanchat:local .The CI workflow runs the test suite with coverage. The benchmark workflow reruns the storage microbenchmark, verifies that the generated throughput change is no worse than its predeclared -15% budget, and uploads benchmarks/latest.json and benchmarks/benchmark_report.md as artifacts. The security-and-supply-chain workflow runs secret scanning, filesystem and container scanning, and produces a CycloneDX SBOM artifact.
For comparable results, record the commit SHA, command, Python version, operating system, CPU/memory characteristics, benchmark parameters, and the generated JSON artifact. Do not compare host-to-host values as a regression result without matching those conditions.
The committed artifact benchmarks/latest.json records a bounded in-process storage microbenchmark, not network, TLS, JSON-serialization, Redis/database, multi-process, RSS, or production-SLO performance. It was generated on Windows 11 with Python 3.12.13 using seven iterations of 50,000 messages and a retention limit of 10,000.
| Measure | Legacy list baseline | Bounded synchronized store | Observed change |
|---|---|---|---|
| Median latency per 50,000 writes | 1,155.519 ms | 1,239.880 ms | +7.3% |
| Throughput | 43,270.60 messages/s | 40,326.50 messages/s | -6.8% |
| Peak Python allocations | 21.205 MiB | 4.228 MiB | -80.06% |
Method. Each iteration inserts structurally identical messages; the benchmark uses time.perf_counter for elapsed time and tracemalloc for Python allocations. The comparison is useful only for the stated storage implementation and environment.
Interpretation. This artifact supports a lower-allocation bounded-retention trade-off within the declared throughput budget. It does not establish client capacity, end-to-end latency, security effectiveness, availability, or suitability for any safety-critical use.
- Authentication: enabled by default; the server rejects connections when
AUTH_TOKENis missing or incorrect. SetREQUIRE_AUTH=falseonly for explicitly controlled development use. - TLS: optional at runtime, not automatic. Set both certificate environment variables before exposing the listener to untrusted networks.
- Backpressure: each peer write is bounded by
DRAIN_TIMEOUT_S; timed-out or disconnected peers are dropped to prevent one client blocking a broadcast. - Scale: connection and identity state are process-local. A cross-instance broker and explicit delivery semantics would be required before horizontal scaling.
- Persistence: messages are not durably stored by the supported server.
NDJSON gives each message an explicit frame boundary. The server reads until a newline with a configured stream limit, allowing it to reject oversized frames instead of treating arbitrary chunk boundaries as messages.
No. The field is still required by the current payload validator, but the server binds the broadcast identity after successful authentication and emits that server-side value in outgoing events.
The server fans out over a snapshot of active writers. Each drain() call has a timeout; connection failures and timeouts remove and close only the affected peer while the remaining fan-out continues.
No. It is opt-in through TLS_CERT_FILE and TLS_KEY_FILE. A startup warning makes the plaintext mode visible; deployers are responsible for enabling TLS where the threat model requires it.
No. They measure only the documented in-process storage experiment. Reproduce the benchmark and publish a separate, versioned end-to-end experiment before making network or capacity claims.
This project is available under the MIT License.