feat(docker): add isolated Claude Code environment with proxy sidecar
Two-container setup: claude (UID 1000, internal-only network) and proxy (Squid, UID 13). The internal Docker network uses internal: true so the claude container has no direct internet route. All egress is tunnelled through the Squid sidecar which enforces a domain allowlist. Both containers drop all capabilities and set no-new-privileges. claude.sh provides start/stop/run/update/logs/status/shell lifecycle management.
This commit is contained in:
commit
e0e5e03e58
10 changed files with 554 additions and 0 deletions
8
.dockerignore
Normal file
8
.dockerignore
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
.env
|
||||
*.log
|
||||
.git
|
||||
README.md
|
||||
claude.sh
|
||||
.gitignore
|
||||
.env.example
|
||||
.dockerignore
|
||||
9
.env.example
Normal file
9
.env.example
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Copy this file to .env and fill in your values.
|
||||
# .env is git-ignored — never commit it.
|
||||
|
||||
# Required: your Anthropic API key
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
|
||||
# Optional: mount a host directory as /workspace inside the Claude container.
|
||||
# If unset, a named Docker volume is used (fully isolated from the host).
|
||||
# WORKSPACE_DIR=/absolute/path/to/your/project
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.env
|
||||
*.log
|
||||
94
CLAUDE.md
Normal file
94
CLAUDE.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Project Guidance
|
||||
|
||||
This file provides context and guidance for working with this project.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**docker-claude** runs Claude Code inside a hardened Docker environment with a Squid proxy sidecar. The goal is full host encapsulation: Claude cannot access the host filesystem or network. All egress is routed through an allowlist-enforcing proxy.
|
||||
|
||||
## Architecture
|
||||
|
||||
Two containers managed by Docker Compose:
|
||||
|
||||
- **`claude`** — Claude Code CLI, non-root (UID 1000), isolated to an internal-only Docker network
|
||||
- **`proxy`** — Squid forward proxy, non-root (UID 13), bridges the internal network to the internet with an egress allowlist
|
||||
|
||||
Key Docker network property: `claude-internal` has `internal: true`, meaning Docker adds no default gateway. The `claude` container physically cannot reach the internet without going through the `proxy` container.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
docker-claude/
|
||||
├── claude.sh # Control script: start / stop / run / update / logs / status / shell
|
||||
├── docker-compose.yml # Service definitions and network topology
|
||||
├── Dockerfile.claude # Claude Code container (node:20-slim, UID 1000)
|
||||
├── Dockerfile.proxy # Squid proxy sidecar (ubuntu:22.04, UID 13)
|
||||
├── proxy/
|
||||
│ └── squid.conf # Squid ACL config — egress allowlist lives here
|
||||
├── .env.example # Template for ANTHROPIC_API_KEY
|
||||
├── .gitignore # Excludes .env and logs
|
||||
├── .dockerignore # Keeps .env out of build context
|
||||
└── README.md # User documentation
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
```bash
|
||||
chmod +x claude.sh
|
||||
cp .env.example .env # set ANTHROPIC_API_KEY
|
||||
./claude.sh start # build + start proxy + launch Claude interactively
|
||||
./claude.sh update # rebuild images (no cache) after upstream updates
|
||||
```
|
||||
|
||||
## Coding Standards
|
||||
|
||||
- Shell scripts use `set -euo pipefail`
|
||||
- Dockerfiles use `--no-install-recommends` and clean apt caches in the same layer
|
||||
- No capabilities granted; `no-new-privileges` on all containers
|
||||
- `.env` is never committed (enforced by `.gitignore` and `.dockerignore`)
|
||||
- Commit messages follow **Angular format**: `type(scope): summary`
|
||||
|
||||
## Extending the Egress Allowlist
|
||||
|
||||
Add domains to `proxy/squid.conf` under the `allowed_sites` ACL, then rebuild:
|
||||
|
||||
```bash
|
||||
docker compose -p claude-secure build proxy
|
||||
./claude.sh stop && ./claude.sh start
|
||||
```
|
||||
|
||||
## Environment Persistence
|
||||
|
||||
This sandbox has a persistent environment file at `/etc/sandbox-persistent.sh`.
|
||||
|
||||
This file is automatically sourced in all shell contexts:
|
||||
- **Non-interactive shells**: via `BASH_ENV=/etc/sandbox-persistent.sh`
|
||||
- **Login shells**: via `/etc/profile.d/sandbox-persistent.sh`
|
||||
- **Interactive shells**: via `/etc/bash.bashrc` and `~/.bashrc`
|
||||
|
||||
## Critical: Shell Completions Must NOT Be in the Persistent Environment File
|
||||
|
||||
**NEVER add shell completion scripts to `/etc/sandbox-persistent.sh`.**
|
||||
|
||||
Shell completion scripts will completely break the bash tool when sourced via the persistent environment file.
|
||||
|
||||
## Network access
|
||||
|
||||
There is a firewall in place to restrict outbound network access. If you need http/https access to
|
||||
an external service, request it by running:
|
||||
|
||||
```bash
|
||||
sbx policy allow network [domain]
|
||||
```
|
||||
|
||||
## Git Authentication
|
||||
|
||||
The sandbox proxy handles GitHub authentication automatically by injecting credentials for HTTPS Git operations.
|
||||
|
||||
## Additional Notes
|
||||
|
||||
- Always read relevant files before making changes
|
||||
- Run tests after making modifications
|
||||
- Follow the existing code structure and patterns
|
||||
- You have sudo permissions, so you can install necessary packages
|
||||
- npm, pip and uv are already available for package management
|
||||
30
Dockerfile.claude
Normal file
30
Dockerfile.claude
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FROM node:20-slim
|
||||
|
||||
# Install minimal runtime dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
curl \
|
||||
ca-certificates \
|
||||
bash \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user
|
||||
RUN groupadd -g 1000 claude \
|
||||
&& useradd -u 1000 -g claude -m -s /bin/bash claude
|
||||
|
||||
# Install Claude Code globally (runs as root for npm -g, then drops)
|
||||
RUN npm install -g @anthropic-ai/claude-code
|
||||
|
||||
# Workspace directory owned by claude user
|
||||
RUN mkdir -p /workspace && chown claude:claude /workspace
|
||||
|
||||
USER claude
|
||||
WORKDIR /workspace
|
||||
|
||||
# Proxy traffic through sidecar — override at runtime if needed
|
||||
ENV HTTP_PROXY=http://proxy:3128
|
||||
ENV HTTPS_PROXY=http://proxy:3128
|
||||
ENV ALL_PROXY=http://proxy:3128
|
||||
ENV NO_PROXY=localhost,127.0.0.1
|
||||
|
||||
ENTRYPOINT ["claude"]
|
||||
25
Dockerfile.proxy
Normal file
25
Dockerfile.proxy
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
FROM ubuntu:22.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
squid \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Give the proxy system user (UID 13) ownership of all Squid paths
|
||||
RUN mkdir -p /var/spool/squid /var/log/squid \
|
||||
&& chown -R proxy:proxy /var/spool/squid /var/log/squid /etc/squid
|
||||
|
||||
COPY --chown=proxy:proxy proxy/squid.conf /etc/squid/squid.conf
|
||||
|
||||
USER proxy
|
||||
|
||||
# Initialise cache directories as the proxy user
|
||||
RUN squid -N -f /etc/squid/squid.conf -z 2>/dev/null || true
|
||||
|
||||
EXPOSE 3128
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
|
||||
CMD /bin/bash -c 'echo >/dev/tcp/127.0.0.1/3128'
|
||||
|
||||
CMD ["squid", "-N", "-f", "/etc/squid/squid.conf"]
|
||||
120
README.md
Normal file
120
README.md
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# docker-claude
|
||||
|
||||
Runs [Claude Code](https://claude.ai/code) inside an isolated Docker environment with a proxy sidecar for controlled egress. Claude cannot reach the host filesystem or network directly.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Host machine │
|
||||
│ │
|
||||
│ claude.sh (control script) │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────┐ │
|
||||
│ │ Docker: claude-secure │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────┐ claude-internal │ │
|
||||
│ │ │ claude │◄─────(internal only)───► │ │
|
||||
│ │ │ (UID 1000) │ │ │ │
|
||||
│ │ └─────────────┘ ┌──────┴──────┐ │ │
|
||||
│ │ │ proxy │ │ │
|
||||
│ │ │ (UID 13) │ │ │
|
||||
│ │ └──────┬──────┘ │ │
|
||||
│ │ proxy-external │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ internet (allowlisted) │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **`claude` container** — Claude Code, runs as UID 1000, on `claude-internal` only (no internet route)
|
||||
- **`proxy` container** — Squid forward proxy, runs as UID 13, bridges `claude-internal` ↔ internet, enforces egress allowlist
|
||||
- **`claude-internal`** — Docker bridge with `internal: true`; Docker adds no default gateway, so containers on this network cannot reach the internet directly
|
||||
- **`proxy-external`** — Standard bridge; the proxy sidecar uses this for controlled outbound access
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker Engine 24+
|
||||
- Docker Compose v2 plugin (`docker compose version`)
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# 1. Clone / copy this repo
|
||||
git clone <repo> docker-claude && cd docker-claude
|
||||
|
||||
# 2. Configure your API key
|
||||
cp .env.example .env
|
||||
$EDITOR .env # set ANTHROPIC_API_KEY
|
||||
|
||||
# 3. Make the control script executable
|
||||
chmod +x claude.sh
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Build images, start proxy, launch Claude Code interactively
|
||||
./claude.sh start
|
||||
|
||||
# Same as start but skips image rebuild (faster on subsequent runs)
|
||||
./claude.sh run
|
||||
|
||||
# Stop and remove all containers (proxy + any running sessions)
|
||||
./claude.sh stop
|
||||
|
||||
# Rebuild images without cache (e.g. after Claude Code updates)
|
||||
./claude.sh update
|
||||
|
||||
# Tail proxy access logs
|
||||
./claude.sh logs
|
||||
|
||||
# Show container status
|
||||
./claude.sh status
|
||||
|
||||
# Open a debug bash shell inside the Claude container
|
||||
./claude.sh shell
|
||||
```
|
||||
|
||||
### Working with host files
|
||||
|
||||
By default, Claude's workspace is a named Docker volume (`claude-secure-workspace`) — fully isolated from the host.
|
||||
|
||||
To mount a specific host directory:
|
||||
|
||||
```bash
|
||||
WORKSPACE_DIR=$HOME/myproject ./claude.sh run
|
||||
```
|
||||
|
||||
The directory is mounted at `/workspace` inside the container.
|
||||
|
||||
## Egress allowlist
|
||||
|
||||
Edit `proxy/squid.conf` and add domains to the `allowed_sites` ACL:
|
||||
|
||||
```squid
|
||||
acl allowed_sites dstdomain api.anthropic.com
|
||||
acl allowed_sites dstdomain statsig.anthropic.com
|
||||
# acl allowed_sites dstdomain api.github.com # uncomment if needed
|
||||
# acl allowed_sites dstdomain registry.npmjs.org
|
||||
```
|
||||
|
||||
Rebuild the proxy after changes:
|
||||
|
||||
```bash
|
||||
docker compose -p claude-secure build proxy
|
||||
./claude.sh stop && ./claude.sh start
|
||||
```
|
||||
|
||||
## Security controls
|
||||
|
||||
| Control | Claude container | Proxy container |
|
||||
|---|---|---|
|
||||
| Non-root user | UID 1000 (`claude`) | UID 13 (`proxy`) |
|
||||
| `no-new-privileges` | yes | yes |
|
||||
| All capabilities dropped | yes | yes |
|
||||
| Direct internet access | no (`internal` network only) | allowlisted only |
|
||||
| Host filesystem | no mounts by default | none |
|
||||
| Docker socket | not mounted | not mounted |
|
||||
163
claude.sh
Executable file
163
claude.sh
Executable file
|
|
@ -0,0 +1,163 @@
|
|||
#!/usr/bin/env bash
|
||||
# claude.sh — Manage the isolated Claude Code Docker environment
|
||||
# Usage: ./claude.sh <command> [args]
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COMPOSE_FILE="$SCRIPT_DIR/docker-compose.yml"
|
||||
PROJECT="claude-secure"
|
||||
|
||||
# ─── Colours ──────────────────────────────────────────────────────────────────
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
info() { echo -e "${GREEN}[+]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
||||
error() { echo -e "${RED}[-]${NC} $*" >&2; }
|
||||
|
||||
# ─── Dependency check ─────────────────────────────────────────────────────────
|
||||
check_deps() {
|
||||
if ! command -v docker &>/dev/null; then
|
||||
error "Docker is not installed. https://docs.docker.com/get-docker/"
|
||||
exit 1
|
||||
fi
|
||||
if ! docker compose version &>/dev/null 2>&1; then
|
||||
error "Docker Compose v2 plugin is required."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Environment loading ──────────────────────────────────────────────────────
|
||||
load_env() {
|
||||
local env_file="$SCRIPT_DIR/.env"
|
||||
if [[ -f "$env_file" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
set -a; source "$env_file"; set +a
|
||||
fi
|
||||
if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
||||
error "ANTHROPIC_API_KEY is not set."
|
||||
error "Copy .env.example → .env and add your key, or export it in your shell."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Workspace volume resolution ──────────────────────────────────────────────
|
||||
# Default: named Docker volume (fully isolated).
|
||||
# Override: export WORKSPACE_DIR=/path/to/project before running.
|
||||
workspace_flag() {
|
||||
if [[ -n "${WORKSPACE_DIR:-}" ]]; then
|
||||
local abs
|
||||
abs="$(realpath "${WORKSPACE_DIR}")"
|
||||
if [[ ! -d "$abs" ]]; then
|
||||
error "WORKSPACE_DIR does not exist: $abs"
|
||||
exit 1
|
||||
fi
|
||||
echo "--volume ${abs}:/workspace:z"
|
||||
else
|
||||
echo "--volume ${PROJECT}-workspace:/workspace"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Compose wrapper ──────────────────────────────────────────────────────────
|
||||
dc() { docker compose -f "$COMPOSE_FILE" -p "$PROJECT" "$@"; }
|
||||
|
||||
# ─── Commands ─────────────────────────────────────────────────────────────────
|
||||
|
||||
cmd_start() {
|
||||
check_deps
|
||||
load_env
|
||||
info "Building images..."
|
||||
dc build
|
||||
info "Starting proxy sidecar..."
|
||||
dc up -d proxy
|
||||
info "Waiting for proxy health check..."
|
||||
dc up -d proxy # no-op if already healthy; compose waits via depends_on
|
||||
info "Launching Claude Code..."
|
||||
# shellcheck disable=SC2046
|
||||
dc run --rm $(workspace_flag) claude "$@"
|
||||
}
|
||||
|
||||
cmd_stop() {
|
||||
check_deps
|
||||
info "Stopping all containers..."
|
||||
dc down
|
||||
info "Done."
|
||||
}
|
||||
|
||||
cmd_run() {
|
||||
check_deps
|
||||
load_env
|
||||
info "Ensuring proxy is running..."
|
||||
dc up -d proxy
|
||||
info "Launching Claude Code..."
|
||||
# shellcheck disable=SC2046
|
||||
dc run --rm $(workspace_flag) claude "$@"
|
||||
}
|
||||
|
||||
cmd_update() {
|
||||
check_deps
|
||||
info "Rebuilding images (no cache)..."
|
||||
dc build --no-cache
|
||||
info "Update complete. Run './claude.sh start' to launch."
|
||||
}
|
||||
|
||||
cmd_logs() {
|
||||
check_deps
|
||||
local svc="${1:-proxy}"
|
||||
dc logs -f "$svc"
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
check_deps
|
||||
dc ps
|
||||
}
|
||||
|
||||
cmd_shell() {
|
||||
check_deps
|
||||
load_env
|
||||
warn "Opening debug shell inside Claude container (non-Claude entrypoint)."
|
||||
# shellcheck disable=SC2046
|
||||
dc run --rm --entrypoint /bin/bash $(workspace_flag) claude
|
||||
}
|
||||
|
||||
cmd_help() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") <command> [args]
|
||||
|
||||
Commands:
|
||||
start [args] Build images, start proxy, launch Claude Code
|
||||
run [args] Start proxy if needed, launch Claude Code
|
||||
stop Stop and remove all containers
|
||||
update Rebuild images without cache
|
||||
logs [svc] Tail logs (default: proxy)
|
||||
status Show container status
|
||||
shell Open a bash shell in the Claude container (debug)
|
||||
help Show this message
|
||||
|
||||
Environment variables:
|
||||
ANTHROPIC_API_KEY Required. Set in .env or exported in your shell.
|
||||
WORKSPACE_DIR Optional. Absolute path to mount as /workspace.
|
||||
Defaults to a named Docker volume (fully isolated).
|
||||
|
||||
Examples:
|
||||
./claude.sh start
|
||||
WORKSPACE_DIR=\$HOME/myproject ./claude.sh run
|
||||
./claude.sh logs proxy
|
||||
./claude.sh shell
|
||||
EOF
|
||||
}
|
||||
|
||||
# ─── Dispatch ─────────────────────────────────────────────────────────────────
|
||||
case "${1:-help}" in
|
||||
start) shift; cmd_start "$@" ;;
|
||||
stop) cmd_stop ;;
|
||||
run) shift; cmd_run "$@" ;;
|
||||
update) cmd_update ;;
|
||||
logs) shift; cmd_logs "${1:-}" ;;
|
||||
status) cmd_status ;;
|
||||
shell) cmd_shell ;;
|
||||
help|-h|--help) cmd_help ;;
|
||||
*)
|
||||
error "Unknown command: ${1}"
|
||||
cmd_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
59
docker-compose.yml
Normal file
59
docker-compose.yml
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
services:
|
||||
|
||||
# ─── Proxy sidecar ─────────────────────────────────────────────────────────
|
||||
# Bridges the isolated internal network to the internet.
|
||||
# Enforces an egress allowlist — see proxy/squid.conf.
|
||||
proxy:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.proxy
|
||||
networks:
|
||||
- claude-internal # reachable by the claude container
|
||||
- proxy-external # has outbound internet access
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp
|
||||
- /var/spool/squid
|
||||
- /var/log/squid
|
||||
|
||||
# ─── Claude Code container ─────────────────────────────────────────────────
|
||||
# No direct internet access. All egress routes through the proxy sidecar.
|
||||
# Run via "docker compose run --rm claude" (managed by claude.sh).
|
||||
claude:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.claude
|
||||
depends_on:
|
||||
proxy:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- claude-internal # only — no route to the internet
|
||||
environment:
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||
- HTTP_PROXY=http://proxy:3128
|
||||
- HTTPS_PROXY=http://proxy:3128
|
||||
- ALL_PROXY=http://proxy:3128
|
||||
- NO_PROXY=localhost,127.0.0.1
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
stdin_open: true
|
||||
tty: true
|
||||
# Workspace is injected by claude.sh via --volume flag at run time.
|
||||
# Default: named Docker volume. Override: set WORKSPACE_DIR on the host.
|
||||
|
||||
networks:
|
||||
# Internal-only: Docker adds no default gateway → no direct internet route
|
||||
claude-internal:
|
||||
driver: bridge
|
||||
internal: true
|
||||
|
||||
# External: standard bridge with internet access (proxy only)
|
||||
proxy-external:
|
||||
driver: bridge
|
||||
44
proxy/squid.conf
Normal file
44
proxy/squid.conf
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Squid forward-proxy sidecar — allowlist-only egress for Claude Code
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
http_port 3128
|
||||
|
||||
# PID must be writable by the non-root proxy user
|
||||
pid_filename /tmp/squid.pid
|
||||
|
||||
# ─── Logging (container-friendly: stdout/stderr) ──────────────────────────────
|
||||
access_log stdio:/dev/stdout combined
|
||||
cache_log stdio:/dev/stderr
|
||||
cache_store_log none
|
||||
|
||||
# ─── No disk cache ────────────────────────────────────────────────────────────
|
||||
cache deny all
|
||||
coredump_dir /var/spool/squid
|
||||
|
||||
# ─── ACL Definitions ──────────────────────────────────────────────────────────
|
||||
acl SSL_ports port 443
|
||||
acl Safe_ports port 80
|
||||
acl Safe_ports port 443
|
||||
acl CONNECT method CONNECT
|
||||
|
||||
# ─── Egress allowlist ─────────────────────────────────────────────────────────
|
||||
# Add domains here as needed. Leading dot matches all subdomains.
|
||||
acl allowed_sites dstdomain api.anthropic.com
|
||||
acl allowed_sites dstdomain statsig.anthropic.com
|
||||
|
||||
# ─── Access rules ─────────────────────────────────────────────────────────────
|
||||
# Block requests to non-standard ports
|
||||
http_access deny !Safe_ports
|
||||
|
||||
# Block CONNECT to non-SSL ports
|
||||
http_access deny CONNECT !SSL_ports
|
||||
|
||||
# Allow HTTPS tunnels only to allowlisted destinations
|
||||
http_access allow CONNECT allowed_sites
|
||||
|
||||
# Allow plain HTTP only to allowlisted destinations
|
||||
http_access allow allowed_sites
|
||||
|
||||
# Deny everything else — default deny
|
||||
http_access deny all
|
||||
Loading…
Add table
Add a link
Reference in a new issue