Old machinery can expose a clean seam.
Corey Prophitt describes a commercial web product built with OpenBSD, C, httpd, and SQLite. In the account, each request gets a fresh process. Replacing one binary changes what later requests run, without restarting an application server.[1]
That is a useful lifetime contract. Request-local memory leaves with the process. A stale global cannot leak into the next request through the same worker. The contract is narrower than "CGI is safe." The CGI specification defines how a server and script divide request work. It does not require every implementation to create a new operating-system process.[2]
Process exit clears process state. It does not validate the state that crossed the boundary.
Four checks survive the reset.
The CGI specification warns scripts to validate client data before using it in system calls, database writes, or other sensitive operations. It also warns against fixed buffers for unbounded bodies and notes that environment data may be visible to other scripts or users on some systems.[2]
- Input limits. Reject unsupported methods, content types, paths, and bodies before parsing or allocation.
- Output context. Escape text, attributes, and URLs for their actual HTML context. A clean process can print unsafe bytes.
- Shared-state behavior. Define transaction start, busy handling, retry limits, and idempotency. The database outlives every request process.
- Operating-system reach. Limit files, network operations, and process actions. Process lifetime does not reduce ambient authority by itself.
Templates need their own interlock.
Lukáš Lalinský's experimental zt template system for Zig makes dynamic values HTML-escaped by default. It applies checks to URL attributes and requires an explicit raw-output form. The compiler checks template parameter types.[3]
Those are output rules, not lifetime rules. A process-per-request C program still needs an equivalent policy. A long-lived Zig worker still needs a reset policy for state it keeps. Pick each control for the failure it can stop.
SQLite keeps a different clock.
SQLite permits several read transactions but only one write transaction at a time. A deferred read can fail with SQLITE_BUSY when it later tries to become a writer. A failed commit can leave the transaction active so the application can retry after the blocker clears.[4]
A fresh request process does not remove that contention. It can make cleanup easier because the connection dies with the process. It can also turn a missing busy policy into a burst of failed requests. Record the transaction mode, timeout, retry ceiling, and duplicate-write protection.
Reduce reach as a separate act.
OpenBSD's pledge lets a program declare groups of system operations. Later calls can reduce the allowed groups. Revoked groups cannot be restored. Most violations kill the process.[5]
That one-way reduction is stronger evidence than saying a program is small. A short C program can still open broad paths or make outbound connections if the host permits them. Keep the promise list beside the request path. Test one denied operation after the final reduction.
Write the contract before tuning speed.
REQUEST_LIFETIME=ONE_PROCESS | POOLED_WORKER | RESIDENT_SERVER
INPUT_LIMITS=METHOD + CONTENT_TYPE + BODY_BYTES + PATH_RULES
OUTPUT_POLICY=ESCAPE_BY_CONTEXT + RAW_OUTPUT_REVIEW
DATABASE_POLICY=TX_MODE + BUSY_TIMEOUT + RETRY_CEILING + IDEMPOTENCY
OS_POLICY=FILES + NETWORK + EXEC + DENIED_PROBE
RESET_PROOF=PROCESS_EXIT | WORKER_RESET_TEST | RESTART_DRILL
The airlock below builds this inspection template. It does not benchmark a stack or inspect a server. A complete panel means the template requests all four checks. It does not mean the application is safe.