From 05df40eecbea1faaa9fc6d6c26e67b68e65e0039 Mon Sep 17 00:00:00 2001 From: BaaRai <130491490+BaaRai@users.noreply.github.com> Date: Tue, 10 Nov 2026 12:52:56 +0100 Subject: [PATCH] test(verification): lock that an empty bound body never satisfies a quote A response captured with Content-Encoding: gzip and no body (HEAD, 304) is decoded to an empty body without error. Pin that behaviour, and pin that an empty body, compressed or not, matches no non-empty quote through readBoundResponse and quoteInBodies, so it can never lead to a verified finding. --- server/finding_verification_decode_test.go | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/server/finding_verification_decode_test.go b/server/finding_verification_decode_test.go index 7566bc0..2666744 100644 --- a/server/finding_verification_decode_test.go +++ b/server/finding_verification_decode_test.go @@ -222,3 +222,64 @@ func TestDecodeBoundResponseBadGzipIsAnError(t *testing.T) { t.Fatalf("br: got %+v err=%v, want unsupported content-encoding", bb, err) } } + +// A response captured with Content-Encoding: gzip and no body at all (HEAD, +// 304) is not a decode failure: it stays an empty body, without error. +func TestDecodeEvidenceBodyEmptyBodyWithEncodingStaysEmpty(t *testing.T) { + for _, head := range []string{ + decodeGzipHead, + "HTTP 304\r\nCONTENT-ENCODING: GZIP\r\n", + "HTTP 200\nContent-Encoding: deflate\n", + } { + for _, raw := range [][]byte{nil, {}} { + out, truncated, err := decodeEvidenceBody(head, raw, verificationReadCap) + if err != nil || truncated || len(out) != 0 { + t.Fatalf("head %q: out=%q truncated=%v err=%v, want an empty body and no error", head, out, truncated, err) + } + } + } +} + +// An empty bound body, compressed or not, is read without error but can never +// satisfy a quote. In assessFindingVerification a quote that matches no body +// only leads to unverified or not_assessed, never verified, so this is what +// keeps an empty response (HEAD, 304) from verifying a finding. +func TestDecodeBoundResponseEmptyBodyNeverMatchesQuote(t *testing.T) { + cases := []struct { + name string + head string + raw []byte + }{ + {"gzip header, no bytes", decodeGzipHead, nil}, + {"deflate header, no bytes", "HTTP 200\nContent-Encoding: deflate\n", nil}, + {"identity header, no bytes", "HTTP 200\nContent-Encoding: identity\n", nil}, + {"no encoding header, no bytes", "HTTP 304\nContent-Type: text/html\n", nil}, + {"gzip stream of an empty payload", decodeGzipHead, gzipBody(t, nil)}, + } + quotes := []string{ + decodeQuote, + "x", + // Present in the response head: the head is not searchable text. + "Content-Encoding: gzip", + "HTTP 200", + } + for _, c := range cases { + store, snap := decodeSnapshot(t, c.head, c.raw) + bb, err := readBoundResponse(store, snap) + if err != nil { + t.Fatalf("%s: %v", c.name, err) + } + if bb.text != "" || bb.nonText || bb.truncated { + t.Fatalf("%s: got %+v, want an empty text body", c.name, bb) + } + for _, q := range quotes { + nq := normalizeEvidenceText(q) + if nq == "" { + t.Fatalf("quote %q normalizes to nothing", q) + } + if quoteInBodies(nq, []boundBody{bb}) { + t.Fatalf("%s: quote %q matched an empty body", c.name, q) + } + } + } +}