Skip to content

Latest commit

 

History

570 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TrojanChat

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.

Latest Release CI Security & supply chain Benchmarks License Python Protocol

Scope and status

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.

Quick start

Prerequisites

  • Python 3.11 or later
  • An AUTH_TOKEN value for the default authenticated mode
  • Docker, optionally, to build the repository image

Run locally

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.py

By 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.

Wire protocol

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.

Architecture

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"]
Loading

System design flow

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
Loading

Reproducibility and verification

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.

Research-style benchmark evidence

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.

Operational considerations

  • Authentication: enabled by default; the server rejects connections when AUTH_TOKEN is missing or incorrect. Set REQUIRE_AUTH=false only 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.

Extended questions and answers

Why use NDJSON rather than arbitrary socket reads?

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.

Does the client-selected user field determine the broadcast identity?

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.

What happens when a peer is slow or disconnects during a broadcast?

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.

Is TLS required?

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.

What security property does first-frame token authentication provide?

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.

Why is the client-provided username still accepted if the server does not trust it as identity?

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.

Are messages persisted if the server restarts?

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.

What delivery guarantee does TrojanChat provide?

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.

Can the server scale horizontally across multiple instances?

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.

Does backpressure handling guarantee the server cannot be overloaded?

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.

Are malformed and oversized messages handled safely?

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.

Do the benchmark numbers prove real-world chat performance?

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.

Why does the bounded store trade some throughput for lower measured allocations?

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.

Does a green CI, security scan, or SBOM mean TrojanChat is production secure?

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.

What would be required before describing TrojanChat as a production chat service?

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.

Why keep experiments/ outside the supported runtime contract?

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.

What is the most defensible claim this repository makes today?

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.

License

This project is available under the MIT License.

About

TrojanChat is a terminal-based chat application designed for USC football fans to connect and chat in real time. Built with modular, object-oriented architecture.

Topics

Resources

Contributing

Security policy

Stars

7 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages