kcgi: A Security-Focused CGI Library for C and C++12 min read2,467 words

kcgi: A Security-Focused CGI Library for C and C++

Explore kcgi, a minimal CGI and FastCGI library for C and C++. Learn how its sandboxed request handling, setup, testing, and trade-offs work in practice.

kcgiC CGI libraryFastCGI for Csandboxed web applicationssecure C web development
kcgi: A Security-Focused CGI Library for C and C++

kcgi: A Practical Guide to Sandboxed Web Apps in C and C++

If you build a web app in C or C++, you get a lot of control - but you also inherit the mess that comes with it. HTTP input is untrusted. Parsing bugs can be nasty. And security features have to match how you actually deploy the service, not just how you wish you deployed it.

kcgi is a small CGI and FastCGI library made for exactly that situation. It parses HTTP requests and writes responses, while isolating the untrusted parsing/validation work in a sandboxed child process. The process boundary is the key design choice. This doesn't magically make your application secure; it just limits how far a parsing failure can go.

This guide covers how kcgi works, what a minimal request handler looks like, how to think about CGI vs. FastCGI, and what you should check before adopting it. The point isn't to treat a C library as a security shortcut. It's to understand what controls it gives you, what operational overhead it adds, and where your application code still has to do the real work.

What kcgi does - and what it does not do

kcgi is an open-source library for writing CGI and FastCGI web applications in C and C++. Its scope stays close to the HTTP boundary: it parses request data, exposes validated inputs, and helps you construct responses. It supports common input forms like query strings, cookies, request bodies, and multipart data. It also includes features for authentication, response caching, compression, request debugging, and formatted output writers.

That feature list matters, but it's not the real architectural point. The important bit is that untrusted network data is parsed and validated in a sandboxed child process. The parsed result is sent back to the parent over a socket, so the risky work stays separated from the process that keeps running your application.

The boundary matters more than the feature count

A request parser works on attacker-controlled bytes. Even a "good" parser can contain a bug. Validation code might also call third-party libraries that have their own failure modes. Running that work with fewer privileges and fewer available resources can reduce the impact if something goes wrong.

But this is still containment, not a guarantee. Once parsed values reach the parent, a sandbox can't fix unsafe logic in your application. It won't automatically prevent authorization mistakes, unsafe file access, injection issues, or broken response handling. You still need to validate inputs against your own rules, apply access controls, and make sure you're using the data safely.

A simple way to think about it: kcgi can help narrow the request-parsing path. Your application is still responsible for what a "valid" request is allowed to do.

When the library is a reasonable fit

kcgi is worth evaluating if you're building a C or C++ service that needs a small HTTP-facing layer and you can run a CGI or FastCGI service. It's especially relevant if you need process isolation and the supported sandboxing model matches your deployment environment.

It's less compelling if you don't have a reason to use C/C++, already use a framework that fits your team's workflow, or can't support the operational model. A lower-level library isn't automatically simpler - you may still need to understand server configuration, process supervision, static linking, and whatever sandbox behavior is available on your platform.

For a small service with modest request volume, CGI can be workable. For a long-running service or higher request rates, FastCGI avoids "start a process per request" overhead, but you have to deal with a persistent process and connection management. Pick the option you can actually run and operate reliably.

How to build a minimal kcgi application

A small CGI handler shows the basic flow: parse the request, set response headers, start the body, write content, and then free request resources. The example below follows the library's documented pattern for a single default page. It returns plain text and doesn't read any user-supplied fields.

#include <sys/types.h>

#include <stdarg.h>

#include <stddef.h>

#include <stdint.h>

#include <kcgi.h>

int

main(void)

{

struct kreq r;

const char *pages[] = { "index" };

if (khttp_parse(&r, NULL, 0, pages, 1, 0) != KCGI_OK)

return 1;

khttp_head(&r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]);

khttp_head(&r, kresps[KRESP_CONTENT_TYPE], "%s",

kmimetypes[KMIME_TEXT_PLAIN]);

khttp_body(&r);

khttp_puts(&r, "Hello, world!");

