# AI Agent Project Memory

This file is a durable notebook for AI agents maintaining or enhancing this
repository. Keep it concise, current, and useful enough that the next session
can avoid a full repository rescan.

## How To Use This File

- Add stable knowledge, design decisions, subsystem maps, verification notes,
  and handoff details.
- Prefer summaries with file paths over long pasted command output.
- Delete or update stale notes when later work proves them wrong.
- Keep unrelated work in separate entries.
- Use this file for project memory; use `AGENTS.md` for standing workflow rules.

Recommended read order when resuming work:

1. Handoff Notes
2. Quick Project Map and relevant Subsystem Notes
3. Progress Log
4. Build And Test Facts

## Quick Project Map

| Path | Purpose |
| --- | --- |
| `src/` | Main OpenLiteSpeed server code and built-in modules. |
| `src/http/` | HTTP request, response, header, vhost, listener, and context logic. |
| `src/h2/` | HTTP/2 implementation. |
| `src/quic/` | QUIC / HTTP/3 integration code. |
| `src/modules/` | Optional and built-in modules such as cache, Lua, gzip, pagespeed, and upload progress. |
| `src/extensions/` | Backend/proxy extension integrations: CGI, FastCGI, LSAPI, SCGI, uWSGI, JK, and proxy. |
| `src/lsr/` | Low-level C runtime utilities mirrored by headers in `include/lsr/`. |
| `src/lstl/` | Local C++ utility/container support. |
| `src/lshpack/` | Bundled HPACK implementation and tests. |
| `include/` | Public/shared headers, module API headers, and `lsr/` C API headers. |
| `test/` | Unit tests and server fixture tree. Tests are enabled by Debug CMake builds on Linux. |
| `addon/example/` | Example modules and module API usage samples. |
| `support/` | Support binaries built by the top-level CMake project. |
| `lshostexec/` | Host execution helper built by the top-level CMake project. |
| `doc/` | Maintainer and developer documentation. |

## Build And Test Facts

- The top-level `CMakeLists.txt` declares `Project(openlitespeed VERSION 1.9.0)`.
- Linux `Debug` builds set `ASAN`, `BUILD_TEST`, define `RUN_TEST`, and add the
  `test/` subdirectory.
- The CMake project sets `CMAKE_EXPORT_COMPILE_COMMANDS ON`.
- The build may run `git submodule update --init --recursive --force` unless
  `GIT_SUBMODULE` is disabled.
- `.editorconfig` requires LF endings, final newline, and four-space indentation
  for C/C++, shell, and PHP files.
- As of 2026-06-06, Debug builds expose unit tests through CTest as individual
  UnitTest++ binaries named `ols_test_<dir>_<source>`. Run them with
  `ctest --test-dir build --output-on-failure -j2 --timeout 60`.
- CTest runs unit binaries from `build/test` and sets
  `ASAN_OPTIONS=detect_leaks=0`; compression test scratch files should stay in
  the build tree.

Useful starting commands:

```bash
git status --short
git log --oneline -8
cmake --build build -j2
```

## Design Notes

Record decisions that should survive across sessions.

| Date | Area | Decision | Rationale | Files |
| --- | --- | --- | --- | --- |
| YYYY-MM-DD | example | Keep this row format. | Makes decisions searchable and easy to review. | `path/to/file` |

## Subsystem Notes

Use this section for facts discovered while working in a subsystem.

### HTTP

- Dynamic gzip can be installed twice: early through
  `HttpSession::setupGzipFilter()` while preparing the dynamic body buffer, and
  later through `HttpSession::contentEncodingFixup()` during header
  finalization. The header-time path installs only the gzip filter, so it must
  require `m_request.gzipAcceptable() == GZIP_REQUIRED`; Brotli acceptability
  alone should not enter that gzip-only path.

### HTTP/2

