# ===================================================================== # owuitools egress proxy - THE authoritative SSRF boundary. # # owuitools-api sits on an `internal: true` network with no default # route. Every byte it sends to the internet goes through here, so this # file - not the application's URL validation - is what actually stops a # guest from talking the assistant into fetching http://192.168.1.10:9443 # or http://172.19.0.2:8080/api/. # # Squid resolves destination hostnames ITSELF and applies the ACLs to the # address it resolved. That is the point: it means a DNS rebinding attack, # where a name passes the app's check and then resolves to a private # address a moment later, still dies here. # ===================================================================== http_port 3128 # Only our own core network may use the proxy at all. acl core_net src 172.22.0.0/24 # --- Destination denial ------------------------------------------------- # Everything RFC1918/loopback/link-local/CGNAT/documentation/reserved. # 100.64.0.0/10 (CGNAT) is in here deliberately: this host may sit behind # a carrier-grade NAT, and that range can reach other subscribers. acl to_private dst 0.0.0.0/8 acl to_private dst 10.0.0.0/8 acl to_private dst 100.64.0.0/10 acl to_private dst 127.0.0.0/8 acl to_private dst 169.254.0.0/16 acl to_private dst 172.16.0.0/12 acl to_private dst 192.0.0.0/24 acl to_private dst 192.0.2.0/24 acl to_private dst 192.168.0.0/16 acl to_private dst 198.18.0.0/15 acl to_private dst 198.51.100.0/24 acl to_private dst 203.0.113.0/24 acl to_private dst 224.0.0.0/4 acl to_private dst 240.0.0.0/4 acl to_private6 dst ::/128 acl to_private6 dst ::1/128 acl to_private6 dst fe80::/10 acl to_private6 dst fc00::/7 # --- Port restriction --------------------------------------------------- acl Safe_ports port 80 acl Safe_ports port 443 acl SSL_ports port 443 acl CONNECT method CONNECT # --- Policy (order matters; first match wins) --------------------------- http_access deny to_private http_access deny to_private6 http_access deny !Safe_ports http_access deny CONNECT !SSL_ports http_access allow core_net http_access deny all # --- Behaviour ---------------------------------------------------------- # Stateless: memory cache only, no cache_dir, so the container needs no # writable spool and can run with a read-only rootfs. cache deny all cache_mem 16 MB maximum_object_size_in_memory 512 KB # Don't advertise the proxy or the client to upstream sites. forwarded_for delete via off httpd_suppress_version_string on request_header_access X-Forwarded-For deny all # Fail fast rather than hanging a tool call. connect_timeout 10 seconds read_timeout 45 seconds request_timeout 45 seconds dns_v4_first on pid_filename none # Audit trail: which tool reached for what. stdout so `docker logs` has it. logfile_rotate 0 # squid creates its log files 0640 proxy:proxy, which the operator cannot # read - and a log nobody can open is not an audit trail. This makes them # world-readable. They contain URLs the assistant fetched, nothing secret. umask 022 # THE AUDIT TRAIL: one line per outbound request, allowed or denied. # Written to both, so `docker compose logs egress` keeps working and # ./logs has it alongside every other component. access_log stdio:/dev/stdout squid access_log stdio:/logs/egress.access.log squid cache_log stdio:/logs/egress.cache.log