Outcall
SpecificationsS006 · HTTP Proxy

S006 · HTTP Proxy

Specification module 006-http-proxy

S006: HTTP Proxy

FieldValue
SpecS006
FeatureHTTP Proxy
Date2026-04-21
StatusImplemented
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

  • httparse plus structured URL parsing for bounded HTTP/1.1 request handling
  • A bounded TLS-record and ClientHello parser for SNI peeking (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 taskImplemented
S006-FR-002FunctionalP1Listen address configurationImplemented
S006-FR-003FunctionalP1HTTP direct forwardingImplemented
S006-FR-004FunctionalP1HTTPS CONNECT tunnelingImplemented
S006-FR-005FunctionalP1SNI extraction from ClientHelloImplemented
S006-FR-006FunctionalP1Rule engine integrationImplemented
S006-FR-007FunctionalP1CEL context populationImplemented
S006-FR-008FunctionalP1BLOCK verdict responseImplemented
S006-FR-009FunctionalP1ALLOW verdict forwardingImplemented
S006-FR-010FunctionalP2Connection timeoutImplemented
S006-FR-011FunctionalP2Idle timeoutImplemented
S006-FR-012FunctionalP2Max concurrent connectionsImplemented
S006-FR-013FunctionalP2Request loggingImplemented
S006-FR-014FunctionalP3Managed-peer authenticationImplemented
S006-FR-015FunctionalP1Graceful shutdownImplemented
S006-FR-016FunctionalP1Container env var injectionImplemented
S006-FR-017FunctionalP1No TLS decryptionImplemented
S006-FR-018FunctionalP2SNI-absent HTTPS handlingImplemented
S006-FR-019FunctionalP1Upstream DNS resolutionImplemented
S006-FR-020FunctionalP2Hop-by-hop header strippingImplemented
S006-FR-021FunctionalP1Error propagationImplemented
S006-FR-022FunctionalP2Health check endpointImplemented
S006-FR-023FunctionalP1Fail closed on rule engine errorsImplemented
S006-FR-024FunctionalP1Unambiguous bounded request framingImplemented
S006-FR-025FunctionalP1Managed peer identity requiredImplemented
S006-FR-026FunctionalP1Restricted upstream addresses blocked by defaultImplemented
S006-AS-001AcceptanceP1HTTP GET allowedImplemented
S006-AS-002AcceptanceP1HTTP GET blockedImplemented
S006-AS-003AcceptanceP1HTTPS CONNECT allowedImplemented
S006-AS-004AcceptanceP1HTTPS CONNECT blockedImplemented
S006-AS-005AcceptanceP1SNI hostname extractionImplemented
S006-AS-006AcceptanceP1Rule engine verdict BLOCKImplemented
S006-AS-007AcceptanceP2Connection timeoutDraft
S006-AS-008AcceptanceP2Max connections reachedDraft
S006-AS-009AcceptanceP1Proxy startup and shutdownImplemented
S006-AS-010AcceptanceP2No SNI in ClientHelloImplemented
S006-AS-011AcceptanceP1Multiple agents concurrentDraft
S006-AS-012AcceptanceP2Upstream unreachableImplemented
S006-AS-013AcceptanceP2Blocked request loggingImplemented
S006-IF-001InterfaceP1Proxy listen addressImplemented
S006-IF-002InterfaceP1HTTP CONNECT protocolImplemented
S006-IF-003InterfaceP1HTTP direct proxy protocolImplemented
S006-IF-004InterfaceP1403 BLOCK response formatImplemented
S006-IF-005InterfaceP1Container environment variablesImplemented
S006-IF-006InterfaceP2Health check endpointImplemented
S006-IF-007InterfaceP1CEL context variablesImplemented
S006-EC-001Edge CaseP1SNI absent from ClientHelloImplemented
S006-EC-002Edge CaseP1Upstream connection refusedImplemented
S006-EC-003Edge CaseP1Upstream DNS failureImplemented
S006-EC-004Edge CaseP2Client disconnects mid-tunnelImplemented
S006-EC-005Edge CaseP2Upstream disconnects mid-tunnelImplemented
S006-EC-006Edge CaseP2Extremely large headersImplemented
S006-EC-007Edge CaseP2Non-CONNECT non-HTTP methodImplemented
S006-EC-008Edge CaseP1Rule engine unavailableImplemented
S006-EC-009Edge CaseP2Port other than 443 in CONNECTImplemented
S006-EC-010Edge CaseP2Proxy address not reachable from containerImplemented
S006-EC-011Edge CaseP3HTTP/2 CONNECTImplemented
S006-EC-012Edge CaseP2Rapid reconnect floodImplemented
S006-EC-013Edge CaseP1Daemon shutdown with active tunnelsImplemented
S006-EC-014Edge CaseP2WebSocket upgrade handlingImplemented
S006-EC-015Edge CaseP1Unmanaged source IPImplemented
S006-EC-016Edge CaseP1Restricted-only upstream targetImplemented
S006-EC-017Edge CaseP1Ambiguous framing or pipeliningImplemented
S006-SC-001SuccessP1HTTP request forwarded when allowedImplemented
S006-SC-002SuccessP1HTTPS tunnel established when allowedImplemented
S006-SC-003SuccessP1Blocked requests return 403Implemented
S006-SC-004SuccessP1SNI hostname matches rule engine evaluationImplemented
S006-SC-005SuccessP1Proxy starts and stops with outcalldImplemented
S006-SC-006SuccessP1No TLS decryption occursImplemented
S006-SC-007SuccessP2Blocked requests are loggedImplemented
S006-SC-008SuccessP1Multiple concurrent agents servedDraft
S006-SC-009SuccessP2Timeouts enforce resource limitsImplemented
S006-SC-010SuccessP1Container env vars point to proxyImplemented

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 -- HTTP Upgrade requests are rejected
  • 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