- `src/h2` CMake/Automake build inputs include `h2connection.cpp`,
  `h2connbase.cpp`, `h2protocol.cpp`, `h2stream.cpp`, `h2streambase.cpp`,
  `h2streampool.cpp`, `unpackedheaders.cpp`, and bundled
  `../lshpack/lshpack.c`; `src/h2/ssledstream.cpp` is present but not part of
  either `src/h2/CMakeLists.txt` or `src/h2/Makefile.am`.
- Review/fix work on 2026-05-07 addressed: inbound DATA flow-control
  accounting in `h2connbase.cpp` and `h2streambase.*`, pending direct DATA
  output stream IDs, malformed HEADERS with `PRIORITY`, malformed GOAWAY frame
  length validation, and padded zero-data DATA frames.

### Modules

- Add notes here after changing `src/modules/`, module API headers, or addon
  examples.

### LSR Runtime

- `include/lsr/ls_base64.h` exposes the C Base64 API used by HTTP auth,
  ZConf, Lua session helpers, and tests. As of 2026-05-18,
  `ls_base64_encode()` and `ls_base64_decode()` require a fourth destination
  buffer size parameter and return `-1` with `errno = ENOSPC` when the output
  buffer is too small. Decode uses a fast upper-bound estimate from encoded
  length rather than pre-scanning for padding or skipped bytes.

### Tests

- Unit tests use `UnitTest++` style with `SUITE(...)` and `TEST(...)` blocks
  guarded by `RUN_TEST` in many files.
- Shared unit-test helpers live in `test/unittest_helpers.cpp`; the helper is
  also listed in `src/CMakeLists.txt` because the Debug `openlitespeed` target
  still compiles the unit-test source list.
- GeoIP tests no longer download fixtures. Set `OLS_GEOIP_TEST_DIR` to a
  directory containing `GeoLite2-*.mmdb`; otherwise DB-dependent cases return
  early.
- `ols_test_thread_workcrewtest` and `ols_test_thread_mtnotifiertest` are
  marked `RUN_SERIAL` in CTest; the notifier test must join its waiter before
  destroying `MtNotifier`.

## Progress Log

Use one entry per focused task. Keep entries short and link to files where
possible.

### 2026-04-29 - Add AI Agent Maintainer Docs

- Goal: Add persistent AI-agent guidance so future sessions can avoid repeated
  full-tree rediscovery.
- Files changed: `AGENTS.md`, `doc/AI_AGENT_PROJECT_MEMORY.md`.
- Summary: Added repo-wide working rules, project memory conventions, a quick
  project map, build/test facts, progress and handoff templates, and commit
  hygiene guidance.
- Verification: Reviewed markdown contents and checked `git status --short`.
- Follow-up: Add subsystem-specific facts as future code work discovers them.

### 2026-05-07 - Fix HTTP/2 Review Findings

- Goal: Land the HTTP/2 review fixes as isolated commits.
- Files changed: `src/h2/h2connbase.*`, `src/h2/h2streambase.*`.
- Summary: Added direct-output stream tracking, stricter HEADERS/GOAWAY frame
  validation, valid padded zero-data DATA handling, and inbound DATA
  flow-control enforcement.
- Verification: `cmake --build build --target h2 -j2`.
- Follow-up: Run protocol-level coverage such as h2spec or a focused HTTP/2
  harness when available.

### 2026-05-18 - Bound C Base64 Output Buffers

- Goal: Refactor the C `ls_base64_*` helpers to accept destination buffer
  sizes and avoid output overruns.
- Files changed: `include/lsr/ls_base64.h`, `src/lsr/ls_base64.c`, direct
  callers under `src/http/`, `src/main/`, `src/modules/lua/`, and focused
  tests under `test/lsr/` plus `test/util/httpfetchtest.cpp`.
- Summary: Added fourth output-size parameters, `EINVAL`/`ENOSPC` failure
  paths, bounded decode writes, encode preflight capacity checks, and safe
  high-bit decode-table indexing.
