feat(agent): add a squid proxy for agent sandbox (#39544)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Yunlu Wen 2026-07-27 14:21:20 +08:00 committed by GitHub
parent 989039db6e
commit e1b55f54e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 346 additions and 118 deletions

View File

@ -531,6 +531,14 @@ services:
- ssrf_proxy_network
# Local sandbox for Dify Agent shell workspaces.
# Network isolation: local_sandbox has NO direct route to `api`. Its only
# networks are `agent_sandbox_network` (so agent_backend can reach it on 5004
# for shellctl, and it can reach agent_backend directly) and
# `local_sandbox_proxy_network` (so its egress is forced through
# agent_ssrf_proxy).
# All non-agent_backend/localhost traffic goes through the Squid forward proxy
# on port 3128, which only allows agent_backend /agent-stub/ and the Dify API
# /files/* endpoints (see ssrf_proxy/squid-agent.conf.template).
local_sandbox:
image: langgenius/dify-agent-local-sandbox:1.16.0
restart: always
@ -539,6 +547,9 @@ services:
required: false
environment:
- SHELLCTL_AUTH_TOKEN=${DIFY_AGENT_SHELLCTL_AUTH_TOKEN:-}
- HTTP_PROXY=http://agent_ssrf_proxy:3128
- HTTPS_PROXY=http://agent_ssrf_proxy:3128
- NO_PROXY=localhost,127.0.0.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5004/healthz"]
interval: 30s
@ -546,7 +557,8 @@ services:
retries: 3
start_period: 10s
networks:
- default
- agent_sandbox_network
- local_sandbox_proxy_network
# plugin daemon
plugin_daemon:
@ -672,6 +684,33 @@ services:
condition: service_started
networks:
- default
# Shared internal network with local_sandbox so agent_backend can reach it
# on port 5004 (shellctl entrypoint) while local_sandbox stays off `default`.
- agent_sandbox_network
# Dedicated SSRF proxy for the dify-agent local_sandbox.
agent_ssrf_proxy:
image: ubuntu/squid:latest
restart: always
volumes:
- ./ssrf_proxy/squid-agent.conf.template:/etc/squid/squid.conf.template
- ./ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template
- ./ssrf_proxy/docker-agent-entrypoint.sh:/docker-entrypoint-mount.sh
entrypoint:
[
"sh",
"-c",
"cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh",
]
environment:
HTTP_PORT: ${SSRF_HTTP_PORT:-3128}
COREDUMP_DIR: ${SSRF_COREDUMP_DIR:-/var/spool/squid}
networks:
# Needs to reach api and agent_backend as forward-proxy destinations.
- default
# Only agent_ssrf_proxy and local_sandbox share this internal network, so
# the local_sandbox can reach Squid without gaining a direct route to `api`.
- local_sandbox_proxy_network
# ssrf_proxy server
# for more information, please refer to
@ -681,6 +720,7 @@ services:
restart: always
volumes:
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
- ./ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
entrypoint:
[
@ -1253,6 +1293,19 @@ networks:
ssrf_proxy_network:
driver: bridge
internal: true
# Internal network shared only by agent_ssrf_proxy and local_sandbox.
local_sandbox_proxy_network:
driver: bridge
internal: true
# shellctl control channel (agent_backend -> local_sandbox:5004).
# sandbox can access agent backend through this network, this is
# a known limitation.
#
# The agent runtime respects HTTP(S)_PROXY, but arbitrary code execution
# is still possible through the shellctl channel.
agent_sandbox_network:
driver: bridge
internal: true
milvus:
driver: bridge
opensearch-net:

View File

@ -199,6 +199,7 @@ services:
restart: always
volumes:
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
- ./ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
entrypoint:
[

View File

@ -537,6 +537,14 @@ services:
- ssrf_proxy_network
# Local sandbox for Dify Agent shell workspaces.
# Network isolation: local_sandbox has NO direct route to `api`. Its only
# networks are `agent_sandbox_network` (so agent_backend can reach it on 5004
# for shellctl, and it can reach agent_backend directly) and
# `local_sandbox_proxy_network` (so its egress is forced through
# agent_ssrf_proxy).
# All non-agent_backend/localhost traffic goes through the Squid forward proxy
# on port 3128, which only allows agent_backend /agent-stub/ and the Dify API
# /files/* endpoints (see ssrf_proxy/squid-agent.conf.template).
local_sandbox:
image: langgenius/dify-agent-local-sandbox:1.16.0
restart: always
@ -545,6 +553,9 @@ services:
required: false
environment:
- SHELLCTL_AUTH_TOKEN=${DIFY_AGENT_SHELLCTL_AUTH_TOKEN:-}
- HTTP_PROXY=http://agent_ssrf_proxy:3128
- HTTPS_PROXY=http://agent_ssrf_proxy:3128
- NO_PROXY=localhost,127.0.0.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5004/healthz"]
interval: 30s
@ -552,7 +563,8 @@ services:
retries: 3
start_period: 10s
networks:
- default
- agent_sandbox_network
- local_sandbox_proxy_network
# plugin daemon
plugin_daemon:
@ -678,6 +690,33 @@ services:
condition: service_started
networks:
- default
# Shared internal network with local_sandbox so agent_backend can reach it
# on port 5004 (shellctl entrypoint) while local_sandbox stays off `default`.
- agent_sandbox_network
# Dedicated SSRF proxy for the dify-agent local_sandbox.
agent_ssrf_proxy:
image: ubuntu/squid:latest
restart: always
volumes:
- ./ssrf_proxy/squid-agent.conf.template:/etc/squid/squid.conf.template
- ./ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template
- ./ssrf_proxy/docker-agent-entrypoint.sh:/docker-entrypoint-mount.sh
entrypoint:
[
"sh",
"-c",
"cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh",
]
environment:
HTTP_PORT: ${SSRF_HTTP_PORT:-3128}
COREDUMP_DIR: ${SSRF_COREDUMP_DIR:-/var/spool/squid}
networks:
# Needs to reach api and agent_backend as forward-proxy destinations.
- default
# Only agent_ssrf_proxy and local_sandbox share this internal network, so
# the local_sandbox can reach Squid without gaining a direct route to `api`.
- local_sandbox_proxy_network
# ssrf_proxy server
# for more information, please refer to
@ -687,6 +726,7 @@ services:
restart: always
volumes:
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
- ./ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
entrypoint:
[
@ -1259,6 +1299,19 @@ networks:
ssrf_proxy_network:
driver: bridge
internal: true
# Internal network shared only by agent_ssrf_proxy and local_sandbox.
local_sandbox_proxy_network:
driver: bridge
internal: true
# shellctl control channel (agent_backend -> local_sandbox:5004).
# sandbox can access agent backend through this network, this is
# a known limitation.
#
# The agent runtime respects HTTP(S)_PROXY, but arbitrary code execution
# is still possible through the shellctl channel.
agent_sandbox_network:
driver: bridge
internal: true
milvus:
driver: bridge
opensearch-net:

View File

@ -0,0 +1,25 @@
#!/bin/bash
tail -F /var/log/squid/access.log 2>/dev/null &
tail -F /var/log/squid/error.log 2>/dev/null &
tail -F /var/log/squid/store.log 2>/dev/null &
tail -F /var/log/squid/cache.log 2>/dev/null &
expand_env() {
awk '{
while(match($0, /\${[A-Za-z_][A-Za-z_0-9]*}/)) {
var = substr($0, RSTART+2, RLENGTH-3)
val = ENVIRON[var]
$0 = substr($0, 1, RSTART-1) val substr($0, RSTART+RLENGTH)
}
print
}' "$1"
}
echo "[ENTRYPOINT] replacing environment variables in the templates"
expand_env /etc/squid/squid.conf.template > /etc/squid/squid.conf
expand_env /etc/squid/dify_common.conf.template > /etc/squid/dify_common.conf
/usr/sbin/squid -Nz
echo "[ENTRYPOINT] starting squid"
/usr/sbin/squid -f /etc/squid/squid.conf -NYC 1

View File

@ -74,16 +74,22 @@ if [ -n "${SSRF_SANDBOX_PROXY_PORT:-}" ]; then
} >> "$SANDBOX_PROXY_CONF"
fi
# Replace environment variables in the template and output to the squid.conf
echo "[ENTRYPOINT] replacing environment variables in the template"
awk '{
while(match($0, /\${[A-Za-z_][A-Za-z_0-9]*}/)) {
var = substr($0, RSTART+2, RLENGTH-3)
val = ENVIRON[var]
$0 = substr($0, 1, RSTART-1) val substr($0, RSTART+RLENGTH)
}
print
}' /etc/squid/squid.conf.template > /etc/squid/squid.conf
# Replace environment variables in a template file.
expand_env() {
awk '{
while(match($0, /\${[A-Za-z_][A-Za-z_0-9]*}/)) {
var = substr($0, RSTART+2, RLENGTH-3)
val = ENVIRON[var]
$0 = substr($0, 1, RSTART-1) val substr($0, RSTART+RLENGTH)
}
print
}' "$1"
}
# Replace environment variables in the templates and output to squid.conf
echo "[ENTRYPOINT] replacing environment variables in the templates"
expand_env /etc/squid/squid.conf.template > /etc/squid/squid.conf
expand_env /etc/squid/dify_common.conf.template > /etc/squid/dify_common.conf
/usr/sbin/squid -Nz
echo "[ENTRYPOINT] starting squid"

