Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@ webpki-roots = "0.26"
lru = "0.12"
rand = "0.8"
anyhow = "1.0"
base64 = "0.22"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "chrono"] }
chrono = "0.4"
dirs = "6.0.0"
hyper-rustls = "0.27.7"
tower-service = "0.3"
tls-parser = "0.12.2"
camino = "1.1.11"
filetime = "0.2"
ctrlc = "3.4"
percent-encoding = "2.3"
url = "2.5"
v8 = "129"
serde = { version = "1.0", features = ["derive"] }
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Or download a pre-built binary from the [releases page](https://github.com/coder
- 🌐 **HTTP/HTTPS interception** - Transparent proxy with TLS certificate injection
- 🛡️ **DNS exfiltration protection** - Prevents data leakage through DNS queries
- 🔧 **Multiple evaluation approaches** - JS expressions or custom programs
- 🏢 **Upstream proxy support** - Chain httpjail's egress through a corporate proxy
- 🖥️ **Cross-platform** - Native support for Linux and macOS

## Quick Start
Expand Down Expand Up @@ -61,8 +62,27 @@ httpjail --server --js "true"

# Run Docker containers with network isolation (Linux only)
httpjail --js "r.host === 'api.github.com'" --docker-run -- --rm alpine:latest wget -qO- https://api.github.com

# Route httpjail's own egress through an upstream (corporate) proxy
HTTPS_PROXY=http://proxy.corp:3128 httpjail --js "true" -- curl https://api.github.com
# Credentials and HTTPS proxies are supported: http://user:pass@proxy.corp:3128, https://proxy.corp:8443
```

### Upstream (corporate) proxy

When httpjail itself runs in an environment with no direct internet access, set
the `HTTP_PROXY` and/or `HTTPS_PROXY` environment variables to route httpjail's
outbound requests through an upstream proxy. Rule evaluation still happens
locally on the intercepted traffic; only the re-originated request is forwarded
through the proxy.

- `http://`, `https://` and bare `host:port` (http assumed) forms are accepted.
- Basic authentication is supported via `http://user:pass@host:port`.
- HTTPS destinations are reached via a `CONNECT` tunnel through the proxy, while
plain HTTP destinations are forwarded in absolute-form.
- In weak mode, httpjail overwrites proxy env vars inside the jailed process to
point sandboxed processes at httpjail itself.

## Documentation

Docs are stored in the `docs/` directory and served
Expand All @@ -82,6 +102,7 @@ Table of Contents:
- [TLS Interception](https://coder.github.io/httpjail/advanced/tls-interception.html)
- [DNS Exfiltration](https://coder.github.io/httpjail/advanced/dns-exfiltration.html)
- [Server Mode](https://coder.github.io/httpjail/advanced/server-mode.html)
- [Upstream Proxy](https://coder.github.io/httpjail/advanced/upstream-proxy.html)

## License

Expand Down
1 change: 1 addition & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- [TLS Interception](./advanced/tls-interception.md)
- [DNS Exfiltration](./advanced/dns-exfiltration.md)
- [Server Mode](./advanced/server-mode.md)
- [Upstream Proxy](./advanced/upstream-proxy.md)

---

Expand Down
62 changes: 62 additions & 0 deletions docs/advanced/upstream-proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Upstream Proxy

By default httpjail contacts destination servers directly. When httpjail itself
runs in an environment that has no direct internet access — for example behind a
corporate proxy — you can route httpjail's own outbound requests through an
upstream proxy with the `HTTP_PROXY` and/or `HTTPS_PROXY` environment variables.

Rule evaluation still happens locally on the intercepted traffic. Only the
request that httpjail re-originates towards the real destination is forwarded
through the upstream proxy.

```bash
# Route httpjail's HTTPS egress through a corporate proxy
HTTPS_PROXY=http://proxy.corp:3128 httpjail --js "true" -- curl https://api.github.com

# Route both HTTP and HTTPS egress through the same proxy
HTTP_PROXY=http://proxy.corp:3128 HTTPS_PROXY=http://proxy.corp:3128 \
httpjail --js "true" -- ./my-app

# With Basic authentication
HTTPS_PROXY=http://user:pass@proxy.corp:3128 httpjail --js "true" -- ./my-app

# Through an HTTPS proxy
HTTPS_PROXY=https://proxy.corp:8443 httpjail --js "true" -- ./my-app
```

## Accepted formats

| Form | Example | Notes |
| --- | --- | --- |
| `http://host:port` | `http://proxy.corp:3128` | Plain HTTP proxy |
| `https://host:port` | `https://proxy.corp:8443` | Connection to the proxy is wrapped in TLS |
| `host:port` | `proxy.corp:3128` | Bare authority, `http` scheme assumed |
| With credentials | `http://user:pass@proxy.corp:3128` | Sends `Proxy-Authorization: Basic ...` |

`HTTP_PROXY` is used for `http://` destinations. `HTTPS_PROXY` is used for
`https://` destinations. Credentials are never written to the logs.

## How it works

- **HTTPS destinations** are reached by issuing a `CONNECT` to the upstream
proxy to obtain a raw TCP tunnel; httpjail then performs the destination TLS
handshake over that tunnel. TLS is validated against Mozilla's webpki roots
plus the httpjail CA, exactly as for a direct connection.
- **Plain HTTP destinations** are forwarded to the proxy in absolute-form, with
the `Proxy-Authorization` header attached when credentials are configured.
- Only connection setup (TCP connect, optional TLS to the proxy, and the
`CONNECT` exchange) is bounded by a timeout. The established tunnel carries no
timeout, so long-running connections such as WebSocket and gRPC keep working.

## Relationship to jailed process proxy variables

The proxy environment variables configure httpjail's own egress. In weak mode,
httpjail overwrites `HTTP_PROXY` and `HTTPS_PROXY` inside the jailed process to
point sandboxed processes at httpjail itself.

```
[ jailed process ] --> [ httpjail ] --HTTP_PROXY/HTTPS_PROXY--> [ corporate proxy ] --> internet
```

The jailed process talks to httpjail; the proxy env vars only affect the hop
from httpjail to the outside world.
22 changes: 14 additions & 8 deletions docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ httpjail's behavior can be configured through command-line options, environment
httpjail follows a simple configuration hierarchy:

1. **Command-line options** - Highest priority, override everything
2. **Environment variables** - Set by httpjail for the jailed process
2. **Environment variables** - Configure httpjail and the jailed process

## Key Configuration Areas

Expand Down Expand Up @@ -72,9 +72,11 @@ httpjail --proc ./rate-limiter.py \

## Environment Variables

### Set by httpjail
### Set for the jailed process

These are automatically set in the jailed process:
These are set in the jailed process where applicable. In weak mode, httpjail
sets proxy variables so applications talk to httpjail. On Linux strong mode,
traffic is redirected transparently without setting proxy variables.

| Variable | Description | Example |
| --------------- | ---------------------------- | ------------------------ |
Expand All @@ -84,14 +86,18 @@ These are automatically set in the jailed process:
| `SSL_CERT_DIR` | CA certificate directory | `/tmp/httpjail-certs/` |
| `NO_PROXY` | Bypass proxy for these hosts | `localhost,127.0.0.1` |

### Controlling httpjail
### Consumed by httpjail

These affect httpjail's behavior:

| Variable | Description | Example |
| ------------------ | -------------------------- | -------------------------------- |
| `RUST_LOG` | Logging level | `debug`, `info`, `warn`, `error` |
| `HTTPJAIL_CA_CERT` | Custom CA certificate path | `/etc/pki/custom-ca.pem` |
| Variable | Description | Example |
| ------------------------ | -------------------------------------- | -------------------------------- |
| `RUST_LOG` | Logging level | `debug`, `info`, `warn`, `error` |
| `HTTPJAIL_CA_CERT` | Custom CA certificate path | `/etc/pki/custom-ca.pem` |
| `HTTP_PROXY` | Upstream proxy for httpjail HTTP egress | `http://proxy.corp:3128` |
| `HTTPS_PROXY` | Upstream proxy for httpjail HTTPS egress | `http://proxy.corp:3128` |

See [Upstream Proxy](../advanced/upstream-proxy.md) for details.

## Platform-Specific Configuration

Expand Down
7 changes: 7 additions & 0 deletions src/jail/linux/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,13 @@ impl DockerLinux {
let mut cmd = Command::new("docker");
cmd.arg("run");

// The parent process may use proxy env vars for httpjail's own egress.
// Do not leak those credentials or settings into the Docker CLI process;
// Docker network isolation routes container traffic through httpjail.
for key in ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"] {
cmd.env_remove(key);
}

// Use our isolated Docker network
cmd.args(["--network", &network_name]);

Expand Down
7 changes: 7 additions & 0 deletions src/jail/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,13 @@ impl Jail for LinuxJail {
cmd.env(key, value);
}

// The parent process may use proxy env vars for httpjail's own egress.
// Do not leak those credentials or settings into the jailed command;
// native Linux isolation redirects traffic transparently.
for key in ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"] {
cmd.env_remove(key);
}

// Preserve SUDO environment variables for consistency with macOS
if let Ok(sudo_user) = std::env::var("SUDO_USER") {
cmd.env("SUDO_USER", sudo_user);
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ pub mod proxy_tls;
pub mod rules;
pub mod sys_resource;
pub mod tls;
pub mod upstream;

pub mod test_utils;
37 changes: 35 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use httpjail::rules::shell::ShellRuleEngine;
use httpjail::rules::v8_js::V8JsRuleEngine;
use httpjail::rules::{Action, RuleEngine};
use hyper::Method;
use std::fmt;
use std::fs::OpenOptions;
use std::os::unix::process::ExitStatusExt;
use std::sync::atomic::{AtomicBool, Ordering};
Expand Down Expand Up @@ -40,7 +41,7 @@ enum Command {
},
}

#[derive(Parser, Debug)]
#[derive(Parser)]
struct RunArgs {
/// Use shell script for evaluating requests
/// The script receives environment variables:
Expand Down Expand Up @@ -139,6 +140,27 @@ struct RunArgs {
exec_command: Vec<String>,
}

impl fmt::Debug for RunArgs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RunArgs")
.field("sh", &self.sh)
.field("proc", &self.proc)
.field("js", &self.js)
.field("js_file", &self.js_file)
.field("request_log", &self.request_log)
.field("weak", &self.weak)
.field("verbose", &self.verbose)
.field("timeout", &self.timeout)
.field("no_jail_cleanup", &self.no_jail_cleanup)
.field("cleanup", &self.cleanup)
.field("server", &self.server)
.field("test", &self.test)
.field("docker_run", &self.docker_run)
.field("exec_command", &self.exec_command)
.finish()
}
}

fn setup_logging(verbosity: u8) {
use tracing_subscriber::fmt::time::FormatTime;

Expand Down Expand Up @@ -590,7 +612,18 @@ async fn main() -> Result<()> {
}
};

let mut proxy = ProxyServer::new(http_bind, https_bind, rule_engine);
let upstream_proxies = httpjail::upstream::UpstreamProxies::from_env()
.context("Failed to configure upstream proxy from environment")?;
if upstream_proxies.is_some() {
debug!("Routing httpjail upstream requests through the proxy environment");
}

let mut proxy = ProxyServer::new_with_upstream_proxies(
http_bind,
https_bind,
rule_engine,
upstream_proxies,
);

// Start proxy in background if running as server; otherwise start with random ports
let (actual_http_port, actual_https_port) = proxy.start().await?;
Expand Down
Loading