- Verification: `git diff --check`, `cmake --build build --target
  ols_unittest -j2`, and a standalone Base64 smoke test passed. Running
  `./build/test/ols_unittest --help` actually runs the full suite; it failed
  in existing unrelated tests including GeoIP download/DNS failures and later
  aborted in `MtNotifier::~MtNotifier()`.
- Follow-up: Use focused smoke tests or a filtered UnitTest++ runner if one is
  added; the current `ols_unittest` main ignores command-line filters.

### 2026-05-25 - Verify Extension Memory Fixes

- Goal: Continue staged exploit verification from `20260519-security-fixes`
  onto `20260524-1.9-real-sec-fix`, focusing on extension/backend issues.
- Files changed: `src/extensions/proxy/proxyconn.cpp`,
  `src/extensions/extworkerconfig.cpp`.
- Summary: Cherry-picked and verified the proxy forwarded-header bound and
  alternate UDS suffix bound. Test artifacts and target notes are under
  `~/ols/security-tests/proxy-forwarded-header-oob/`,
  `~/ols/security-tests/extensions-alt-uds-suffix/`, and
  `~/ols/security-tests/extensions-target-list-2026-05-25.md`.
- Verification: Rebuilt `build/src/openlitespeed`, copied it to
  `~/ols/bin/openlitespeed`, reproduced the proxy ASAN heap overflow before
  the fix via a Docker-bridge request, verified no ASAN after the fix, and
  verified the UDS collision no longer creates an overlong alternate socket.
- Follow-up: Remaining extension candidates include generated app URI
  termination and command-parameter validation; blank proxy address was not
  reproducible through plain config syntax in this environment.

### 2026-05-25 - Continue Runtime Security Verification

- Goal: Continue ASAN-backed staged verification of high-risk HTTP, LSIAPI,
  rewrite, SSI, LSAPI, FastCGI, and config-edge targets from
  `20260519-security-fixes`.
- Files changed: `src/http/httprespheaders.cpp`, `src/http/httpreq.cpp`.
- Summary: Verified and cherry-picked `http: bound response header append
  lengths` using a custom LSIAPI module with a negative response-header value
  length, and `http: cap complex header variable expansion` using
  `extraHeaders Header set X-QS "%{QUERY_STRING}e"` plus a 9 KB query string.
  Continued passes 10, 11, and 12 with six cases each and found no additional
  ASAN-confirmed issue.
- Verification: Rebuilt `build/src/openlitespeed`, copied it to
  `~/ols/bin/openlitespeed`, and re-ran the successful repros. Test artifacts
  are under `~/ols/security-tests/pass7-lsiapi-response-header-length/`,
  `~/ols/security-tests/pass9-header-op-expansion/`, and the later clean-pass
  directories `pass10-*`, `pass11-http-edge-requests/`, and
  `pass12-fcgi-response-edges/`.
- Follow-up: Passes 10 through 12 were clean by ASAN. The config-edge harness
  under `pass11-config-edges/` was abandoned because `lswsctrl start` can
  outlive the wrapper; prefer request/response-driven cases for stop-condition
  accounting.

### 2026-05-25 - Additional Clean Security Pass

- Goal: Try another request/response/config-triggered pass against remaining
  unapplied security commits, focusing on real writes, bad lengths, and ASAN
  crashes rather than bounded-compare false positives.
- Files changed: `doc/AI_AGENT_PROJECT_MEMORY.md`.
- Summary: No new ASAN-confirmed issue was found. Staged probes covered the
  resumed form decoder split after `%A`, LSIAPI request-header enumeration
  with `Transfer-Encoding`, FastCGI path-info rewrite expansion with a long
  docroot/URI, SSI `printenv` with a long escaped header, request
  `Content-Length` overflow, malformed blank SSI directives, FastCGI response
  `Content-Length` overflow, and cache setup with a long Host header.
