@@ -27,6 +27,33 @@ const std = @import("std");
2727const core = @import ("core.zig" );
2828const process = @import ("process.zig" );
2929
30+ // =============================================================================
31+ // proven FFI — extern declarations matching proven.h
32+ // =============================================================================
33+
34+ /// C ABI result type for boolean operations (matches ProvenBoolResult in proven.h).
35+ /// Layout must match: struct { int32_t status; bool value; }
36+ const ProvenBoolResult = extern struct {
37+ status : c_int ,
38+ value : bool ,
39+ };
40+
41+ /// proven status codes (subset used here).
42+ const PROVEN_OK : c_int = 0 ;
43+
44+ /// Check if a byte slice contains CRLF injection characters ("\r\n").
45+ /// Implemented in verification-ecosystem/proven/ffi/zig/src/main.zig.
46+ /// Linked via -lproven_ffi in build.zig.
47+ extern fn proven_header_has_crlf (ptr : [* ]const u8 , len : usize ) ProvenBoolResult ;
48+
49+ /// Error returned when proven_header_has_crlf detects CRLF injection.
50+ pub const HeaderError = error {
51+ /// proven detected CRLF injection characters in the header.
52+ CRLFInjectionDetected ,
53+ /// proven returned an unexpected status code.
54+ ProvenStatusError ,
55+ };
56+
3057// =============================================================================
3158// Constants
3259// =============================================================================
@@ -193,12 +220,25 @@ fn idxFromHandle(handle: u64) ?usize {
193220
194221/// Write a minimal HTTP/1.1 response with a text body.
195222/// `status_code` e.g. 200, 400, 404, 503.
223+ ///
224+ /// Validates Content-Type header using safeHeaderDefault() to prevent CRLF
225+ /// injection attacks. If validation fails, the response is refused entirely
226+ /// and a 500 error is sent instead (fail-closed policy).
196227fn writeResponse (
197228 conn : * std.net.Server.Connection ,
198229 status_code : u16 ,
199230 content_type : []const u8 ,
200231 body : []const u8 ,
201232) void {
233+ // Validate Content-Type header against CRLF injection.
234+ // safeHeaderDefault returns false if CRLF is detected or proven errors.
235+ if (! safeHeaderDefault (content_type )) {
236+ // CRLF injection detected or proven validation failed.
237+ // Refuse the response entirely (fail-closed).
238+ writeInternalError (conn , "response header validation failed" );
239+ return ;
240+ }
241+
202242 // Reuse a stack buffer for the status line + headers.
203243 var header_buf : [512 ]u8 = undefined ;
204244 const headers = std .fmt .bufPrint (
@@ -228,6 +268,26 @@ fn writeInternalError(conn: *std.net.Server.Connection, msg: []const u8) void {
228268 writeResponse (conn , 500 , "application/json" , body );
229269}
230270
271+ /// Check if a header name or value is safe (no CRLF injection).
272+ /// Uses proven_header_has_crlf (formally-verified detection from libproven_ffi).
273+ /// Returns false if CRLF is detected or if proven returns an error.
274+ ///
275+ /// This is a load-bearing proven FFI call: writeResponse uses this to validate
276+ /// headers before adding them to the response. If any header fails validation,
277+ /// the entire response is refused (fail-closed policy).
278+ fn safeHeaderDefault (value : []const u8 ) bool {
279+ if (value .len == 0 ) return true ; // empty header is safe
280+ const result = proven_header_has_crlf (value .ptr , value .len );
281+ if (result .status != PROVEN_OK ) {
282+ // proven returned an error: fail closed.
283+ core .setError ("proven_header_has_crlf returned status {d} for header '{s}'" ,
284+ .{ result .status , value });
285+ return false ;
286+ }
287+ // result.value == true means CRLF detected → deny.
288+ return ! result .value ;
289+ }
290+
231291// =============================================================================
232292// Request body reading
233293// =============================================================================
0 commit comments