diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..c581405
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,31 @@
+# LLM Agent Instructions
+
+* To compile `ModRuby`, run these commands:
+ * `rm -f /usr/src/mod_ruby/CMakeCache.txt`
+ * `rm -Rf /usr/src/mod_ruby/CMakeFiles`
+ * `cmake3 /usr/src/mod_ruby`
+ * `make -j4`
+ * `make install`
+ * If any compilation commands fail, the patch failed.
+* To test `ModRuby`:
+ * Reload Apache by running `killall httpd` -- it will automatically restart and load
+ the new `mod_ruby.so` shared object that was previously compiled.
+ * Use curl to test `ModRuby` with this: `curl -m1 localhost`
+ * It should output the string `Ruby check_access()\nHi there from ruby`
+ * If curl times out and Apache segfaults, the patch failed
+ * To inspect the backtrace, look at the file `/var/log/httpd/gdb_backtrace.txt`
+ * After each crash, Apache is restarted
+ * Each new crash will delete the previous contents of `/var/log/httpd/gdb_backtrace.txt`
+ * If Apache completely fails to start up due to a symbol linking problem or other error,
+ the output from the server will be in `/var/log/httpd/error_log` and it will
+ continue to try and restart itself every 10 seconds. The log file might be large,
+ so something like `tail -20 /var/log/httpd/error_log` is a safer method to inspect
+ this file.
+* codex is running inside an Oracle Linux 8 Docker container, running as the root user.
+ If you need tools or packages installed, you may use `dnf install` for distro packages
+ or use other methods.
+* NodeJS 22 is installed and you may install npm packages
+* Python 3.12 is installed and you may construct Python utility scripts and execute them.
+* If you want to inspect the Ruby source code, it is installed in `/usr/local/rvm/src/ruby-3.2.3/`
+ * If you want to inspect the Ruby C++ headers, they are in `/usr/local/rvm/src/ruby-3.2.3/include/`
+* If you want to inspect the Ruby binary installation, it is installed in `/usr/local/rvm/rubies/ruby-3.2.3/`
diff --git a/Dockerfile b/Dockerfile
index 6fa22d8..8797737 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -55,9 +55,9 @@ RUN rvm requirements
# Pick your ruby version here
# Segfaults in rb_protect()
-#RUN rvm install ruby-3.2.3
+RUN rvm install ruby-3.2.3
# Works
-RUN rvm install ruby-2.7.5
+#RUN rvm install ruby-2.7.5
# Same segfault as 3.2.3
#RUN rvm install ruby-3.0.3
diff --git a/Dockerfile.codex b/Dockerfile.codex
new file mode 100644
index 0000000..d15509a
--- /dev/null
+++ b/Dockerfile.codex
@@ -0,0 +1,107 @@
+# This builds a docker image suitable for running OpenAI's codex CLI
+FROM oraclelinux:8
+
+RUN dnf install -y oraclelinux-release-el8 oracle-epel-release-el8 \
+ && dnf --enablerepo=ol8_codeready_builder install -y \
+ apr-devel \
+ apr-util \
+ apr-util-devel \
+ autoconf \
+ automake \
+ bison \
+ bzip2 \
+ cmake3 \
+ curl \
+ cyrus-sasl \
+ cyrus-sasl-devel \
+ flex \
+ gcc \
+ gcc-c++ \
+ gdb \
+ git \
+ gpg \
+ httpd \
+ httpd-devel \
+ libffi-devel \
+ libtool \
+ libyaml \
+ libyaml-devel \
+ openssl-devel \
+ patch \
+ readline-devel \
+ ruby \
+ sqlite-devel \
+ make \
+ redhat-lsb \
+ unzip \
+ zlib-devel \
+ && dnf module enable nodejs:22 \
+ && dnf install nodejs nodejs-devel npm \
+ && npm install -g @openai/codex
+
+# Import GPG key for RVM
+RUN gpg \
+ --keyserver keyserver.ubuntu.com \
+ --recv-keys \
+ 409B6B1796C275462A1703113804BB82D39DC0E3 \
+ 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
+
+# Install RVM system wide
+RUN curl -sSL https://get.rvm.io | bash -s stable
+
+# Set our shell to a bash full login environment to pull in
+# RVM's profile in all RUN instructions below.
+# (Requires Docker 1.12)
+SHELL ["/bin/bash", "-l", "-c"]
+
+# In case we missed any package requirements, this installs them
+RUN rvm requirements
+
+# Pick your ruby version here
+# Segfaults in rb_protect()
+RUN rvm install ruby-3.2.3 --disable-binary
+# Works
+#RUN rvm install ruby-2.7.5
+# Same segfault as 3.2.3
+#RUN rvm install ruby-3.0.3
+
+# Setup our libruby.so dir in ld.so.conf
+RUN rvm config-get libdir > /etc/ld.so.conf.d/ruby.conf && ldconfig
+
+WORKDIR /usr/src/mod_ruby
+
+COPY . /usr/src/mod_ruby
+
+# Pulls in the RVM environment and installed ruby
+RUN cmake3 . && make -j4 && make install
+
+# Remove some junk that the httpd package installs
+RUN rm -f /etc/httpd/conf.d/welcome.conf /etc/httpd/conf.modules.d/00-systemd.conf
+
+# Manually copy some files I couldn't figure out with the CMake system
+RUN cp -a config/mod_ruby.conf /etc/httpd/conf.modules.d/
+
+# librhtml.so
+RUN cp -a lib/* $(rvm config-get libdir) && ldconfig
+
+COPY docker/index.html /var/www/html/index.html
+COPY docker/*.rb /var/www/html/
+COPY docker/*.cgi /var/www/cgi-bin/
+COPY docker/httpd.conf /etc/httpd/conf/httpd.conf
+COPY docker/gdb.input /gdb.input
+COPY docker/httpd-gdb /httpd-gdb
+COPY docker/httpd-gdb-loop /httpd-gdb-loop
+
+# Force apache logs to docker console logs
+#RUN ln -sf /dev/console /var/log/httpd/access_log \
+# && ln -sf /dev/console /var/log/httpd/error_log
+
+# Graceful shutdown signal for apache
+# (Requires Docker > 1.11)
+# Note: gdb is set to trap SIGWINCH, so this is for
+# alternate uses with less debugging
+#STOPSIGNAL SIGWINCH
+
+# If you want a simpler image without gdb...
+#CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
+CMD ["/httpd-gdb"]
diff --git a/README.md b/README.md
index a48868b..9c0dd7f 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ The build will create these files in the local repo:
To build from source, you need to the following packages:
- * Ruby 1.9 or 2.x. (Ruby header files also needed).
+ * Ruby 2.x or 3.x. (Ruby header files also needed).
* Apache, APR and APR Util headers
* CMake
@@ -69,7 +69,7 @@ using the Docker image as a handy developer's environment:
bash $ ./script/docker_run
...
- 0x00007f30bb13733f in accept4 () from /lib64/libc.so.6
+ 0x00007f7a7adfc7b4 in read () from /lib64/libpthread.so.0
A single Apache worker child is running in a gdb shell. Smoke test mod_ruby in a
separate terminal window:
@@ -77,7 +77,7 @@ separate terminal window:
bash $ curl localhost:8080
Hi there from ruby
Hello World from HTML!
-
+
If mod_ruby crashes, gdb will print a full stack trace. You may also do
`Ctrl + C` to break out to a gdb prompt to inspect the running Apache child.
@@ -87,6 +87,60 @@ cases.
Submit pull requests using feature branches, and have fun!
+### Developing with OpenAI Codex
+
+Working with the Ruby C API can be frustrating. Documentation is sparse
+and a lot of the experience is trial & error.
+
+This project integrates OpenAI's Codex for an agentic approach. There
+is a separate `Dockerfile.codex` which loads in the full Ruby C++ headers
+and source code and installs the Codex CLI. The project root contains
+an AGENTS.md system prompt to tell the LLM how to compile, install
+and test ModRuby. It can run web searches, install packages as root,
+search the Ruby source and behave like an entitled twerp.
+
+Apache is started up with the container and will run in a reload loop
+if it crashes. The agent is given instructions to test changes and
+inspect the stack trace if Apache segfaults. It should keep iterating
+until the goal is met, which could be a very long time.
+
+**Use with caution. Don't leave it run unattended.**
+
+First, install [Codex CLI](https://developers.openai.com/codex/cli/)
+on your workstation and get the credentials set up by logging into ChatGPT.
+The `ModRuby` container bind mounts your workstation's `$HOME/.codex`
+into the container and runs the agent inside the container. The `ModRuby`
+project root is bind mounted into `/usr/src/mod_ruby` so changes made by the
+agent will be reflected immediately on your host OS.
+
+Start up the container with:
+
+ bash $ ./script/docker_codex
+ . . .
+ To get started, describe a task or try one of these commands:
+ >
+
+Go wild and have fun.
+
+```
+> Search for a potential security vulnerability in ModRuby. Identify the
+ attack vector and write a test case to confirm the vulnerability. Write
+ a patch to fix the vulnerability and supply code comments.
+```
+
+```
+Updated Plan
+ High-level roadmap before diving into the source and modifications.
+ Survey key ModRuby modules for input handling to spot potential security issues.
+ Create a regression test that demonstrates the vulnerability.
+ Implement a fix with comments, rebuild, and rerun the new test.
+
+I'm focusing on confirming an overflow issue in url_encode by running a
+death test with MALLOC_CHECK set to catch heap errors. I'm thinking about
+creating a dedicated test compiled with AddressSanitizer to catch the integer
+overflow vulnerability in url_encode by triggering a heap-buffer-overflow if present.
+```
+
## License
Redistribution and use in source and binary forms, with or without modification,
diff --git a/docker/httpd-gdb-loop b/docker/httpd-gdb-loop
new file mode 100755
index 0000000..c188008
--- /dev/null
+++ b/docker/httpd-gdb-loop
@@ -0,0 +1,88 @@
+#!/bin/bash
+# This is similar to httpd-gdb, except it's more hands-off and
+# better suited for automated testing. If apache crashes the
+# stack trace will be written to /var/log/httpd/gdb_backtrace.txt
+# and then apache is restarted. Each new crash will delete the
+# backtrace log and dump the fresh backtrace into it.
+#set -euo pipefail
+
+# Config
+TRACE_FILE="${TRACE_FILE:-/var/log/httpd/gdb_backtrace.txt}"
+
+mkdir -p "$(dirname "$TRACE_FILE")"
+
+# Clean previous trace each run
+rm -f "$TRACE_FILE"
+
+# Ensure child gdb/httpd dies if this script is killed
+trap 'pkill -P $$ || true' INT TERM
+
+while :; do
+ ts="$(date -Is)"
+ tmp="${TRACE_FILE}.tmp"
+
+ echo "[$ts] starting httpd"
+
+ rm -f /var/run/httpd/httpd.pid
+
+ /usr/sbin/httpd 1>>"$tmp" 2>>"$tmp"
+
+ ret=$?
+ if [[ "$ret" != "0" ]]; then
+ {
+ echo "Failed to start up Apache:"
+ echo
+ } >>"$tmp"
+ mv -f "$tmp" "$TRACE_FILE"
+ # longer sleep time on big time fails
+ sleep 10
+ continue
+ fi
+
+ sleep 2
+ pid=$(ps ax |grep /usr/sbin/httpd |grep -v grep | grep -v defunct | awk '{print $1}' |tail -1)
+ echo "httpd fork pid: $pid"
+
+ # If apache started up successfully and the previous trace file
+ # contains a fail message, delete the trace file. We want to keep
+ # backtraces intact for observability though.
+ if grep -q "Failed to start up Apache" "$TRACE_FILE"; then
+ echo "Deleting trace file"
+ rm -f "$TRACE_FILE"
+ fi
+
+ #-ex "set detach-on-fork on" \
+ #-ex "handle SIGPIPE nostop noprint pass" \
+ #-ex "handle SIGSEGV stop print nopass" \
+ #-ex "set follow-fork-mode child" \
+ gdb -p $pid -q --batch \
+ -ex "set pagination off" \
+ -ex "set confirm off" \
+ -ex "handle SIGPIPE nostop noprint pass" \
+ -ex "handle SIGSEGV stop print nopass" \
+ -ex "continue" \
+ -ex "echo \n===== THREAD BACKTRACE =====\n" \
+ -ex "thread apply all bt full" \
+ -ex "echo \n===== REGISTERS =====\n" \
+ -ex "info registers" \
+ 2>&1 | tee "$tmp"
+
+ # If it crashed, keep the trace; else discard.
+ if grep -q "received signal SIGSEGV" "$tmp"; then
+ {
+ echo "===== TIMESTAMP ====="
+ echo "$ts"
+ } >>"$tmp"
+ mv -f "$tmp" "$TRACE_FILE"
+ echo "segfault detected. backtrace saved to: $TRACE_FILE"
+ else
+ cat "$tmp"
+ rm -f "$tmp"
+ echo "no segfault detected. restarting."
+ fi
+
+ ps ax |grep /usr/sbin/httpd |grep -v grep | grep -v defunct | awk '{print $1}' | xargs kill -9
+
+ sleep 2
+done
+
diff --git a/docker/httpd.conf b/docker/httpd.conf
index dc6083f..e22eeba 100644
--- a/docker/httpd.conf
+++ b/docker/httpd.conf
@@ -15,7 +15,16 @@ DocumentRoot "/var/www/html"
# Allow open access:
Require all granted
+# Make apache chill out on the event MPM settings so that
+# we don't have such a huge mess of threads in gdb to deal with
MaxClients 1
+ThreadsPerChild 1
+MaxRequestWorkers 1
+MinSpareThreads 1
+MaxSpareThreads 1
+MaxConnectionsPerChild 0
+ServerLimit 1
+
CoreDumpDirectory /tmp
RubyHandlerDeclare TEST
RubyHandlerModule TEST "/var/www/html/test.rb"
diff --git a/script/docker_codex b/script/docker_codex
new file mode 100755
index 0000000..625b041
--- /dev/null
+++ b/script/docker_codex
@@ -0,0 +1,26 @@
+#!/bin/bash
+root=$( readlink -f $( dirname $( readlink -f $0 ) )/.. )
+docker build -f $root/Dockerfile.codex -t mod_ruby_codex $root
+
+docker rm -f mod_ruby_codex_run_container
+# Change 8080 here if you have a local conflict with it
+docker run \
+ --name=mod_ruby_codex_run_container \
+ --cap-add=SYS_PTRACE \
+ --security-opt seccomp=unconfined \
+ -v $root:/usr/src/mod_ruby \
+ -v $HOME/.codex:/root/.codex \
+ -d \
+ -p 8080:80 \
+ mod_ruby_codex \
+ /bin/sleep infinity
+
+# Start up Apache in the background. This script will loop
+# restarting Apache when it crashes. Stack traces are logged
+# for the Codex agent to inspect.
+docker exec -d mod_ruby_codex_run_container /httpd-gdb-loop
+#docker exec -ti mod_ruby_codex_run_container /httpd-gdb-loop
+
+# Run the Codex agent with full access and web search
+docker exec -ti mod_ruby_codex_run_container codex --sandbox danger-full-access --search --cd /usr/src/mod_ruby
+
diff --git a/src/module.cpp b/src/module.cpp
index e27f86f..176e864 100644
--- a/src/module.cpp
+++ b/src/module.cpp
@@ -2,9 +2,17 @@
#include
#include
+#include
+#include
+#include
#include