- Verification: Runtime artifacts were saved under
  `~/ols/security-tests/pass13-form-resume-decode/`,
  `~/ols/security-tests/pass13-lsiapi-get-req-headers-te/`,
  `~/ols/security-tests/pass13-fcgi-pathinfo-rewrite/`, and
  `~/ols/security-tests/pass13-extra-cases/`. The successful request probes
  returned expected 200/413/500/503 responses without ASAN reports in
  `~/ols/logs/stderr.log`; the ASAN-instrumented custom module attempt was
  discarded because loading an ASAN-linked module into the ASAN debug binary
  produced an incompatible-runtime startup failure, not a target crash.
- Follow-up: The FastCGI path-info rewrite candidate did not overflow in this
  environment: normal URL limits keep data inside the 64 KiB global buffer,
  while larger URI tests make the signed-short path-info length collapse before
  copying. The LSIAPI `get_req_headers_count()`/`get_req_headers()` mismatch is
  a count/logic inconsistency for `Transfer-Encoding`, but the current loop
  returns immediately after filling the caller-provided count and did not
  produce an out-of-bounds write with documented API usage.

### 2026-05-26 - Stale Realloc Pointer Audit

- Goal: Search for stale-source pointers kept across buffer reallocation or
  release/copy operations.
- Files changed: `src/lsr/ls_buf.c`, `src/lsr/ls_loopbuf.c`,
  `src/util/autostr.cpp`, related focused tests, and `include/lsr/ls_lock.h`.
- Summary: Fixed ASAN-confirmed self-referential `ls_buf_t` append and
  `AutoStr::setStr()` cases. Added a potential hardening fix for
  `ls_loopbuf_t` self-append across growth on `20260519-security-fixes`, but
  did not apply it to `20260524-1.9-real-sec-fix` because the old-code repro
  did not trip ASAN with the current pool path. Added the missing
  `ls_mutex_ipc_locked` alias to unblock clean rebuilds that include SHM.
- Verification: Standalone ASAN repros against old `ls_buf_xappend2()` and
  `AutoStr::setStr()` produced use-after-poison reports; the patched repros
  exited cleanly. `cmake --build build --target util -j2` passed. Full
  `openlitespeed` rebuild progressed past the tracked IPC mutex alias failure
  but then stopped in the symlinked external `src/shm` tree.
- Follow-up: `src/shm` is a symlink to `../../lsr/src/shm/` on branch
  `20260511-pool-mmap-optimize`; its `lsshmlfhash.cpp` expects
  `ls_shmrwlock_owner_t`, but the symlinked `lsshmtypes.h` does not define
  it. Resolve that external-tree mismatch before relying on a clean debug
  rebuild from this checkout.

### 2026-05-31 - Verify MP4 Static Seek Fixes

- Goal: Pick high-risk HTTP fixes from `20260519-security-fixes` into
  `20260531-1.9-real-fixes` only when reproducible through the local ASAN
  `~/ols` server.
- Files changed: `src/http/moov.cpp`.
- Summary: Confirmed and fixed MP4 pseudo-streaming crashes or invalid ranges
  reachable by `GET /security-tests/<file>.mp4?start=0.0001` on port 8088.
  Landed separate commits for mini-moov `stsz` sizing, invalid `stts` entries,
  zero `mdhd` timescale, invalid `stsc` entries, missing sample boxes, invalid
  `mdat` ranges, missing container boxes, and table count overflow checks.
- Verification: Rebuilt with `cmake --build build --target openlitespeed -j2`,
  copied `build/src/openlitespeed` to `~/ols/bin/openlitespeed`, restarted with
  `~/ols/bin/lswsctrl stop/start`, and checked `~/ols/logs/stderr.log` for
  ASAN/FPE/SEGV after each probe. The active vhost docroot is
  `~/ols/Example/html/`; crafted MP4 repro files are under
  `~/ols/Example/html/security-tests/`.
- Follow-up: Empty MP4 sample table probes
  (`mp4_stts_empty_count_big.mp4`, `mp4_stsc_empty_count_big.mp4`,
  `mp4_stsz_empty_count_big.mp4`) returned 200 without ASAN in the final pass;
  treat them as low priority unless a stronger crash or corruption case is
  found.

