Skip to content

Optional VM egress MITM proxy with mock-secret header rewriting#134

Open
sjmiller609 wants to merge 20 commits intomainfrom
feature/egress-mitm-proxy-secret-rewrite
Open

Optional VM egress MITM proxy with mock-secret header rewriting#134
sjmiller609 wants to merge 20 commits intomainfrom
feature/egress-mitm-proxy-secret-rewrite

Conversation

@sjmiller609
Copy link
Collaborator

@sjmiller609 sjmiller609 commented Mar 8, 2026

Summary

This PR adds an optional, default-off egress MITM proxy mode for Hypeman VMs so workloads can run with mock secrets in-VM while real secrets stay on the host.

When enabled per instance, Hypeman now:

  • Starts/uses a host-side HTTP/HTTPS MITM proxy on the VM bridge gateway.
  • Injects proxy env vars into the guest (HTTP_PROXY / HTTPS_PROXY, lower-case variants).
  • Installs proxy CA material in guest init so TLS MITM can be trusted by system components.
  • Rewrites outbound HTTP header values by replacing configured mock literals with real values from host env vars.
  • Enforces egress path on Linux using per-instance iptables FORWARD rules for TCP ports 80/443 so direct internet egress bypass is rejected.

Why

This enables safer execution of untrusted or lower-trust workloads in VMs without placing real secrets inside guest env/config, while still allowing authenticated outbound API traffic.

API / Config changes

  • CreateInstanceRequest now supports top-level egress_proxy:
    • enabled: bool
    • mock_to_real_env_var: map[string]string (mock literal -> host env var name)
  • OpenAPI schema updated accordingly.
  • Instance domain model now persists optional EgressProxyConfig.

Implementation details

  • New module: lib/egressproxy/
    • Host CA generation/load and on-the-fly cert signing.
    • HTTP proxying and HTTPS CONNECT MITM handling.
    • Header replacement policy resolved per source VM IP.
    • Linux enforcement helpers for egress gating.
    • Behavior-focused README.
  • Lifecycle wiring in instances manager:
    • Register proxy policy/enforcement on create/start/restore when enabled.
    • Remove policy/enforcement on stop/standby/delete and rollback paths.
  • Guest init support:
    • New guest config section for egress proxy settings and CA PEM.
    • CA installation call added in both exec and systemd modes.

Tests

Added integration test:

  • TestEgressProxyRewritesHTTPSHeaders
    • Boots a VM with egress proxy mode enabled.
    • Sends HTTPS request with mock secret in Authorization header.
    • Verifies upstream receives rewritten real secret value.

Validation run

Executed on deft-kernel-dev as root:

  • sudo -n /usr/local/go/bin/go test ./cmd/api/api -run TestDoesNotExist -count=1
  • sudo -n /usr/local/go/bin/go test ./lib/instances -run TestEgressProxyRewritesHTTPSHeaders -count=1 -v
  • sudo -n /usr/local/go/bin/go test ./... -run TestDoesNotExist -count=1

All passed for this change set.

Notes

  • Enforcement currently targets default HTTP/HTTPS ports (80/443) by design.
  • Header replacement applies to HTTP headers only (not bodies).

Note

High Risk
Introduces a security-sensitive MITM proxy and host firewall enforcement (iptables) that affects VM networking and secret handling across create/start/restore/cleanup paths; misconfiguration or lifecycle bugs could leak secrets or break connectivity.

Overview
Adds an optional, default-off per-instance egress_proxy mode that starts a host-side HTTP/HTTPS MITM proxy, injects proxy env vars + a trusted CA into the guest, and rewrites outbound HTTPS header values from mock-<ENV_VAR> back to real host-provided secrets (optionally gated by destination-domain allowlists).

Wires this into the instance lifecycle (create/start/restore register policy + enforcement; stop/standby/delete unregister) and persists config via new instances.EgressProxyConfig plus vmconfig/guest-init CA installation; on Linux, adds iptables-based direct egress blocking with selectable enforcement mode (all vs http_https_only).

Updates OpenAPI (openapi.yaml + generated lib/oapi) and API handler mapping for CreateInstanceRequest.egress_proxy, adds extensive unit/integration tests (including a VM test verifying HTTPS header rewriting), and improves CI/e2e reliability by supporting a test registry mirror (HYPEMAN_TEST_REGISTRY) and retrying async image pulls; cmd/test-prewarm also prewarms additional images.

Written by Cursor Bugbot for commit 8c3775f. This will update automatically on new commits. Configure here.

Add a new host-side egress proxy module that supports HTTP/HTTPS interception and per-instance header secret substitution from mock values to real host environment secrets.

Wire proxy lifecycle into instance create/start/restore/stop/standby/delete flows, inject guest proxy settings via config disk, and install proxy CA material in guest init.

Add Linux egress enforcement rules to require proxy path for outbound 80/443 traffic, document behavior in lib/egressproxy/README.md, and add an integration test validating HTTPS header rewrite end to end.
@github-actions
Copy link

github-actions bot commented Mar 8, 2026

✱ Stainless preview builds

This PR will update the hypeman SDKs with the following commit message.

feat: add optional VM egress MITM proxy with mock-secret header rewriting

Edit this comment to update it. It will appear in the SDK's changelogs.

hypeman-typescript studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ✅test ✅

npm install https://pkg.stainless.com/s/hypeman-typescript/f83d3670e7afa296441b5fc7b443cd5f3ad7513e/dist.tar.gz
hypeman-openapi studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅

hypeman-go studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ✅test ✅

go get github.com/stainless-sdks/hypeman-go@136e3f4be6136d1371975eb74065458c2b1dfcf2

