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 TCP chunk boundaries as complete 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. This prevents a client from selecting an arbitrary broadcast identity after authentication.
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. This limits the ability of one slow writer to stall every connected peer.
No. It is opt-in through TLS_CERT_FILE and TLS_KEY_FILE. A startup warning makes plaintext mode visible. Deployers are responsible for enabling TLS when credentials or messages cross an untrusted network.
It provides a simple shared-secret admission check before a connection is accepted into the authenticated peer set. The comparison is performed in constant time, and failed authentication is audited and disconnected. It is not a complete identity platform: there are no per-user credentials, token rotation protocol, authorization roles, account recovery flows, or external identity-provider integration in the supported server.
It remains part of the current message schema for compatibility and validation, but authorization and emitted identity are intentionally separated from that field. A future protocol revision could remove or redefine the client field without weakening the current server-side identity boundary.
No. The supported chat server does not provide durable message history. Connection state and broadcast state are process-local, and a restart loses that transient state. Durable chat history would require a defined storage model, retention policy, schema/versioning rules, recovery behavior, and tests around those guarantees.
The supported implementation provides best-effort process-local fan-out to peers that are connected and writable at broadcast time. It does not claim exactly-once, at-least-once, replay, durable acknowledgement, ordered delivery across multiple processes, or offline delivery. Those guarantees would require explicit message identifiers, persistence or a broker, acknowledgement semantics, retry rules, and idempotency handling.
Not without additional architecture. Each process owns its own peer set and has no cross-instance message bus. Horizontal scaling would require a shared broker or pub/sub layer, cross-instance identity/session decisions, delivery semantics, health/readiness behavior, observability, and failure tests for broker loss and partial instance failure.
No. The drain timeout protects the broadcast path from one slow writer indefinitely blocking progress, but it is not a complete admission-control or overload-management system. Production-scale overload protection would also consider connection limits, per-client rate limits, queue bounds, CPU/memory saturation, accept-loop pressure, load shedding, and representative concurrent load tests.
The supported framing and validation path bounds message size, validates JSON/message shape, audits malformed inputs, and disconnects on configured oversize conditions. These controls reduce obvious parser and memory-pressure risks, but they do not establish that the service is secure against every malicious-input strategy or denial-of-service scenario.
No. They measure only the documented in-process bounded-storage experiment. The benchmark excludes sockets, TLS, serialization costs, concurrent clients, process scheduling, external storage, proxies, and network variability. A network-capacity claim would require a separate versioned end-to-end load test with concurrency, p50/p95/p99 latency, throughput, errors, saturation, and resource observations.
The reference benchmark compares a legacy unbounded/list-oriented path with a synchronized bounded-retention implementation. In the recorded environment, the bounded design reduced peak traced Python allocations substantially while staying inside the predeclared throughput-regression budget. That is an engineering trade-off for the measured storage path, not a universal claim that the bounded implementation is faster.
No. CI, static analysis, secret scanning, image/filesystem scanning, and an SBOM are useful assurance mechanisms, but none proves the absence of vulnerabilities or operational risk. They demonstrate that specific automated checks ran against a specific revision and should be interpreted with the threat model and runtime boundaries documented here.
At minimum: a defined identity and authorization model; TLS/key lifecycle management; durable or explicitly brokered delivery semantics; horizontal-scale architecture; rate limiting and admission control; representative concurrent load and soak testing; richer runtime metrics and alerting; deployment/rollback procedures; backup/recovery rules if persistence is added; and incident-oriented failure tests for dependency, resource, and network degradation.
Separating experimental code from the supported deployable prevents prototypes from silently expanding the repository's claims. A feature should move into the supported surface only after its dependencies, configuration, tests, security implications, failure behavior, documentation, and release path are explicit.
TrojanChat is a tested single-process asyncio NDJSON broadcast-server reference with bounded framing, shared-token admission, server-bound broadcast identity, optional TLS, audit logging, backpressure-aware fan-out, reproducible verification, and versioned engineering artifacts. It is deliberately not represented as a durable, horizontally scaled production messaging platform.
This project is available under the MIT License.