### 2026-06-01 - Verify URL Unescape Overflow

- Goal: Continue picking ASAN-confirmed high-risk fixes from
  `20260519-security-fixes` into `20260531-1.9-real-fixes`, then stop after
  six clean follow-up probes.
- Files changed: `src/util/httputil.cpp`.
- Summary: Confirmed an SSI `QUERY_STRING_UNESCAPED` stack overflow reachable
  with `GET /security-tests/pass14-qs-unescaped.shtml?%41...` where the query
  contains about 9,500 `%41` triplets. Cherry-picked the URL-unescape
  destination guard and bounded output-pointer fixes, rebuilt the ASAN binary,
  and copied it to `~/ols/bin/openlitespeed`.
- Verification: Before the fix, ASAN reported a write past
  `SsiEngine::processEcho()`'s `achBuf1` from `HttpUtil::unescape()` via
  `RequestVars::getReqVar(REF_QS_UNESCAPED)`. After the fix, the same request
  returned 200 with no ASAN markers. Six follow-up probes were clean: empty SSI
  file, rewrite cookie action with an oversized cookie value, Range header with
  a following numeric header line, long request-line/User-Agent/Referer access
  log request, PHP/LSAPI malformed `Content-Type`, and PHP/LSAPI large response
  header plus stderr.
- Follow-up: Test artifacts are under
  `~/ols/Example/html/security-tests/pass14-qs-unescaped.shtml`,
  `pass14-empty.shtml`, `pass14-bad-content-type.php`, and
  `pass14-lsapi-large-header.php`. Temporary rewrite config changes were
  restored, and `.htaccess` was removed because it did not exist before the
  probes.

### 2026-06-01 - Exhaust Remaining High-Risk Targets

- Goal: Exhaust the remaining high-risk request/response/config targets from
  the `20260519-security-fixes` list against branch
  `20260531-1.9-real-fixes`.
- Files changed: `src/modules/lua/lsluasession.cpp`.
- Summary: Confirmed a Lua query-string heap overflow by loading `mod_lua` and
  requesting `pass15-lua.lua?a=` with a 30 KB value. The crash wrote one byte
  past an exact-size session-pool allocation in `HttpUtil::unescapeQs()` via
  `LsLuaFillTable()`. Cherry-picked `lua: reserve query unescape terminators`.
- Verification: Rebuilt `openlitespeed` and `mod_lua`, copied them to
  `~/ols/bin/openlitespeed` and `~/ols/modules/mod_lua.so`, restarted OLS, and
  reran the same request. The fixed request returned 200 with no ASAN markers.
  Runtime artifacts and the temporary Lua module config are under
  `~/ols/security-tests/pass15-lua-query-unescape/`.
- Follow-up: Clean ASAN probes in this pass covered header-op `DATE_LOCAL`
  expansion, LSAPI partial response headers, external-app command-parameter
  validation, final config line without newline, raw H2 path-before-method
  short and overlong methods, Lua invalid unescape, Lua raw headers, negative
  Lua body-buffer init, large Lua response header value, and Lua POST args.
  The active `~/ols/conf/httpd_config.conf` was restored after verification;
  `mod_lua.so` remains copied in `~/ols/modules/` for reproducibility.

### 2026-06-01 - Apply Direct Security Picks And Filter Remaining Targets

- Goal: Apply user-approved direct fixes from `20260519-security-fixes` to
  `20260531-1.9-real-fixes`, then filter cache and CGI namespace candidates
  under practical request/response/header/object size limits.
- Files changed: `src/http/reqparser.*`, `src/lsiapi/lsiapilib.cpp`,
  `src/extensions/jk/ajp13.cpp`, `src/http/httpcgitool.cpp`,
  `src/modules/uploadprogress/uploadprogress.cpp`,
  `src/http/chunkinputstream.cpp`, `src/http/htauth.h`,
  `src/http/httpvhost.cpp`.