View File

@ -0,0 +1,21 @@
# Dedicated Squid config for the dify-agent local_sandbox SSRF proxy.
# All traffic on this proxy is restricted to:
# - agent_backend /agent-stub/* endpoints
# - Dify API /files/* endpoints (signed upload/download URLs)
# External internet is allowed; all other private-network destinations are denied.
include /etc/squid/dify_common.conf
acl dst_agent_backend dstdomain agent_backend
acl dst_dify_api dstdomain api
acl path_files urlpath_regex -i ^/files/
acl path_agent_stub urlpath_regex -i ^/agent-stub/
http_port ${HTTP_PORT}
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow dst_agent_backend path_agent_stub
http_access allow dst_dify_api path_files
http_access deny to_private_networks
http_access allow all

View File

@ -0,0 +1,90 @@
# Shared Squid configuration used by both ssrf_proxy and agent_ssrf_proxy.
################################## ACL Definitions ################################
acl client_localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN)
acl client_localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN)
acl client_localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN)
acl client_localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
acl client_localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN)
acl client_localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN)
acl client_localnet src fc00::/7 # RFC 4193 local private network range
acl client_localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines
acl to_private_networks dst 0.0.0.0/8
acl to_private_networks dst 10.0.0.0/8
acl to_private_networks dst 100.64.0.0/10
acl to_private_networks dst 127.0.0.0/8
acl to_private_networks dst 169.254.0.0/16
acl to_private_networks dst 172.16.0.0/12
acl to_private_networks dst 192.168.0.0/16
acl to_private_networks dst 224.0.0.0/4
acl to_private_networks dst 240.0.0.0/4
acl to_private_networks dst ::/128
acl to_private_networks dst ::1/128
acl to_private_networks dst ::ffff:0:0/96 # IPv4-mapped
acl to_private_networks dst ::/96 # deprecated IPv4-compatible
acl to_private_networks dst fc00::/7
acl to_private_networks dst fe80::/10
acl SSL_ports port 443
acl Safe_ports port 80 # http
acl Safe_ports port 21 # ftp
acl Safe_ports port 443 # https
acl Safe_ports port 70 # gopher
acl Safe_ports port 210 # wais
acl Safe_ports port 1025-65535 # unregistered ports
acl Safe_ports port 280 # http-mgmt
acl Safe_ports port 488 # gss-http
acl Safe_ports port 591 # filemaker
acl Safe_ports port 777 # multiling http
acl CONNECT method CONNECT
################################## Common Parameters ################################
tcp_outgoing_address 0.0.0.0
################################## Proxy Server ################################
coredump_dir ${COREDUMP_DIR}
refresh_pattern ^ftp: 1440 20% 10080
refresh_pattern ^gopher: 1440 0% 1440
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims
refresh_pattern \/InRelease$ 0 0% 0 refresh-ims
refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
refresh_pattern . 0 20% 4320
################################## Request Buffer ################################
client_request_buffer_max_size 100 MB
################################## Performance & Concurrency ###############################
max_filedescriptors 65536
connect_timeout 30 seconds
request_timeout 2 minutes
read_timeout 2 minutes
client_lifetime 5 minutes
shutdown_lifetime 30 seconds
server_persistent_connections on
client_persistent_connections on
persistent_request_timeout 30 seconds
pconn_timeout 1 minute
client_db on
server_idle_pconn_timeout 2 minutes
client_idle_pconn_timeout 2 minutes
quick_abort_min 16 KB
quick_abort_max 16 MB
quick_abort_pct 95
memory_cache_mode disk
cache_mem 256 MB
maximum_object_size_in_memory 512 KB
dns_timeout 30 seconds
dns_retransmit_interval 5 seconds
logformat dify_log %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
access_log daemon:/var/log/squid/access.log dify_log
logfile_rotate 10

