Back to KB
Difficulty
Intermediate
Read Time
7 min

Three Things "Set HTTPS_PROXY" Cannot Stop

By Codcompass Team··7 min read

Enforcing Egress: Why Environment Variables Fail for Agent Security

Current Situation Analysis

Modern AI agents and automation frameworks increasingly operate with broad system access, making egress control a critical security requirement. A pervasive operational pattern involves setting the HTTPS_PROXY environment variable to route agent traffic through a scanning gateway. This approach assumes that the proxy becomes the mandatory choke point for all outbound communication.

This assumption is fundamentally flawed. Environment variables are application-layer hints, not kernel-enforced rules. The Linux kernel does not inspect process environments when routing packets; it routes based on network namespaces, IP tables, and socket ownership. Consequently, any process that can manipulate its execution context or choose its transport layer can bypass HTTPS_PROXY without triggering a single kernel alarm.

The industry overlooks this gap because developers conflate configuration with enforcement. Setting HTTPS_PROXY works for cooperative HTTP libraries, but it provides zero protection against:

  1. Environment manipulation: Subprocesses spawned without the proxy variable.
  2. Transport diversity: Raw TCP, UDP, QUIC, and ICMP traffic that ignores HTTP proxy semantics.
  3. Internal routing loops: Services listed in NO_PROXY that possess their own outbound capabilities, effectively tunneling traffic around the proxy boundary.

In default Linux configurations, the kernel sees these bypasses but lacks the rules to block them. The result is a false sense of security where audit logs show clean proxy traffic while the agent silently exfiltrates data or accesses unauthorized resources via alternative paths.

WOW Moment: Key Findings

The distinction between application-layer hints and kernel-level enforcement is not incremental; it is categorical. The following comparison demonstrates why environment variables cannot serve as a security control.

Control MechanismBypass SurfaceTransport ScopeEnforcement LevelIdentity Binding
HTTPS_PROXY Env VarHigh (Env clear, lib choice, NO_PROXY abuse)HTTP/HTTPS onlyApplication HintNone (Process can drop vars)
Kernel UID/Pod RuleLow (Requires privilege escalation)All Protocols (TCP/UDP/ICMP)Hard EnforcementStrong (Socket ownership)
NetworkPolicy (K8s)Low (Requires pod escape)Per-Protocol/PortHard EnforcementStrong (Pod identity)

Why this matters: Moving from environment variables to identity-based kernel rules closes all bypass shapes simultaneously. The kernel does not care whether the process intends to use a proxy, which library it loads, or what hostname it targets. It only checks the source identity against the rule set. This shift transforms egress control from a "polite request" to a deterministic security boundary.

Core Solution

The robust solution requires identity-centric egress control. You must isolate the agent process under a dedicated identity and enforce a deny-all egress policy that permits traffic only to the proxy endpoint. This approach is transport-agnostic and environment-independent.

Step 1: Isolate the Agent Identity

The agent must run under a unique User ID (UID) on bare metal or within a dedicated Pod in Kubernetes. Shared identities allow other processes to inherit the agent's network permissions or allow the agent to inherit broader permissions.

Bare Metal: Create a dedicated user.

sudo useradd -r -s /usr/sbin/nologin agent-runner

Kubernetes: Ensure the agent runs in a namespace with strict RBAC and a dedicated service account.

Step 2: Apply Kernel-Level Egress Rules

Once isolated, apply rules that drop all outbound traffic from the agent's identity except traffic destined for the proxy.

nftables Implementation (Bare Metal): The following rule set creates a table that filters output based on socket UID. It allows loopback and DNS to a local resolver, permits traffic to the proxy, and drops everything else.

table inet agent_egress {
    chain output {
        type filter hook output priority 0; policy drop;

        # Allow loopback
        oifname "lo" accept

        # Allow DNS to local resolver (replace 127.0.0.53)
        ip daddr 127.0.0.53 udp dport 53 accept
        ip daddr 127.0.0.53 tcp dport 53 accept

        # Allow traffic to proxy (replace 10.0.0.100:8080)
        ip daddr 10.0.0.100 tcp dport 8080 accept

        # Drop all other traffic from agent UID
        meta skuid agent-runner drop
    }
}

