# 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`.

### Node.js Runners

- `dist/fcgi-bin/lsnode.js` and `lsnodesm.js` patch `child_process` before
  loading the application. Forked Node children preload the same runner in
  watchdog mode and compare their current parent PID with the PID recorded at
  fork time, allowing them to terminate after an uncatchable parent `SIGKILL`.
- Observable runner exits also signal tracked `fork`, `spawn`, `execFile`, and
  `exec` children; on Linux, a synchronous `/proc` snapshot reaches their
  current descendants. Arbitrary non-Node children cannot preload the watcher,
  so parent `SIGKILL` coverage is guaranteed for `child_process.fork()` trees,
  not for detached `spawn()`/`exec()` processes.
- `test/nodejsrunnerchildtest.js` covers both CommonJS and ESM runners, parent
  `SIGTERM`/`SIGKILL`, explicit child environments, intentional IPC disconnect,
  recursive forks, forced cleanup of a descendant that ignores `SIGTERM`, and
  `fork({execPath})` for both a symlinked node and an unknown runtime. Assert
  watchdog coverage under `SIGKILL`, not `SIGTERM`: a tracked child dies on
  `SIGTERM` whether or not it preloaded the watcher.
- The watchdog is injected with `--require`, which node accepts natively and
  bun accepts as an alias of its own `--preload`; bun runs the same runner.
  A child running the same binary as the runner is always preloaded. `fork()`
  can name a third runtime through `execPath`; that one is matched by name
  after `realpathSync()`, so a versioned install behind a link such as
  `/opt/runtime/current` still counts as node. Anything else is started
  untouched -- a missing watchdog costs that child only its parent-death
  check, a flag an unknown runtime rejects costs it startup.
- `LSNODE_BIND_SOCKET` mode -- the application binds its own unix socket under
  umask 0117 instead of accepting on fd 0 -- lives only on branch
  `202060608-lsnode-listen-socket-improve`. Here `customListen()` always uses
  `socketObject = { fd: 0 }`, so do not port that branch's bind-umask restore
  code on its own: the restore reads an `lsBindMask` that only the bind path
  assigns, and an undeclared read throws `ReferenceError` on every `listen()`,
  taking down every Node application. Port the mode whole or not at all.

## Progress Log

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

### 2026-08-05 - Supervise Node.js Application Children

- Goal: Prevent `child_process.fork()` workers from surviving after an
  OpenLiteSpeed-managed Node.js runner stops.
- Files changed: `dist/fcgi-bin/lsnode.js`, `dist/fcgi-bin/lsnodesm.js`,
  `test/nodejsrunnerchildtest.js`, and `test/CMakeLists.txt`.
- Summary: Added child tracking for observable shutdown, recursive fork
  watchdog preloading for parent `SIGKILL`, bounded forced termination,
  deliberate IPC-disconnect handling, and a `LSNODE_KEEP_CHILDREN=1` opt-out.
- Verification: `node --check` passed for both runners and the test;
  `ctest --test-dir build -R '^node_runner_child_supervision$'
  --output-on-failure` passed all lifecycle scenarios.
- Follow-up: If non-Node detached children must also be covered after runner
  `SIGKILL`, that requires OS-level supervision such as a dedicated process
  group/cgroup or native parent-death signaling rather than a runner handler.

### 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:
- ...
```