View File

@ -1,39 +1,5 @@
acl client_localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN)
acl client_localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN)
acl client_localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN)
acl client_localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
acl client_localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN)
acl client_localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN)
acl client_localnet src fc00::/7 # RFC 4193 local private network range
acl client_localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines
acl to_private_networks dst 0.0.0.0/8
acl to_private_networks dst 10.0.0.0/8
acl to_private_networks dst 100.64.0.0/10
acl to_private_networks dst 127.0.0.0/8
acl to_private_networks dst 169.254.0.0/16
acl to_private_networks dst 172.16.0.0/12
acl to_private_networks dst 192.168.0.0/16
acl to_private_networks dst 224.0.0.0/4
acl to_private_networks dst 240.0.0.0/4
acl to_private_networks dst ::/128
acl to_private_networks dst ::1/128
acl to_private_networks dst ::ffff:0:0/96 # IPv4-mapped
acl to_private_networks dst ::/96 # deprecated IPv4-compatible
acl to_private_networks dst fc00::/7
acl to_private_networks dst fe80::/10
acl SSL_ports port 443
# acl SSL_ports port 1025-65535 # Enable the configuration to resolve this issue: https://github.com/langgenius/dify/issues/12792
acl Safe_ports port 80 # http
acl Safe_ports port 21 # ftp
acl Safe_ports port 443 # https
acl Safe_ports port 70 # gopher
acl Safe_ports port 210 # wais
acl Safe_ports port 1025-65535 # unregistered ports
acl Safe_ports port 280 # http-mgmt
acl Safe_ports port 488 # gss-http
acl Safe_ports port 591 # filemaker
acl Safe_ports port 777 # multiling http
acl CONNECT method CONNECT
include /etc/squid/dify_common.conf
acl allowed_domains dstdomain .marketplace.dify.ai
http_port ${HTTP_PORT}
@ -49,73 +15,3 @@ http_access allow allowed_domains
http_access allow client_localnet
http_access allow localhost
http_access deny all
tcp_outgoing_address 0.0.0.0
################################## Proxy Server ################################
coredump_dir ${COREDUMP_DIR}
refresh_pattern ^ftp: 1440 20% 10080
refresh_pattern ^gopher: 1440 0% 1440
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims
refresh_pattern \/InRelease$ 0 0% 0 refresh-ims
refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
refresh_pattern . 0 20% 4320
# cache_dir ufs /var/spool/squid 100 16 256
# upstream proxy, set to your own upstream proxy IP to avoid SSRF attacks
# cache_peer 172.1.1.1 parent 3128 0 no-query no-digest no-netdb-exchange default
################################## Request Buffer ################################
# Unless the option's size is increased, an error will occur when uploading more than two files.
client_request_buffer_max_size 100 MB
################################## Performance & Concurrency ###############################
# Increase file descriptor limit for high concurrency
max_filedescriptors 65536
# Timeout configurations for image requests
connect_timeout 30 seconds
request_timeout 2 minutes
read_timeout 2 minutes
client_lifetime 5 minutes
shutdown_lifetime 30 seconds
# Persistent connections - improve performance for multiple requests
server_persistent_connections on
client_persistent_connections on
persistent_request_timeout 30 seconds
pconn_timeout 1 minute
# Connection pool and concurrency limits
client_db on
server_idle_pconn_timeout 2 minutes
client_idle_pconn_timeout 2 minutes
# Quick abort settings - don't abort requests that are mostly done
quick_abort_min 16 KB
quick_abort_max 16 MB
quick_abort_pct 95
# Memory and cache optimization
memory_cache_mode disk
cache_mem 256 MB
maximum_object_size_in_memory 512 KB
# DNS resolver settings for better performance
dns_timeout 30 seconds
dns_retransmit_interval 5 seconds
# By default, Squid uses the system's configured DNS resolvers.
# If you need to override them, set dns_nameservers to appropriate servers
# for your environment (for example, internal/corporate DNS). The following
# is an example using public DNS and SHOULD be customized before use:
# dns_nameservers 8.8.8.8 8.8.4.4
# Logging format for better debugging
logformat dify_log %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
access_log daemon:/var/log/squid/access.log dify_log
# Access log to track concurrent requests and timeouts
logfile_rotate 10