khttp_free(&r);

return 0;

}

The page list tells kcgi which page names your application recognizes - in this case, there's only one. Because the example doesn't define any input fields, it's not a template for accepting arbitrary user data. In a real handler, decide which methods and pages you accept, define the inputs you expect, and then apply your own checks before doing anything sensitive.

Build and link with the package configuration

If you install kcgi from a system package or from source, use pkg-config to get the compiler and linker flags:

cc `pkg-config --cflags kcgi` -c yourprog.c

cc yourprog.o `pkg-config --libs kcgi` -o yourprog

The package name and installation steps vary by operating system. The project documents builds for systems including OpenBSD, FreeBSD, Arch Linux, and Homebrew. Still, don't guess - check the kcgi project page and the repository instructions for the current setup.

The documented hard build-time dependencies are BSD make (often bmake on Linux) and zlib. Regression testing also needs libcurl. If you deploy inside a chroot, make sure shared libraries exist inside the chroot, or consider static linking if it fits your setup:

cc -static yourprog.o `pkg-config --static --libs kcgi` -o yourprog

Static linking is a deployment choice, not an automatic security win. It can make library availability easier in a restricted filesystem, but it increases binary size and changes how dependency updates are rolled out. Verify behavior and licensing requirements for your target platform before relying on it.

Treat the example as a lifecycle, not a full application

The order in the example matters: parse before you use request state, send headers before the body, and call khttp_free() to release resources. Use the library's documentation and man pages for the function contracts and the exact details for the version you're using. Start with the kcgi manual pages, especially the parts on request parsing and response handling.

For FastCGI, request setup splits into initialization and per-request parsing. The documented flow uses khttp_fcgi_init() to initialize context and khttp_fcgi_parse() to receive requests. You still need to free requests. Don't copy a CGI-style control flow into a FastCGI service without accounting for the fact that the process may handle multiple requests.

How kcgi isolates untrusted request handling

The security model is easiest to understand as two roles. The parser and input validator handle untrusted request data inside a sandboxed child process. The parent keeps running and receives the parsed result over a socket. In the FastCGI case, a connection delegator accepts incoming requests and passes them along under a different set of sandboxing requirements.

Sandbox support varies by operating system. The project describes platform-specific handling, including resource limits and OS sandbox facilities. That means the security properties depend partly on your platform and deployment configuration. "Uses sandboxing" isn't enough for a threat model - verify what's actually enabled where you plan to run the service.

Keep sandboxed validation self-contained

If validation happens in the sandboxed child, the validation code should avoid depending on the broader environment. In particular, avoid validation callbacks that open files, make network connections, or assume access to all process resources. Depending on the OS sandbox, those operations may fail or terminate the child.

This constraint pushes you toward validation that answers questions like: is the field present, is it represented correctly, and does it match an expected format. If later decisions require database access, filesystem checks, or other privileged work, do that in the appropriate application context after parsing, with explicit authorization and solid error handling.

This separation doesn't mean "all business logic belongs in the parent." It means you should distinguish checks that can be derived safely from request data alone from decisions that require external state.

Understand the limits of process isolation

The child process boundary can reduce exposure, but it doesn't erase the risk once the parent gets data. A parser can prove a string is syntactically valid while the value is still unsafe for your intended use. For example, validating something "as a number" doesn't make it an authorized account ID. A filename that parses as a valid path doesn't prove the caller can access that resource.

A practical review should trace each input through three steps:

  1. Parsing: Was the request field correctly interpreted and constrained?
  2. Authorization: Is this user allowed to perform this action on this resource?
  3. Use: Is the value handled safely by whatever comes next (database, filesystem, template, etc.)?

kcgi mainly addresses the parsing boundary. Your application and the surrounding system must handle the rest. That's why adopting a security-oriented library should lead to a clearer threat model - not just a shorter security review.

Deployment, testing, and performance trade-offs