Kubernetes NetworkPolicy: NetworkPolicy enforces egress at the pod level. This policy allows only TCP traffic to the proxy service.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: agent-egress-restrict
  namespace: agent-ns
spec:
  podSelector:
    matchLabels:
      app: ai-agent
  policyTypes:
  - Egress
  egress:
  # Allow DNS
  - to:
    - namespaceSelector: {}
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53
  # Allow Proxy
  - to:
    - podSelector:
        matchLabels:
 
     app: scanning-proxy
ports:
- protocol: TCP
  port: 8080

#### Step 3: Sanitize `NO_PROXY`

Even with kernel rules, `NO_PROXY` can create logical bypasses if internal services have outbound access. Restrict `NO_PROXY` to loopback addresses only. If internal services must be reached, route them through the proxy as well.

```bash
export NO_PROXY="127.0.0.1,localhost"

Code Example: Demonstrating the Bypass vs. Enforcement

The following TypeScript example illustrates how a subprocess can bypass environment variables, and why kernel rules are the only effective mitigation.

Bypass Demonstration (Application Layer Failure):

import { spawn } from 'child_process';

// The parent process has HTTPS_PROXY set.
// The child is spawned with an empty environment, dropping the hint.
const child = spawn('curl', ['https://external-api.io/data'], {
  env: {}, // Clears all environment variables
  stdio: 'inherit'
});

child.on('exit', (code) => {
  console.log(`Process exited with code ${code}`);
});

In this scenario, curl connects directly to external-api.io. The proxy is bypassed. If only HTTPS_PROXY is relied upon, this traffic is unscanned.

Enforcement Reality (Kernel Layer Success): When the same code runs under the nftables or NetworkPolicy rules defined above, the kernel intercepts the connect() syscall. The kernel identifies the socket owner as agent-runner (or the agent pod). It checks the egress rules, sees that external-api.io is not the proxy, and drops the packet. The curl command fails with a connection timeout or reset. The bypass is neutralized regardless of environment variables or library choices.

Pitfall Guide

1. The "Polite" Proxy Fallacy

  • Explanation: Assuming HTTPS_PROXY guarantees routing. Environment variables are optional; libraries can ignore them, and processes can drop them.
  • Fix: Never rely on environment variables for security boundaries. Implement kernel-level egress controls.

2. Wildcard NO_PROXY Expansion

  • Explanation: Using patterns like *.cluster.local or *.internal in NO_PROXY. If any service matching the wildcard has outbound internet access, the agent can tunnel traffic through it.
  • Fix: Minimize NO_PROXY to 127.0.0.1 and localhost. Route internal traffic through the proxy to maintain scanning visibility.

3. UDP and DNS Blind Spots

  • Explanation: HTTPS_PROXY does not cover UDP. Agents can exfiltrate data via DNS queries or use QUIC/HTTP/3 over UDP to bypass TCP-based proxy rules.
  • Fix: Ensure firewall rules explicitly drop UDP traffic unless required for DNS resolution to a trusted resolver. Block QUIC if not needed.

4. Shared Identity Risks

  • Explanation: Running the agent as a shared UID (e.g., root or a generic app user). This prevents granular firewall rules and allows other processes to inherit agent permissions.
  • Fix: Use a dedicated UID for the agent. In Kubernetes, use a dedicated pod with a specific label selector for NetworkPolicy.

5. Internal Gateway Tunneling

  • Explanation: Internal services (e.g., LLM gateways, MCP servers, logging aggregators) often have outbound access. Agents can call these services directly (via NO_PROXY) and use them as proxies to reach the internet.
  • Fix: Treat internal services as untrusted egress points. Audit all services reachable by the agent for outbound capabilities. Restrict internal service egress or route agent traffic through the main proxy.

6. QUIC/HTTP/3 Fallback

  • Explanation: Modern HTTP libraries and browsers may fall back to QUIC for performance. QUIC runs over UDP and typically ignores proxy variables.
  • Fix: Disable QUIC in agent configurations where possible. Enforce HTTP/2 over TCP. Ensure UDP is blocked by kernel rules.

7. Incomplete Environment Sanitization

  • Explanation: Assuming env -i is the only way to clear variables. Processes can use execve or library calls to drop specific variables while retaining others.
  • Fix: Kernel rules catch all execution paths. Do not attempt to patch application-layer environment handling; enforce at the kernel.

Production Bundle

Action Checklist

  • Assign Dedicated Identity: Create a unique UID for the agent or deploy it in a dedicated pod with a distinct label.
  • Draft Egress Rules: Write nftables rules or NetworkPolicy YAML to deny all egress except loopback, DNS, and the proxy endpoint.
  • Audit NO_PROXY: Review the NO_PROXY list. Remove wildcards and internal services. Restrict to loopback addresses.
  • Verify Internal Services: Identify all internal services the agent can reach. Ensure they do not have unrestricted outbound internet access.
  • Test Bypasses: Run audit commands (e.g., env -i curl, nc -z) as the agent identity to confirm traffic is blocked.
  • Monitor Drops: Configure logging for dropped packets to detect bypass attempts and misconfigurations.
  • Disable QUIC: Ensure agent libraries are configured to prefer HTTP/2 and do not fall back to QUIC.

Decision Matrix

ScenarioRecommended ApproachWhyCost Impact
Kubernetes ClusterNetworkPolicy + Pod IsolationNative integration, scalable, declarative.Low (Native feature).
Bare Metal / VMnftables + Dedicated UIDDirect kernel control, covers all transports.Medium (Requires sysadmin setup).
High-Security Enclavenftables + Seccomp + Read-Only FSDefense in depth; prevents process manipulation and file writes.High (Complexity and latency).
Legacy App ConstraintsProxy + App-Layer HardeningKernel rules may break legacy apps; use as interim measure.Medium (Risk remains).

Configuration Template

nftables Script for Agent Egress Control:

#!/bin/bash
# agent-egress.nft

# Flush existing rules for this table
flush ruleset

table inet agent_egress {
    chain output {
        type filter hook output priority 0; policy drop;

        # Allow loopback
        oifname "lo" accept

        # Allow DNS to local resolver
        ip daddr 127.0.0.53 udp dport 53 accept
        ip daddr 127.0.0.53 tcp dport 53 accept

        # Allow traffic to proxy
        ip daddr 10.0.0.100 tcp dport 8080 accept

        # Drop all other traffic from agent UID
        meta skuid agent-runner drop
    }
}

Kubernetes NetworkPolicy Template:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: agent-strict-egress
  namespace: production
spec:
  podSelector:
    matchLabels:
      role: ai-agent
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector: {}
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
  - to:
    - podSelector:
        matchLabels:
          app: egress-proxy
    ports:
    - protocol: TCP
      port: 8080

Quick Start Guide

  1. Create Agent User: Run sudo useradd -r -s /usr/sbin/nologin agent-runner on the host.
  2. Apply Firewall: Save the nftables script and load it with sudo nft -f agent-egress.nft.
  3. Run Agent: Execute the agent process as the dedicated user: sudo -u agent-runner ./run-agent.sh.
  4. Verify: Attempt a direct connection: sudo -u agent-runner curl https://example.com. The command should hang or fail.
  5. Confirm Proxy: Ensure the agent is configured to use the proxy. Test a proxied request to verify legitimate traffic flows.

By shifting from environment variables to kernel-enforced identity rules, you eliminate the bypass surface inherent in application-layer hints. This approach provides deterministic egress control that remains effective regardless of process behavior, transport choices, or configuration manipulations.