View File

@ -6,12 +6,18 @@ IMAGE="${SSRF_PROXY_TEST_IMAGE:-ubuntu/squid:latest}"
CLIENT_IMAGE="${SSRF_PROXY_TEST_CLIENT_IMAGE:-busybox:latest}"
CONTAINER_NAME="${SSRF_PROXY_TEST_CONTAINER:-dify-ssrf-proxy-test-$$}"
SANDBOX_CONTAINER_NAME="${CONTAINER_NAME}-sandbox"
AGENT_PROXY_CONTAINER_NAME="${CONTAINER_NAME}-agent-proxy"
API_CONTAINER_NAME="${CONTAINER_NAME}-api"
AGENT_BACKEND_CONTAINER_NAME="${CONTAINER_NAME}-agent-backend"
NETWORK_NAME="${SSRF_PROXY_TEST_NETWORK:-dify-ssrf-proxy-test-$$}"
RUN_PUBLIC_CHECK="${SSRF_PROXY_TEST_PUBLIC_CHECK:-true}"
cleanup() {
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
docker rm -f "$SANDBOX_CONTAINER_NAME" >/dev/null 2>&1 || true
docker rm -f "$AGENT_PROXY_CONTAINER_NAME" >/dev/null 2>&1 || true
docker rm -f "$API_CONTAINER_NAME" >/dev/null 2>&1 || true
docker rm -f "$AGENT_BACKEND_CONTAINER_NAME" >/dev/null 2>&1 || true
docker network rm "$NETWORK_NAME" >/dev/null 2>&1 || true
}
@ -106,6 +112,7 @@ docker run \
--entrypoint sh \
--network "$NETWORK_NAME" \
--volume "$ROOT_DIR/docker/ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template:ro" \
--volume "$ROOT_DIR/docker/ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template:ro" \
--volume "$ROOT_DIR/docker/ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh:ro" \
--env HTTP_PORT=3128 \
--env COREDUMP_DIR=/var/spool/squid \
@ -141,3 +148,79 @@ if [[ "$RUN_PUBLIC_CHECK" == "true" ]]; then
fi
assert_sandbox_bridge_allowed "http://$CONTAINER_NAME:8194/health"
# ---------------------------------------------------------------------------
# agent_ssrf_proxy tests
# ---------------------------------------------------------------------------
# Mock api server: serves /files/* (200) and everything else (404).
docker run \
--detach \
--name "$API_CONTAINER_NAME" \
--network "$NETWORK_NAME" \
--network-alias api \
"$CLIENT_IMAGE" \
sh -c "mkdir -p /www/files && echo file-ok > /www/files/test && echo denied > /www/index.html && httpd -f -p 5001 -h /www" \
>/dev/null
# Mock agent_backend server: serves /agent-stub/* (200) and everything else (404).
docker run \
--detach \
--name "$AGENT_BACKEND_CONTAINER_NAME" \
--network "$NETWORK_NAME" \
--network-alias agent_backend \
"$CLIENT_IMAGE" \
sh -c "mkdir -p /www/agent-stub && echo stub-ok > /www/agent-stub/config && echo denied > /www/index.html && httpd -f -p 5050 -h /www" \
>/dev/null
docker run \
--detach \
--name "$AGENT_PROXY_CONTAINER_NAME" \
--entrypoint sh \
--network "$NETWORK_NAME" \
--volume "$ROOT_DIR/docker/ssrf_proxy/squid-agent.conf.template:/etc/squid/squid.conf.template:ro" \
--volume "$ROOT_DIR/docker/ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template:ro" \
--volume "$ROOT_DIR/docker/ssrf_proxy/docker-agent-entrypoint.sh:/docker-entrypoint-mount.sh:ro" \
--env HTTP_PORT=3128 \
--env COREDUMP_DIR=/var/spool/squid \
"$IMAGE" \
-c "cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh" \
>/dev/null
agent_proxy_url="http://$AGENT_PROXY_CONTAINER_NAME:3128"
for _ in {1..30}; do
agent_probe_status="$(http_code_for "$agent_proxy_url" "http://127.0.0.1:80/")"
if [[ -n "$agent_probe_status" ]]; then
break
fi
sleep 1
done
if [[ -z "${agent_probe_status:-}" ]]; then
echo "Agent SSRF proxy did not respond to probes."
docker logs "$AGENT_PROXY_CONTAINER_NAME" >&2 || true
exit 1
fi
# Private targets must be blocked.
assert_private_target_blocked "$agent_proxy_url" "http://127.0.0.1:80/"
assert_private_target_blocked "$agent_proxy_url" "http://169.254.169.254/latest/meta-data/"
# agent_backend /agent-stub/* must be allowed.
assert_public_target_allowed "$agent_proxy_url" "http://agent_backend:5050/agent-stub/config"
# agent_backend non-/agent-stub paths must be blocked (403 from Squid).
assert_private_target_blocked "$agent_proxy_url" "http://agent_backend:5050/index.html"
# api /files/* must be allowed.
assert_public_target_allowed "$agent_proxy_url" "http://api:5001/files/test"
# api non-/files paths must be blocked.
assert_private_target_blocked "$agent_proxy_url" "http://api:5001/index.html"
# External internet must be allowed.
if [[ "$RUN_PUBLIC_CHECK" == "true" ]]; then
assert_public_target_allowed "$agent_proxy_url" "http://example.com/"
fi
echo "All SSRF proxy tests passed."