This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-03-10 21:25:53 UTC

…n test

Switch the new egress proxy integration test away from curlimages/curl:8.12.1 so it works with CI strict prewarm registry mirror.

Use docker.io/library/nginx:alpine (already mirrored in CI) while keeping HTTPS header rewrite validation via curl.
@sjmiller609 sjmiller609 marked this pull request as ready for review March 8, 2026 21:54
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated risk triage result: High risk.

Why this is high risk (from code diff evidence):

  • Introduces a new host-side HTTP/HTTPS MITM subsystem (lib/egressproxy/*) including dynamic cert signing, CONNECT interception, and header rewriting logic.
  • Adds Linux egress enforcement via host iptables FORWARD rules (lib/egressproxy/enforce_linux.go), which affects networking behavior and operational safety.
  • Wires proxy/enforcement into core VM lifecycle flows (create, start, restore, stop, standby, delete) across lib/instances/*.
  • Expands external API surface (openapi.yaml, lib/oapi/oapi.go, API request mapping) and guest init trust/bootstrap behavior (lib/system/init/*, lib/vmconfig/config.go).

Decision:

  • Code review is required.
  • No auto-approval (high-risk PRs are not approved by automation).

Reviewer assignment:

  • PR already has 2 reviewers requested, so no additional reviewers were added.

Open in Web View Automation 

@sjmiller609 sjmiller609 enabled auto-merge (squash) March 9, 2026 02:58
@sjmiller609 sjmiller609 changed the title feat: add optional VM egress MITM proxy with mock-secret header rewriting Add optional VM egress MITM proxy with mock-secret header rewriting Mar 9, 2026
@sjmiller609 sjmiller609 changed the title Add optional VM egress MITM proxy with mock-secret header rewriting Optional VM egress MITM proxy with mock-secret header rewriting Mar 9, 2026
…roxy-secret-rewrite

# Conflicts:
#	lib/instances/create.go
#	lib/instances/fork.go
#	lib/instances/types.go
#	lib/oapi/oapi.go
#	openapi.yaml
…roxy-secret-rewrite

# Conflicts:
#	lib/instances/create.go
#	lib/instances/manager.go
#	lib/oapi/oapi.go
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Gateway: alloc.Gateway,
Netmask: alloc.Netmask,
TAPDevice: alloc.TAPDevice,
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restore path constructs NetworkConfig missing DNS field

High Severity

The NetworkConfig manually constructed from the Allocation in the restore path omits the DNS field. When createConfigDisk uses this incomplete config via buildGuestConfig, the resulting GuestDNS will be empty, breaking DNS resolution in the restored guest. The create and start paths get DNS from the network manager's returned NetworkConfig, but this restore path constructs one by hand and leaves DNS zeroed.

Fix in Cursor Fix in Web

if stored.EgressProxy != nil && stored.EgressProxy.Enabled {
proxyRegistered = true
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restore unconditionally regenerates config disk for all networked instances

Medium Severity

The new restore block at step 4b regenerates the config disk for every networked instance, not just proxy-enabled ones. For non-proxy instances, this is unnecessary and introduces a new requirement that the image metadata still be loadable via GetImage during restore. A restore that previously succeeded can now fail at the "get image for restore config disk" step. The image load and createConfigDisk call belong inside a guard checking stored.EgressProxy != nil && stored.EgressProxy.Enabled.

Fix in Cursor Fix in Web

example:
PORT: "3000"
NODE_ENV: production
egress_proxy:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the direction here, but i’m a little worried the public api is getting shaped around the current implementation (egress_proxy + mocked env var rewriting) rather than the longer-term policy model we may want.

after looking at a few similar products, the ones that seem to age best expose this as a network/credential policy problem, not a proxy feature:

  • vercel sandbox firewall has first-class network policy (allow-all / deny-all / custom allow+deny rules) and then layers credential brokering / header transforms on top
  • deno sandbox separates allowNet from secrets, where secrets are host-scoped and substituted on outbound requests without ever entering the vm
  • e2b already has allowOut / denyOut, and even their open credential brokering proposal models secret injection as an egress transform layered onto network policy
  • daytona is simpler, but even there the api is framed as network restriction (networkAllowList / networkBlockAll), not proxy config

the reason i’m flagging it is that this shape feels pretty mechanism-specific:

  • egress_proxy bakes in “host-side mitm proxy” as the conceptual model
  • mock_env_vars assumes env vars are the long-term secret source abstraction
  • mock_env_var_domains couples destination policy to that env-var abstraction
  • enforcement_mode is really egress/firewall policy, but it lives inside the same object as secret substitution

if we ever want to grow this into broader outbound controls (allow/deny rules, port restrictions, runtime policy updates, non-env-backed credentials, different injection strategies, explicit private-network blocking, etc), i think this api may box us in a bit.

maybe the public shape wants to be closer to something like this, even if the backend implementation for v1 is still proxy-based:

network:
  egress:
    default: allow | deny
    allow:
      - hosts: ["api.openai.com", "*.anthropic.com"]
      - cidrs: ["10.0.0.0/8"]
    deny:
      - cidrs: ["169.254.169.254/32"]
    enforcement:
      mode: all | http_https_only

credentials:
  OPENAI_API_KEY:
    source:
      env: OPENAI_API_KEY
    inject:
      - hosts: ["api.openai.com"]
        as:
          header: Authorization
          format: "Bearer ${value}"

or some variation of that. the main thing i’m after is separating:

  1. what outbound destinations are reachable
  2. what credentials can be brokered where
  3. how we currently implement that under the hood

that feels a little more future-proof to me than making egress_proxy itself the core api concept.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants