All proxying must be done on a streaming basis so that all types of requests are supported at minimal latency and a constant memory overhead.
Any time we need to read a specific number of bytes from the stream (e.g. to determine protocol), we must establish a timeout for the operation.
Timeouts must not preclude long-running connections such as GRPC or WebSocket.
For faster builds during development and debugging, use the fast profile:
cargo build --profile fastThis profile inherits from release mode but uses lower optimization levels and disables LTO for significantly faster build times while still providing reasonable performance.
When writing tests, prefer pure rust solutions over shell script wrappers.
Important: Shell script based tests (.sh files) should NOT be committed to the repository. They are acceptable for transient testing during development (WIP), but all submitted tests must be written in Rust as either integration or unit tests. This ensures consistency, maintainability, and proper CI integration.
Test Philosophy: Write terse, minimal tests that cover the essential behavior. Avoid verbose test suites with many similar test cases. Each test should have a clear, specific purpose. Prefer 1-2 focused tests over 5-10 comprehensive tests. This keeps the test suite fast and maintainable.
Prefer logging over temporary scripts for debugging. Tests automatically have tracing enabled via ctor initialization in tests/common/logging.rs. To debug test failures:
- Run tests with
RUST_LOG=debug cargo testto see detailed logging - Add
tracing::debug!()ortracing::info!()statements rather thanprintln!or temporary test files - The logging setup uses
try_init()so it won't panic if already initialized
This approach ensures:
- Debugging information is available in CI logs
- No temporary files clutter the repository
- Debugging aids can be left in place without affecting normal test runs
When testing behavior outside of the strong jailing, use --weak for an environment-only
invocation of the tool. --weak works by setting the HTTP_PROXY and HTTPS_PROXY environment
variables to the proxy address.
The integration tests run against the binary built by Cargo; no manual environment variables are required. On Linux, run the strong-jail integration tests with sudo:
sudo -E cargo test --test linux_integrationWeak-mode tests (environment-only, cross-platform) run without sudo:
cargo test --test weak_integrationRun the full suite:
cargo testAll tests must complete within seconds, not minutes. The CI timeout is set to 30 seconds per test. Tests that require longer operations (like timeouts) should use minimal durations:
- Use
HttpjailCommand::timeout(2)for timeout tests withsleep 3 - Network tests should use
--connect-timeout 5 --max-time 8for curl commands - Any test taking longer than a few seconds should be optimized or redesigned
This ensures fast feedback during development and prevents CI timeouts.
Integration tests should run in parallel by default. The jails are designed to be independent from each other, so the test suite should leverage good parallelism. Tests should only be marked as serial (#[serial]) when there's a specific global resource that would be contended, such as:
- Global system settings that affect all processes
- Shared network ports or interfaces
- System-wide firewall rules that can't be isolated
Each jail operates in its own network namespace (on Linux) or with its own proxy port, so most tests can safely run concurrently. This significantly reduces total test runtime.
Occasionally you will encounter permissions issues due to running the tests under sudo. In these cases,
DO NOT cargo clean. Instead, chown -R <user> target.
- macOS uses weak mode (environment-only) and does not use PF. No root/sudo required for standard usage or tests.
- To run integration tests on macOS, prefer the weak-mode suite:
cargo test --test weak_integration
- curl and most CLI tools: Respect the
SSL_CERT_FILE/SSL_CERT_DIRenvironment variables that httpjail sets, so they work even without the CA in the system keychain - Go programs (gh, go, etc.): Use the macOS Security.framework and ignore environment variables, requiring the CA to be installed in the keychain via
httpjail trust --install - When the CA is not trusted in the keychain, httpjail will:
- Still attempt TLS interception (not pass-through)
- Warn that applications may fail with certificate errors
- Go programs will fail to connect until
httpjail trust --installis run
User-facing documentation should be in the README.md file.
Code/testing/contributing documentation should be here.
When updating any user-facing interface of the tool in a way that breaks compatibility or adds a new feature, update the README.md file.
CI requires the following to pass on both macOS and Linux targets:
cargo clippy --all-targets -- -D warnings
When the user asks to run clippy and provides the ability to run on both targets, try to run it on both targets.
After modifying code, run cargo fmt to ensure consistent formatting before committing changes.
In regular operation of the CLI-only jail (non-server mode), info and warn logs are not permitted as they would interfere with the underlying process output. Only use debug level logs for normal operation and error logs for actual errors. The server mode (--server) may use info/warn logs as appropriate since it has no underlying process.
The Linux CI tests run on a self-hosted runner (ci-1) in GCP. Only Coder employees can directly SSH into this instance for debugging.
The CI workspace is located at /home/ci/actions-runner/_work/httpjail/httpjail. IMPORTANT: Never modify files in this directory directly as it will interfere with running CI jobs.
# SSH into CI-1 instance (interactive or with commands)
./scripts/ci-ssh.sh # Interactive shell
./scripts/ci-ssh.sh "ls /tmp/httpjail-*" # Run command
# SCP files to/from CI-1
./scripts/ci-scp.sh src/ /tmp/httpjail-docker-run/ # Upload
./scripts/ci-scp.sh root@ci-1:/path/to/file ./ # Download
# Wait for PR checks to pass or fail
./scripts/wait-pr-checks.sh # Auto-detect PR from current branch
./scripts/wait-pr-checks.sh 47 # Monitor specific PR #47
./scripts/wait-pr-checks.sh 47 coder/httpjail # Specify PR and repo explicitlyTwo helper scripts are available for managing GitHub PR code review comments:
# Get all resolvable code review comments for the current PR
./scripts/get-pr-comments.sh
# Get raw output (useful for extracting comment IDs)
./scripts/get-pr-comments.sh --raw
# Get compact output (one line per comment)
./scripts/get-pr-comments.sh --compact
# Get comments for a specific PR
./scripts/get-pr-comments.sh 54The script shows comments with line numbers in the format:
username [CID=12345678] path/to/file.rs#L42: Comment text
# Reply to a specific comment (auto-marks as automated)
./scripts/reply-to-comment.sh <COMMENT_ID> <MESSAGE>
# Examples:
./scripts/reply-to-comment.sh 2365688250 "Fixed in commit abc123"
./scripts/reply-to-comment.sh 2365688250 "Thanks for the feedback - addressed this issue"To find comment IDs, use:
./scripts/get-pr-comments.sh --raw | grep CIDBest Practices for Replies:
- IMPORTANT: Push your commits to the remote branch BEFORE replying with commit hashes. GitHub needs the commits to be on the remote to create clickable links.
git push origin branch-name # Push commits first ./scripts/reply-to-comment.sh CID "Fixed in abc123" # Then reply
- Always include the commit hash that fixes the issue (e.g., "Fixed in a5813f3")
- If the commit includes multiple unrelated changes, include a diff snippet showing just the relevant fix:
Fixed in commit a5813f3. Relevant change: \`\`\`diff - old line + new line \`\`\` - Use
git log --oneline -n 5to find recent commit hashes - Use
git show <hash> -- <file>to get the diff for a specific file
Note: The reply script automatically marks all messages with "🤖 Automated 🤖" to indicate the response was generated with AI assistance. This script only works with resolvable code review comments (comments on specific lines of code), not general PR comments.
Important: When debugging on ci-1, prefer using scp to sync individual files rather than creating commits for every small edit. This avoids polluting the git history with debugging commits.
# Set up a fresh workspace for your branch
BRANCH_NAME="your-branch-name"
gcloud --quiet compute ssh root@ci-1 --zone us-central1-f --project httpjail -- "
rm -rf /tmp/httpjail-$BRANCH_NAME
git clone https://github.com/coder/httpjail /tmp/httpjail-$BRANCH_NAME
cd /tmp/httpjail-$BRANCH_NAME
git checkout $BRANCH_NAME
"
# Sync individual files during debugging (preferred over commits)
gcloud compute scp src/rules/proc.rs root@ci-1:/tmp/httpjail-$BRANCH_NAME/src/rules/ --zone us-central1-f --project httpjail
gcloud compute scp tests/json_parity.rs root@ci-1:/tmp/httpjail-$BRANCH_NAME/tests/ --zone us-central1-f --project httpjail
# Or sync entire directories if many files changed
gcloud compute scp --recurse src/ root@ci-1:/tmp/httpjail-$BRANCH_NAME/ --zone us-central1-f --project httpjail
gcloud compute scp Cargo.toml root@ci-1:/tmp/httpjail-$BRANCH_NAME/ --zone us-central1-f --project httpjail
# Build and test in the isolated workspace (using shared cargo cache)
gcloud --quiet compute ssh root@ci-1 --zone us-central1-f --project httpjail -- "
cd /tmp/httpjail-$BRANCH_NAME
export CARGO_HOME=/home/ci/.cargo
/home/ci/.cargo/bin/cargo build --profile fast
sudo ./target/fast/httpjail --help
"This ensures you don't interfere with active CI jobs and provides a clean environment for testing.