- Summary: Landed separate commits for POST argument indexing, JK/AJP
  terminator space, bounded multipart diagnostics, upload-progress counter
  sizing, full chunked content length preservation, bounded LSIAPI module
  parameter parsing, and a configuration-time 200-byte auth realm-name cap.
- Verification: `git diff --check HEAD~7..HEAD` and
  `cmake --build build --target openlitespeed -j2` passed.
- Follow-up: Cache candidates still worth review with size caps are serialized
  metadata length truncation (`84270ad`), URI tag failure behavior when URL or
  query exceeds the 16 KiB tag staging buffer (`d4fbaea`, conditional on URL
  limits), and config-only long cache/compress path builders (`2a10a956`,
  `a6c7dd`). CGI namespace candidates still worth review are overlong
  temporary paths (`d3668ee`), overlong hostnames (`7e5a1fe`), and empty
  namespace ID lists (`d55178b`). Most other cache and namespace commits need
  corrupt on-disk cache files, hostile IPC peers, or unrealistically huge
  config/string lengths under the current threat model.

### 2026-06-01 - Apply Cache And CGI Namespace Follow-Ups

- Goal: Apply the approved cache candidates and rework cache path and CGI
  namespace fixes to match maintainer review preferences.
- Files changed: `src/modules/cache/cacheentry.cpp`,
  `src/modules/cache/cache.cpp`, `src/modules/cache/dirhashcachestore.cpp`,
  `src/main/httpserver.cpp`, `src/extensions/cgi/ns.c`.
- Summary: Applied serialized cache metadata and URI-tag validation fixes.
  Replaced runtime path-builder patches for cache storage and compressed cache
  paths with configuration-time base-path caps. Reworked namespace temp-path
  and hostname checks to use `memccpy()` terminator detection, and limited the
  namespace ID-list fix to the empty-list rejection.
- Verification: `git diff --check HEAD~6..HEAD` and
  `cmake --build build --target openlitespeed -j2` passed.
- Follow-up: `2a10a956` and `a6c7dd` were intentionally not cherry-picked;
  the active branch caps those path values during configuration instead.

### 2026-06-02 - Apply Remaining High-Risk Security Picks

- Goal: Apply the approved high-risk leftovers from
  `20260519-security-fixes`, reworking the internal Location remap fix to use
  bounded formatting, and pick compact medium/low follow-ups where the fix was
  small and low overhead.
- Files changed: HTTP rewrite/path handling, LSAPI packet validation,
  `HttpUtil` escape helpers, generated app/local-worker command buffers,
  LSIAPI raw logging, CGI namespace symlink handling, and startup/config path
  guards.
- Summary: Landed rewrite URI/Location copy caps, path translation and symlink
  path terminator guards, LSAPI response packet length validation, rewrite
  decode failure handling, the `escape_uri()` inline truncation rework, module
  URI length-bounded unescape, empty-buffer guards, and small config/startup
  guards. Reworked generated app URI and local worker command fixes to use
  `memccpy()` truncation detection; reworked internal Location remap to use
  `lsnprintf()` with a length-limited suffix.
- Verification: `git diff --check HEAD~25..HEAD` passed before the docs
  update, and `cmake --build build --target openlitespeed -j2` passed. The
  build emitted existing CMake deprecation warnings and a clock-skew warning at
  final link, but produced `openlitespeed`.
- Follow-up: Larger medium/low CGI socket/chroot path checks and Lua/module
  size-narrowing hardening were not included in this batch; review those with
  the same preference for `memccpy()`/`lsnprintf()` simplification before
  applying.

### 2026-06-02 - Apply Additional Non-OOM Security Picks

- Goal: Apply the remaining important fixes from `20260519-security-fixes`
  while skipping the allocation-failure-only hardening group.
- Files changed: LSAPI response parsing, MIME lookup, request rewrite/redirect
  handling, plain config parsing, CGI/suexec startup paths, `removeMatchFile()`,
  Lua module APIs, and `ls_pcreg.c`.
- Summary: Landed LSAPI STDERR and partial-header bounds, MIME subtype trim
  guard, invalid redirect decode handling, matched rewrite query length fix,
  final-config-line copy bound, CGI/suexec path formatting fixes, cgid chroot
  trimming validation, stderr chroot-prefix validation, matched-file cleanup
  bounds, and Lua size/result checks for POST args, headers, request bodies,
  URI rewrites, escaping, sockets, stack dumps, base64, regex, and shared
  strings. The dynamic response `Content-Length` parser was already present in
  this branch, so its cherry-pick was skipped as empty.
- Verification: `git diff --check HEAD~30..HEAD` passed.
  `cmake --build build --target openlitespeed mod_lua -j2` passed.
- Follow-up: Allocation-failure-only hardening commits remain intentionally
  unapplied. The remaining `git cherry` positives are mostly already-reworked
  fixes, previously checked lower-risk targets, cache corrupt-file-only cases,
  or broad status/report clamp hardening.

### 2026-06-04 - Current 1.9 ASAN Security Probe Pass

- Goal: Search current `1.9` for remaining request/response-triggered memory
  issues after the major security fixes landed, using the local ASAN
  `~/ols` setup and stopping after ten consecutive misses.
- Files changed: `doc/AI_AGENT_PROJECT_MEMORY.md`; runtime artifacts only
  under `~/ols/security-tests/pass16-current-1.9-audit/` and
  `~/ols/Example/html/security-tests/pass16/`.
- Summary: No new ASAN-confirmed issue was found. The ten probes covered H2
  short/long method rewriting, SSI `printenv` and malformed directives, empty
  SSI files, known MP4 pseudo-streaming crash corpus, raw H2 pseudo-header
  reordering, CGI-generated oversized/partial/malformed response headers, and
  request-parser edges for chunked bodies, oversized `Content-Length`, and
  oversized `Range`.
- Verification: Rebuilt `build/src/openlitespeed`, copied it to
  `~/ols/bin/openlitespeed`, restarted with `~/ols/bin/lswsctrl stop/start`,
  and checked `~/ols/logs/stderr.log` after every attempt. Attempts
  `attempt01` through `attempt10` all recorded `NO_ASAN`.
- Follow-up: Several `git cherry` positives are already present by content on
  current `1.9`; filter by source inspection before treating patch-id output
  as missing fixes. The mini-moov track-count guard is still not obviously
  request-triggerable because the normal `moov_read()` path rejects excessive
  tracks before caching mini-moov data.

### 2026-06-04 - Add Focused Parser Security Regression Target

- Goal: Start a durable fuzz/regression branch after the current `1.9` ASAN
  manual pass reached diminishing returns.
- Files changed: `src/h2/unpackedheaders.cpp`, `src/http/httpmethod.h`,
  `test/CMakeLists.txt`, and `test/security/parserregression.cpp`.
- Summary: Added a dedicated `ols_security_regression` UnitTest++ target with
  deterministic parser security corpora for H2 unpacked headers,
  `ChunkInputStream`, and `HttpRange`. Tightened H2 decoded-header handling so
  LF values are rejected by the lookup table and overlong `:method` values are
  rejected before request-line buffer rewriting.
- Verification: `cmake -S . -B build`; `cmake --build build --target
  ols_security_regression -j2`; `ASAN_OPTIONS=detect_leaks=0
  ./build/test/ols_security_regression` passed with 4 tests; `cmake --build
  build --target ols_unittest -j2` passed. The full `ols_unittest` runtime is
  not currently a clean gate because unrelated GeoIP fixture/network failures
  and existing HTTP header expectations fail before this branch's tests matter.
- Follow-up: After the 2026-06-06 CTest split, this target is available as
  `ols_test_security_parserregression`. Extend it with SSI, response-header,
  and MP4/moov corpora next. Keep it deterministic and fast enough to run
  under ASAN on every security branch.

