Production-edge threat model¶
Status: accepted hostile-input inventory for EDGE-002 as of 2026-07-11.
Implementation and release assurance remain assigned to the task IDs below.
This model covers servery acting as the public TLS endpoint for one static site or local Python application without nginx, Caddy, or an external process manager. It describes current controls honestly; a listed control is not proof that a surface is ready for hostile production traffic. The production claim is earned only when the follow-up task and release gates pass against the packaged artifact.
Assets and trust boundaries¶
The assets are availability, response and file integrity, confidentiality of the served root and credentials, TLS private keys and ACME account state, correct client identity, application isolation from malformed wire input, and bounded CPU, memory, descriptors, processes, threads, queues, and disk.
Data crosses these boundaries:
- Public network to protocol parser. Every TCP byte, UDP datagram, TLS handshake, HTTP field, URL, WebSocket frame, and HTTP/2 or HTTP/3 control frame is attacker-controlled.
- Protocol layer to application or filesystem. Normalized request data becomes a path, WSGI/ASGI event, CGI environment, proxy request, upload, WebDAV operation, archive selection, or listing filter.
- Server to upstream authority. ACME directories and reverse-proxy targets are separate network principals. Their JSON, headers, certificates, status, timing, and failures are untrusted input.
- Configuration and local state to privileged runtime. Configuration, application import strings, certificate/key files, password files, the served tree, and ACME cache are trusted only to the degree that their owner, permissions, provenance, and atomicity are verified.
- Worker to parent and old generation to new generation. Exit status, readiness, metrics, inherited descriptors, and replacement state can be stale, partial, or malicious if operator-loaded application code is compromised.
The socket peer is the client identity. Forwarding headers have no authority by default. WSGI, ASGI, and CGI applications are operator-trusted code, but their exceptions, cancellation behavior, response fields, and resource consumption are not trusted to preserve server availability.
Non-configurable invariants¶
- Ambiguous HTTP framing, invalid field syntax, invalid protocol state, path escape, partial committed writes, unvalidated certificate activation, and forwarding-header trust fail closed. No production option weakens them.
- Attacker-controlled sizes, counts, rates, recursion, expansion, queues, and retention have finite production limits. A larger operator-selected limit is policy; an unbounded production default is not.
- Filesystem mutation uses containment checks, same-target coordination, private temporary state, and atomic replacement. A path checked before an attacker-controlled blocking interval is revalidated against the opened or destination identity where the platform permits it.
- A malformed connection is closed or reset at the narrowest safe boundary. Healthy clients continue when a stream can be isolated.
- Secrets, raw URLs/query strings, filenames, addresses, application messages, and exception text never become metric labels or stable reason codes.
- Optional HTTP/3 does not weaken the core release gate. TFTP, CGI, anonymous writes, self-signed TLS, and public administration are rejected by the first production profile unless explicitly removed from that profile's claim.
Public parser and expensive-operation inventory¶
“Signal” names either a stable code from the
observability vocabulary or the bounded
status/close evidence available today. Codes not implemented yet are owned by
EDGE-040; attack-shaped verification is owned by EDGE-062 or the named
release gate.
| Surface and boundary | Threat to assets | Current disposition/control | Missing control or decision | Signal | Verification owner |
|---|---|---|---|---|---|
| HTTP/1 request line, fields, Host, framing, chunked body | request smuggling, oversized heads, slowloris, parser disagreement, connection pinning | shared strict field/Host/framing policy; 64 KiB per-line and 100-field ceilings; conflicting or invalid length rejected; socket, keep-alive, total-head, total-body, write, and request-count policies exist | unify finite production defaults; add combined header-byte admission and equivalent backend behavior (EDGE-030) |
400/414 or close today; request_head_timeout, request_body_timeout, connection_capacity |
EDGE-030 boundary tests; EDGE-062 HTTP/1 corpus; EDGE-061 wire interop |
| HTTP/2 frame codec, HPACK, stream state and flow control | HPACK expansion/table abuse, continuation floods, rapid resets, control-frame CPU amplification, stream starvation, buffered-body exhaustion | maximum frame, encoded block, decoded header-list and dynamic-table sizes; continuation, reset and acknowledgement-triggering control budgets; finite advertised/enforced streams; fair response-data pass; protocol errors close/reset | make abuse budgets cohesive/configurable; cover every control-frame class, per-stream/body buffers, global/client rates, and cross-client fairness (EDGE-031) |
h2_stream_capacity, h2_rapid_reset, h2_control_rate, transport reset |
EDGE-031 attack replays; EDGE-062 HPACK/H2 corpus; EDGE-061 h2spec/nghttp2 |
| TLS ClientHello, certificate/key loading and ALPN | handshake CPU/descriptor flood, downgrade or advertising unsupported protocols, malformed/mismatched/expired material, key disclosure | Python ssl performs wire parsing; ALPN advertises only configured live protocols; password-file support; invalid material normally fails startup |
production crypto/store decision, private atomic state, cert/key/SAN/expiry validation (EDGE-050); admission budgets (EDGE-030); live activation (EDGE-052) |
handshake transport error today; invalid_material, activation_failure, certificate_expired |
EDGE-050 malformed-state tests; EDGE-052 sustained handshakes; EDGE-061 pinned TLS scan |
Optional QUIC/HTTP/3 parser (aioquic) |
UDP flood, QUIC amplification, stream/control exhaustion, dependency vulnerabilities | isolated optional dependency; live UDP endpoint alone emits Alt-Svc; separate stream limit and bounded large-file streaming |
pin and report dependency closure (EDGE-060); H3 stream/control/amplification policy (EDGE-031); explicit restart on TLS change (EDGE-052) |
h3_stream_capacity, transport error |
EDGE-031; isolated H3 cohort in EDGE-061; package audit in EDGE-060 |
| ACME directory, nonce, JSON/JWS, challenge and returned PEM | malicious/compromised CA response, replay/state confusion, issuance storms, corrupt cache, challenge hijack, renewal outage | HTTPS CA connection, ACME v2 flow, staging default, cached reuse, HTTP-01 token validation | audited crypto/store choice (EDGE-050); expiry-driven singleton scheduler, bounded retry/jitter and challenge service (EDGE-051); atomic activation (EDGE-052) |
challenge_failure, ca_rejected, network_failure, invalid_material, certificate_expired |
EDGE-050; fake-clock/mock-CA cases in EDGE-051; failure soak in EDGE-063 |
| WebSocket upgrade and frame parser | payload allocation, fragmented-message accumulation, ping/message flood, invalid UTF-8/opcodes, slow peer, application fanout amplification | handshake validation; 16 MiB frame and reassembled-message cap; client masking and control-frame rules; UTF-8 check; transport write deadline and graceful close support | configurable production message/fragment-count limits, queue and rate budgets, ping/idle policy, fanout fairness (EDGE-031); cross-backend parity (EDGE-022) |
websocket_message_capacity, write_timeout, deadline_websocket |
EDGE-031 attack replay; EDGE-062 frame corpus; Autobahn/slow-consumer EDGE-061 |
| Multipart headers, boundary scanner, filenames and upload stream | malformed boundary/header ambiguity, path injection, memory/disk exhaustion, many partial uploads, overwrite race | custom streaming parser; bounded total upload; sanitized basename; private temp plus atomic replace; overwrite denied by default; same-target lock; bounded partial count and lazy TTL | bound part count, per-part headers and boundary-search work; global disk/expensive-work budgets; multi-worker coordination (EDGE-030, EDGE-021, EDGE-013) |
413/400/409 today; expensive_work_capacity, request_body_timeout |
EDGE-030; multipart corpus in EDGE-062; disk/failure soak in EDGE-063 |
Resumable Content-Range upload state |
sparse/overlapping chunks, sidecar exhaustion, stale-state collision, cross-worker race | ordered contiguous chunks; maximum final size; same-target in-process lock; partial-count ceiling; TTL; atomic final commit | parent/singleton or filesystem-safe cross-worker ownership (EDGE-013); global disk quotas (EDGE-030); crash consistency (EDGE-063) |
409/413/507 today; expensive_work_capacity |
EDGE-013 concurrent workers; EDGE-062 range/state corpus; EDGE-063 crash/disk-full injection |
WebDAV XML, Depth, Destination, lock tokens and write methods |
XML CPU/memory abuse, traversal, huge enumeration, destructive unauthenticated writes, lock-table exhaustion, cross-worker inconsistency | write mode opt-in; shared containment/atomic writes; Destination containment; Depth-1 entry cap; in-memory enforced locks; read-only class 1 available |
bound XML bytes/nesting/property count and lock table; coordinate or reject multi-worker writable DAV; production profile forbids unsafe combinations (EDGE-030, EDGE-013, EDGE-070) |
400/403/423/507; expensive_work_capacity |
WebDAV corpus EDGE-062; cross-worker EDGE-013; production config EDGE-070 |
| Uploaded ZIP/TAR inspection and extraction | zip/tar bombs, path traversal, symlink/device escape, huge member count, decompression CPU/disk exhaustion, partial tree | opt-in; validates members without extractall; rejects unsafe kinds/paths and excessive expansion; same-target coordination |
one cohesive CPU/disk/member/ratio budget, bounded offload, crash-atomic directory semantics (EDGE-021, EDGE-030, EDGE-063) |
rejection today; compression_expansion, expensive_work_capacity |
archive corpus EDGE-062; mixed-work gate EDGE-021; disk/crash soak EDGE-063 |
| Download archive selection and TAR.GZ/ZIP generation | directory walk amplification, symlink/TOCTOU disclosure, compressor CPU, slow-reader worker pinning | selection is contained; symlinks skipped; output streams with valid framing; write-progress timeout available | snapshot/opened-identity policy, file/member/byte and concurrent archive budgets, bounded blocking pool (EDGE-021, EDGE-030) |
expensive_work_capacity, write_timeout, work class archive |
EDGE-021 mixed load; archive corpus/invariants EDGE-062; scaling EDGE-064 |
| Static compression and encoded cache | compression CPU/memory amplification, cache-key confusion/poisoning, stale representation, eviction races | MIME/size eligibility; max_compress_size; byte-bounded optional cache; key includes canonical path, size, mtime, coding, level; Vary and coding-specific ETag |
global expensive-work concurrency and single-flight bounds across workers; adversarial cache/eviction tests (EDGE-021, EDGE-030) |
compression_expansion, expensive_work_capacity, cache result |
EDGE-021; cache corpus/concurrency EDGE-062; performance EDGE-064 |
| Integrity digest | attacker forces full-file reads for range/HEAD-like work, CPU/I/O amplification, stale digest after replacement | opt-in negotiation; digest uses opened file identity; bounded cache; hashing can be offloaded in selector prototype | shared bounded digest queue/concurrency, cancellation and cross-worker cache policy (EDGE-021, EDGE-030) |
expensive_work_capacity, cache result |
EDGE-021 mixed work; identity/race corpus EDGE-062; EDGE-064 |
| Directory listing/query parser and WebDAV enumeration | huge directories, pathological sort/filter/query inputs, HTML injection, metadata race | escaped rendering; query parsing; finite scanned/matched/rendered limits and pagination; PROPFIND cap |
move blocking scan/render behind bounded scheduler; global expensive-work budget (EDGE-021, EDGE-030) |
507 or request rejection; expensive_work_capacity |
EDGE-021; listing/path corpus EDGE-062; EDGE-064 |
| URL, percent decoding, query, Range/conditional/auth fields and filesystem path translation | traversal, double decoding, NUL/control injection, symlink race, open redirect, cache-validator confusion, auth timing leak | common URL split; separator-aware realpath containment; symlink escape denied by default; strict single-range and conditional selection; escaped output; constant-time credential comparison | opened-identity operations consistently across every backend; trusted identity only under explicit CIDRs (EDGE-022, EDGE-032); TOCTOU failure injection (EDGE-063) |
400/403/404/416; access status without raw path label |
path corpus EDGE-062; identity tests EDGE-032; race soak EDGE-063 |
| Reverse-proxy request/response parser and configured upstream URL | SSRF by configuration, hop-by-hop/framing confusion, credential leakage, upstream stall, cache poisoning, unsafe retry/replay, response amplification | routes are operator configuration; only HTTP(S) upstream; prefix dispatch; client authorization stripped; hop-by-hop fields filtered; ordinary request/body/write limits | strict upstream response framing/size/deadlines; bounded proxy concurrency; DNS/rebinding decision; no retries/cache in direct-edge v1 (EDGE-030, EDGE-022) |
work class proxy; request_body_timeout, write_timeout, expensive_work_capacity |
proxy differential/corpus EDGE-062; integration EDGE-022; failure soak EDGE-063 |
| TOML/CLI/environment, app import string, secrets and effective config | malicious or accidental unsafe combination, secret disclosure, partial reload, import side effects before validation, parser resource abuse | immutable typed Config; many invalid combinations fail; core CLI parsing is stdlib |
typed schema, unknown-key rejection, precedence, redaction, secret references and validation before bind/import (EDGE-003, EDGE-041); atomic generation reload (EDGE-014) |
invalid_config, startup failure; redacted event only |
schema tests EDGE-003/EDGE-041; config corpus EDGE-062; reload injection EDGE-063 |
| WSGI/ASGI event and response-field adapters | application blocks, allocates, suppresses cancellation, emits invalid framing/fields, leaks exception/secret, or exhausts background work | strict ASGI response state/framing; body and write deadlines; lifespan startup gate; active-work registry and drain deadline | bounded application admission/execution and selector parity (EDGE-021, EDGE-022, EDGE-030); broader framework/slow-consumer gates (EDGE-061) |
worker_queue_capacity, write_timeout, cancellation_suppressed, deadline_application |
EDGE-021/EDGE-022; framework cohort EDGE-061; soak EDGE-063 |
| CGI process and response parser | arbitrary trusted-code execution, process fork flood, environment/header injection, child hang/output amplification | explicit opt-in directory and contained script resolution; not a production-profile application mode | production profile rejects CGI (EDGE-070); if later claimed, it requires process/count/output/time budgets under EDGE-030 and a dedicated corpus |
startup rejection in production; otherwise worker_queue_capacity/timeout |
exclusion test EDGE-070; future parser work must enter EDGE-062 |
| TFTP request/options/path parser and UDP transfer state | spoofing/reflection, amplification, traversal, transfer flood, anonymous overwrite, retransmit storm | explicitly LAN-only; off by default; writes separately opt-in; contained paths; finite transfer ceiling and retransmission timeout | production profile rejects TFTP (EDGE-070); it is not part of the public-edge claim |
startup rejection in production; connection_capacity if used outside it |
exclusion test EDGE-070; existing TFTP regression suite |
Cross-cutting attack scenarios¶
| Scenario | Required disposition | Observable evidence | Owning tasks |
|---|---|---|---|
| Slow head, body, idle peer, or response reader | Distinct finite production deadlines release the relevant connection, stream, task, and queue slot without extending total head/body time on trickle progress. | request_head_timeout, request_body_timeout, keepalive_timeout, or write_timeout; active gauges return to baseline |
EDGE-030, EDGE-031, EDGE-063 |
| Connection, request, stream, or client flood | Layered global and per-client admission uses bounded state; overload returns 429 or 503 when safe and never queues without a ceiling. |
connection_capacity, request_rate, client_rate, worker_queue_capacity, h2_stream_capacity |
EDGE-030, EDGE-031, EDGE-040, EDGE-064 |
| Rapid reset/control-frame flood | Charge control work before doing it, bound per-connection and global rates, isolate the abusive stream when safe, and prove unrelated clients progress. | h2_rapid_reset, h2_control_rate; CPU and healthy-client latency remain bounded |
EDGE-031, EDGE-062, EDGE-064 |
| Compression, digest, listing, archive, proxy, or application amplification | Admit from separate bounded expensive-work capacity; cancel abandoned work; preserve a cheap-request lane; cap retained output/cache bytes. | expensive_work_capacity, compression_expansion, queue depth/capacity and cache result |
EDGE-021, EDGE-030, EDGE-040, EDGE-064 |
| Upload/partial/archive disk exhaustion | Reject declared excess early, meter actual expansion and temp/partial state, survive disk-full without a visible partial destination, and recover quota after cleanup. | 413/507, expensive_work_capacity; no partial committed file |
EDGE-030, EDGE-062, EDGE-063 |
| Symlink, rename, overwrite, and cross-worker race | Contain the opened/final identity, serialize the decision and commit across supported workers, and use atomic replace; fail closed where platform guarantees are insufficient. | explicit conflict/forbidden status; no outside-root or partial artifact | EDGE-013, EDGE-022, EDGE-062, EDGE-063 |
| Cache poisoning or stale representation | Key every negotiated representation by all selectors and opened identity; bound cache; invalidate atomically; never share proxy and compression caches accidentally. | cache hit/miss/reject plus exact response hash/ETag/Vary | EDGE-021, EDGE-030, EDGE-062 |
| Worker crash loop or poisoned generation | Parent gates readiness, uses bounded exponential restart backoff and a crash budget, keeps the last ready generation, and exposes terminal failure rather than busy-looping. | unexpected_exit, startup_failure, restart count, worker states |
EDGE-012, EDGE-013, EDGE-014, EDGE-063 |
| Failed, corrupt, or late certificate renewal | Keep a still-valid identity active, retry with jitter/backoff, reject invalid material, alert before expiry, and make readiness false at the defined terminal safety point. | ACME reason codes and minimum expiry gauge | EDGE-050, EDGE-051, EDGE-052, EDGE-063 |
| Log/metric injection or cardinality exhaustion | Fixed labels/reasons only, structured escaping and redaction, bounded handoff with observable drop/block/stderr policy. | servery_log_events_dropped_total; no attacker text in labels |
EDGE-040, EDGE-062, EDGE-063 |
| Reload/shutdown race | Validate before bind/import, atomically select a generation, stop admission, drain registered work, and force only at the configured deadline. | reload result, lifecycle state, forced-drain reason | EDGE-010, EDGE-014, EDGE-063 |
Verification ownership and release effect¶
EDGE-062 owns a bounded harness, seed corpus, mutation job, reproducer, and
regression case for every parser row above that is in the public-edge claim.
EDGE-063 owns blocking, crash, disk, clock, descriptor, and race injection.
EDGE-061 owns independent protocol and framework peers. EDGE-064 owns proof
that admission and expensive-work controls preserve progress under controlled
load. EDGE-065 makes unresolved crashes, hangs, unexplained skips, corpus
regressions, and expired security waivers release-blocking.
A surface cannot disappear from the matrix merely because it is hard to fuzz.
It must instead be rejected by production configuration and tested as rejected,
as with CGI and TFTP in EDGE-070. General proxy pools and shared proxy caching
remain non-goals; adding either requires a new threat-model row before code.
Supported versions and vulnerability response¶
The public reporting and support policy is in the repository
SECURITY.md. In
summary:
- Only versions explicitly listed there are security-supported. The current direct-edge work is pre-release and carries no production-readiness claim.
- A production-edge release must test every supported CPython minor and platform
in package metadata; a newly released interpreter is not production-supported
merely because
Requires-Pythonpermits installation. - Vulnerabilities are reported privately, acknowledged and triaged to published targets, fixed on supported lines according to severity, and disclosed with affected versions, mitigations, and artifact provenance.
- CPython, OpenSSL, the operating system, and optional
aioquicremain separate upstream security boundaries. Operators must run supported patched versions; servery does not silently vendor their fixes.
Acceptance disposition¶
Every public parser and expensive operation has an asset/trust boundary, current
control, named remaining task, bounded signal, and verification owner. Every
cross-cutting threat named by EDGE-002 has an explicit disposition. Unknown
implementation sufficiency is not recorded as “safe”; it is assigned to
EDGE-021, EDGE-030, EDGE-031, EDGE-040, EDGE-050..052, or
EDGE-060..065, while intentionally unsupported production modes are assigned
to EDGE-070.