kcgi isn't a web server. You still need a server that can run CGI or FastCGI, such as Apache, nginx, or OpenBSD's httpd. The process model depends on the server and configuration. With CGI, the program typically starts per request. With FastCGI, the server may start the application or connect to it separately over a socket. On OpenBSD, the project describes using kfcgi with httpd as a deployment pattern.

Before you choose a mode, write down who starts the application, how it receives requests, how it restarts after failures, and what privileges it runs with. A process model that behaves cleanly in a local test can break when you move it under a production service manager or into a chroot. Test the actual deployment path, including permissions and socket access.

Test the request boundary, not only the happy path

The project includes an automated regression framework (make regress) and a kcgiregress(3) library for testing applications. The framework acts like a small web server: it converts an HTTP request into a CGI request and sends it to a CGI client. Its internal tests use libcurl. The documented setup binds to a fixed local port, so watch for port conflicts in your environment.

The project also provides fuzzing targets for multipart, plain, and URL-encoded input using American Fuzzy Lop. Fuzzing helps because request parsers must handle malformed and unexpected data - not just what typical browser traffic looks like. Fuzzing doesn't replace application tests, but it can hit edge cases that hand-written tests miss.

For your application, test at least:

  • Missing, duplicated, oversized, and malformed fields.
  • Unsupported methods and unknown page names.
  • Invalid encodings and incomplete request bodies.
  • Multipart boundaries and unexpected file metadata, if you accept uploads.
  • Authentication and authorization failures.
  • What happens when the parser (or a downstream dependency) returns an error.

Run tests in the same OS and web-server configuration you plan to deploy. The project reports regression results across supported systems and compiler sanitization in continuous integration, but your application still needs its own coverage.

Budget for the cost of containment

Security costs CPU time and complexity. The source describes three main contributors: starting a child process to handle untrusted input, setting up the sandbox, and transferring parsed data back to the parent. With CGI, process creation and cleanup can be a big part of the overhead. With FastCGI, the data transfer and the request-handling path become more important.

There isn't a single "universal throughput" number that makes this easy to compare. Measure what you plan to run. Use realistic request sizes, concurrency levels, error cases, and the real server integration. If your workload is CPU-bound, storage-bound, or waiting on external services, parser overhead may not dominate. If it's a small, high-frequency CGI endpoint, lifecycle overhead may matter a lot more.

So the real question isn't "security vs. performance" in the abstract. It's whether the containment benefit is worth the measurable overhead in your deployment. For most teams, the sensible approach is: build the simplest supported configuration, run the regression suite, benchmark realistic traffic, then optimize only after you find the bottleneck.

Conclusion

kcgi is a focused option for C and C++ web applications that need CGI or FastCGI support and want request parsing separated from the main application process. Its core security idea is straightforward: keep untrusted parsing constrained, then pass the result across a process boundary. That reduces exposure, but it doesn't replace input validation, authorization, or operational discipline.

Evaluate it against the system you need to run. Confirm platform support and what sandboxing looks like in practice, build a small handler, test malformed inputs, and measure the actual deployment instead of guessing. If it fits your constraints, start with the official kcgi documentation and its example programs before adding more application complexity.

Frequently Asked Questions

kcgi is an open-source C and C++ library for building CGI and FastCGI web applications. It provides HTTP request parsing and response-writing features, and isolates parsing and validation of untrusted request data in a sandboxed child process.
No. Its sandboxed parsing design can limit the reach of a failure in the untrusted input path, but application code remains responsible for authorization, safe use of parsed values, dependency security, deployment configuration, and correct error handling.
Yes. kcgi supports FastCGI. Its documented flow separates initialization from parsing individual requests, and the application must manage the request lifecycle correctly. Your web server or process manager also needs to be configured to start or connect to the FastCGI service.
The project identifies BSD make (called bmake on many Linux systems) and zlib as its hard build-time dependencies. The regression tests also require libcurl. Use the package configuration supplied by your installation to obtain compiler and linker flags.
It depends on the workload and deployment. CGI starts a process per request, so process creation can be a constraint. FastCGI avoids that pattern but has its own connection and data-transfer costs. Benchmark both options under the conditions you expect to operate.

Related articles