### 2026-06-06 - Split Unit Tests Into CTest Targets

- Goal: Replace the monolithic `ols_unittest` runner with CTest-managed
  individual test binaries and make the active unit suite a clean gate.
- Files changed: top-level and test CMake files, `test/unittest_main.cpp`,
  `test/unittest_helpers.cpp`, focused HTTP/util/thread tests, and production
  fixes in `src/http/` plus `src/socket/gsockaddr.cpp`.
- Summary: Added one `ols_test_*` binary per UnitTest++ source, shared common
  test setup, CTest registration, build-tree working directories, serial
  scheduling for thread tests, deterministic GeoIP fixture skips, and refreshed
  stale expectations. The split exposed real fixes for rewrite flags, valid
  chunk-size extensions, unsatisfiable range accounting, streamed multipart
  header/body splits, and overlong Unix socket paths.
- Verification: `cmake --build build -j2`; `ctest --test-dir build
  --output-on-failure -j2 --timeout 60` passed 84/84 tests.
- Follow-up: Add more security regression corpora as focused test files so
  they appear as independent CTest targets.

### 2026-06-08 - Fix Dynamic Gzip With Brotli Accept

- Goal: Investigate why `~/ols` returned gzip for
  `http://localhost:8088/phpinfo.php` even with `enableGzipCompress 0` and
  `enableDynGzipCompress 0`.
- Files changed: `src/http/httpsession.cpp`.
- Summary: Found that `Accept-Encoding: br` made
  `contentEncodingFixup()` call `updateContentCompressible()` successfully,
  then unconditionally install the gzip send filter. Gated that path on
  `GZIP_REQUIRED` so Brotli acceptability alone cannot trigger gzip.
- Verification: `cmake --build build --target openlitespeed -j2` passed.
  Copied the rebuilt binary into `~/ols/bin/openlitespeed`, restarted
  `~/ols`, and confirmed `Accept-Encoding: br`, `gzip`, and
  `gzip, deflate, br` all returned no `Content-Encoding` and plain HTML first
  bytes under the disabled gzip config.
- Follow-up: Add a focused regression test if a lightweight `HttpSession`
  harness becomes available.

### YYYY-MM-DD - Short Task Name

- Goal:
- Files changed:
- Summary:
- Verification:
- Follow-up:

## Handoff Notes

Use this section when stopping with unfinished work or important context.

### 2026-04-29 - AI Docs Initial State

- Current state: Initial AI-agent markdown guidance has been added.
- Next step: For future implementation tasks, read `AGENTS.md`, then this file,
  then inspect only the relevant source and test files.
- Risks or unknowns: No subsystem-specific design facts have been validated
  beyond the top-level layout and build/test facts recorded above.
- Useful commands: `git status --short`, `git log --oneline -8`,
  `cmake --build build -j2`.

### 2026-05-07 - HTTP/2 Review

- Current state: Reviewed `src/h2` for parser/state-machine, header decoding,
  stream lifecycle, and flow-control risks; fixes were committed separately.
- Next step: Run h2spec-style protocol coverage, especially around DATA
  flow-control, HEADERS/GOAWAY frame size errors, and padded DATA frames.
- Risks or unknowns: No direct HTTP/2 unit tests were found under `test/`;
  protocol behavior likely needs h2spec-style integration coverage or focused
  harness tests.
- Useful commands: `cmake --build build --target h2 -j2` passed on
  2026-05-07; it re-ran CMake and emitted only existing CMake deprecation
  warnings.

### YYYY-MM-DD - Short Handoff Title

- Current state:
- Next step:
- Risks or unknowns:
- Useful commands:

## Commit Planning Notes

Before committing, check that the staged set is one coherent change.

Suggested body fields for non-trivial commits:

```text
What changed:
- ...

Why:
- ...

How:
- ...

Improvement:
- ...

Problem solved:
- ...
```
