Outcall
SpecificationsS006 · HTTP Proxy

S006 · HTTP Proxy

Specification module 006-http-proxy

S006: HTTP Proxy

FieldValue
SpecS006
FeatureHTTP Proxy
Date2026-04-21
StatusDraft
Author@marktopper

Overview

Provide L7 (application layer) inspection of outbound HTTP and HTTPS traffic from agent containers. The proxy runs as a Tokio task inside outcalld -- there is no separate process. Agent containers reach the proxy via standard HTTP_PROXY / HTTPS_PROXY environment variables.

For plain HTTP, the proxy sees the full request (method, path, headers, hostname) and evaluates it against the rule engine before forwarding. For HTTPS, the proxy receives an HTTP CONNECT request, peeks at the TLS ClientHello to extract the SNI hostname, evaluates rules, and either tunnels the raw bytes or returns a 403.

How it works

  1. outcalld starts the proxy listener as part of its normal startup.
  2. Agent containers are launched with HTTP_PROXY and HTTPS_PROXY pointing at the proxy address (e.g. http://10.200.0.1:8080).
  3. The proxy receives requests:
    • HTTP: direct request with full URL -- proxy inspects and forwards.
    • HTTPS: CONNECT host:443 -- proxy peeks at TLS SNI, inspects, then tunnels if allowed.
  4. Each request is evaluated against the rule engine (S003). The CEL context is populated with network.hostname, http.method, http.path, and http.headers.*.
  5. If the verdict is ALLOW, traffic is forwarded/tunneled.
  6. If the verdict is BLOCK, the proxy returns HTTP 403 with a reason body.

Tech stack

  • hyper for HTTP handling (request parsing, response generation)
  • rustls for TLS ClientHello / SNI parsing (no decryption)

User Scenarios

S006-US-001 [P1] As a host operator, I want all outbound HTTP/HTTPS traffic from agent containers to pass through an inspecting proxy so that rule-based access control is enforced at the application layer.

S006-US-002 [P1] As a host operator, I want the proxy to evaluate each request against the rule engine so that I can allow or block traffic by hostname, path, method, or header values.

S006-US-003 [P1] As a host operator, I want HTTPS connections to be inspected by SNI without decrypting the traffic so that hostname-based rules work while preserving end-to-end encryption.

S006-US-004 [P2] As a host operator, I want the proxy to log blocked requests with the reason so that I can audit policy violations.

S006-US-005 [P2] As a host operator, I want connection limits and timeouts so that a misbehaving agent cannot exhaust proxy resources.

Requirements Summary

IDTypePriorityTitleStatus
S006-FR-001FunctionalP1In-process Tokio taskDraft
S006-FR-002FunctionalP1Listen address configurationDraft
S006-FR-003FunctionalP1HTTP direct forwardingDraft
S006-FR-004FunctionalP1HTTPS CONNECT tunnelingDraft
S006-FR-005FunctionalP1SNI extraction from ClientHelloDraft
S006-FR-006FunctionalP1Rule engine integrationDraft
S006-FR-007FunctionalP1CEL context populationDraft
S006-FR-008FunctionalP1BLOCK verdict responseDraft
S006-FR-009FunctionalP1ALLOW verdict forwardingDraft
S006-FR-010FunctionalP2Connection timeoutDraft
S006-FR-011FunctionalP2Idle timeoutDraft
S006-FR-012FunctionalP2Max concurrent connectionsDraft
S006-FR-013FunctionalP2Request loggingDraft
S006-FR-014FunctionalP3Proxy authenticationDraft
S006-FR-015FunctionalP1Graceful shutdownDraft
S006-FR-016FunctionalP1Container env var injectionDraft
S006-FR-017FunctionalP1No TLS decryptionDraft
S006-FR-018FunctionalP2SNI-absent HTTPS handlingDraft
S006-FR-019FunctionalP1Upstream DNS resolutionDraft
S006-FR-020FunctionalP2Hop-by-hop header strippingDraft
S006-FR-021FunctionalP1Error propagationDraft
S006-FR-022FunctionalP2Health check endpointDraft
S006-AS-001AcceptanceP1HTTP GET allowedDraft
S006-AS-002AcceptanceP1HTTP GET blockedDraft
S006-AS-003AcceptanceP1HTTPS CONNECT allowedDraft
S006-AS-004AcceptanceP1HTTPS CONNECT blockedDraft
S006-AS-005AcceptanceP1SNI hostname extractionDraft
S006-AS-006AcceptanceP1Rule engine verdict BLOCKDraft
S006-AS-007AcceptanceP2Connection timeoutDraft
S006-AS-008AcceptanceP2Max connections reachedDraft
S006-AS-009AcceptanceP1Proxy startup and shutdownDraft
S006-AS-010AcceptanceP2No SNI in ClientHelloDraft
S006-AS-011AcceptanceP1Multiple agents concurrentDraft
S006-AS-012AcceptanceP2Upstream unreachableDraft
S006-AS-013AcceptanceP2Blocked request loggingDraft
S006-IF-001InterfaceP1Proxy listen addressDraft
S006-IF-002InterfaceP1HTTP CONNECT protocolDraft
S006-IF-003InterfaceP1HTTP direct proxy protocolDraft
S006-IF-004InterfaceP1403 BLOCK response formatDraft
S006-IF-005InterfaceP1Container environment variablesDraft
S006-IF-006InterfaceP2Health check endpointDraft
S006-IF-007InterfaceP1CEL context variablesDraft
S006-EC-001Edge CaseP1SNI absent from ClientHelloDraft
S006-EC-002Edge CaseP1Upstream connection refusedDraft
S006-EC-003Edge CaseP1Upstream DNS failureDraft
S006-EC-004Edge CaseP2Client disconnects mid-tunnelDraft
S006-EC-005Edge CaseP2Upstream disconnects mid-tunnelDraft
S006-EC-006Edge CaseP2Extremely large headersDraft
S006-EC-007Edge CaseP2Non-CONNECT non-HTTP methodDraft
S006-EC-008Edge CaseP1Rule engine unavailableDraft
S006-EC-009Edge CaseP2Port other than 443 in CONNECTDraft
S006-EC-010Edge CaseP2Proxy address not reachable from containerDraft
S006-EC-011Edge CaseP3HTTP/2 CONNECTDraft
S006-EC-012Edge CaseP2Rapid reconnect floodDraft
S006-EC-013Edge CaseP1Daemon shutdown with active tunnelsDraft
S006-EC-014Edge CaseP2WebSocket upgrade handlingDraft
S006-SC-001SuccessP1HTTP request forwarded when allowedDraft
S006-SC-002SuccessP1HTTPS tunnel established when allowedDraft
S006-SC-003SuccessP1Blocked requests return 403Draft
S006-SC-004SuccessP1SNI hostname matches rule engine evaluationDraft
S006-SC-005SuccessP1Proxy starts and stops with outcalldDraft
S006-SC-006SuccessP1No TLS decryption occursDraft
S006-SC-007SuccessP2Blocked requests are loggedDraft
S006-SC-008SuccessP1Multiple concurrent agents servedDraft
S006-SC-009SuccessP2Timeouts enforce resource limitsDraft
S006-SC-010SuccessP1Container env vars point to proxyDraft

Out of Scope

  • TLS decryption / MITM -- the proxy inspects SNI only, never decrypts
  • Request body inspection -- L7 inspection covers method, path, headers, hostname; not body content
  • WebSocket upgrade handling -- initial HTTP upgrade request is evaluated, but the proxy does not inspect WebSocket frames
  • HTTP/2 multiplexing -- the proxy operates at HTTP/1.1 level between client and proxy
  • Caching -- the proxy is not a caching proxy
  • Content modification -- the proxy does not rewrite request or response bodies
  • Non-HTTP protocols -- only HTTP and HTTPS (via CONNECT) are handled
  • Per-container proxy bypass -- all containers on the network use the same proxy; bypass is not supported

Cross-Spec Dependencies

  • Depends on: S003 (rule evaluation for each request)
  • Depends on: S002 (proxy binds to the network gateway address)
  • Required by: Container management (future spec -- sets HTTP_PROXY/HTTPS_PROXY env vars)

Shared Types (outcall-api)

Constants

pub const PROXY_DEFAULT_PORT: u16 = 8080;
pub const PROXY_CONNECT_TIMEOUT_SECS: u64 = 10;
pub const PROXY_IDLE_TIMEOUT_SECS: u64 = 300;
pub const PROXY_MAX_CONNECTIONS: usize = 1024;
pub const PROXY_MAX_HEADER_SIZE: usize = 8192;

Types

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyConfig {
    pub listen_addr: SocketAddr,
    pub connect_timeout_secs: u64,
    pub idle_timeout_secs: u64,
    pub max_connections: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyStatus {
    pub listening: bool,
    pub listen_addr: SocketAddr,
    pub active_connections: usize,
    pub total_requests: u64,
    pub total_blocked: u64,
}

/// Populated for each request and passed to the rule engine.
#[derive(Debug, Clone)]
pub struct ProxyRequestContext {
    pub hostname: String,
    pub method: Option<String>,
    pub path: Option<String>,
    pub headers: HashMap<String, String>,
    pub is_connect: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ProxyVerdict {
    Allow,
    Block { reason: String },
}

On this page