Skip to content

feat: introduced an egress proxy for L7 credential injection - #39771

Open
wylswz wants to merge 27 commits into
langgenius:mainfrom
wylswz:feat/egress-proxy-l7-cred-injection
Open

feat: introduced an egress proxy for L7 credential injection#39771
wylswz wants to merge 27 commits into
langgenius:mainfrom
wylswz:feat/egress-proxy-l7-cred-injection

Conversation

@wylswz

@wylswz wylswz commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Important

  1. Make sure you have read our contribution guidelines
  2. Ensure there is an associated issue and you have been assigned to it
  3. Use the correct syntax to link this PR: Fixes #<issue number>.

Summary

This is an implementation of #39278.

Open for further discussion.

Goals

  • Provide a foundation of distributing sensitive environment variables to sandbox safely.
  • Implement two credential injection policies as PoC.
  • Protect DIFY_AGENT_STUB_AUTH_JWE.

Non-Goals

  • Modify current agent env editor implementation.

Architecture

  • Introduce an egress proxy for L7 cred injection.
┌─────────────────────────────────────────────────────────────────────┐
│  local_sandbox container                                            │
│                                                                     │
│  ┌──────────────┐    ┌──────────────────────┐    ┌───────────────┐  │
│  │  Agent Job   │    │  Egress MITM Proxy   │    │  Squid (SSRF) │  │
│  │  (tmux)      │───▶│  127.0.0.1:18080     │───▶│  agent_ssrf   │  │
│  │              │    │                      │    │  _proxy:3128  │  │
│  │  env:        │    │  ┌────────────────┐  │    └──────┬────────┘  │
│  │  HTTP_PROXY  │    │  │   Resolver     │  │           │           │
│  │  HTTPS_PROXY │    │  │  system tier   │  │           ▼           │
│  │  TAVILY_API_ │    │  │  session tier  │  │     ┌──────────┐      │
│  │  KEY=__secret│    │  │  (per sandbox) │  │     │ Internet │      │
│  │  :tavily/    │    │  └────────────────┘  │     │  (e.g.   │      │
│  │  api_key__   │    │                      │     │  tavily) │      │
│  └──────────────┘    │  1. Inject headers   │     └──────────┘      │
│                      │  2. Replace          │                       │
│                      │     placeholders     │                       │
│                      │  3. Strip Proxy-Auth │                       │
│                      └──────────────────────┘                       │
│                                                                     │
│  system-credentials.yaml ──▶ loaded at startup into system tier     │
│  (mounted read-only via Docker volume)                              │
└─────────────────────────────────────────────────────────────────────┘

Key components

  • System credential manifest (system-credentials.yaml): Mounted into the container via Docker volume. Parsed at startup (YAML or JSON). Credentials enter the Resolver's system tier — shared across all sandbox sessions, never mutated at runtime.

  • Session credentials: Registered per sandbox session via PUT /v1/prepare API (with sandbox_id). Stored in the Resolver's session tier — isolated per sandbox, no cross-session leakage. Session credentials shadow system credentials on key conflict.

  • Egress MITM Proxy (127.0.0.1:18080): Intercepts all outbound HTTP/HTTPS traffic from agent jobs. For HTTPS, it performs TLS interception using a per-container CA (generated fresh at startup, installed into the system trust store). The proxy:

    1. Extracts sandbox_id from the Proxy-Authorization header (embedded as Basic-Auth userinfo in the proxy URL).
    2. Proactively injects credential headers based on domain-matching policies (e.g. Authorization: Bearer <token> for api.tavily.com).
    3. Replaces placeholders like __secret:tavily/api_key__ in request headers and URL query parameters with resolved credential values.
    4. Strips the Proxy-Authorization header before forwarding.
  • Squid SSRF proxy (agent_ssrf_proxy:3128): Upstream of the egress proxy. Enforces network-level egress restrictions (deny private networks, allow public internet).

  • Per-container CA: Generated at startup by egressproxy.GenerateCA(). Installed into the system trust store via update-ca-certificates (Dockerfile grants the non-root dify user write access to the necessary paths). This means all tools — including apt-get, wget, Java, etc. — trust the MITM proxy's TLS certificates without needing per-tool env vars.

proxy library

The proxy implementation must not perform name resolution when forwarding traffic to upstream, otherwise, the resolved IP is in-consistent when passed through different docker networks. Also, the policy in squid proxy fails if the host of the target is missing. elazarl/goproxy is one option.

Limitations

  • Only http/https are supported.
  • mTLS is not supported.

Cloud edition considerations

  • Upstream proxy

co-authored with Claude Sonnet 5

Screenshots

img_v3_02143_9e9866ff-330e-435f-9e0e-a9f96c614d6g img_v3_02143_a005e289-9901-4764-910d-695b6821276g

Checklist

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.
  • I ran make lint && make type-check (backend) and cd web && pnpm exec vp staged (frontend) to appease the lint gods

@wylswz
wylswz marked this pull request as draft July 29, 2026 15:45
@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Jul 29, 2026
@wylswz wylswz self-assigned this Jul 29, 2026
@wylswz

wylswz commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

TODO:

  1. Clean up codes
  2. Provide an end-to-end POC, which may include a working example accessing a SaaS product with api keys (e.g., Tavily)
  3. The credential config need to be polished to potentially support complex credential injection policies in the future.

@dosubot

dosubot Bot commented Jul 29, 2026

Copy link
Copy Markdown

📄 Knowledge review

✏️ Suggested updates

1 page suggestion needs review.

Page Library Status
Dify Agent Server 模块分析 (Commit 55f95db) dify 🟡 Review

📝 Dify Agent Server 模块分析 (Commit 55f95db)Accept · Edit · Decline


Leave Feedback Ask Dosu about dify

Comment thread api/Dockerfile Outdated
@wylswz wylswz changed the title feat: introduced an egress proxy for L7 cred injection feat: introduced an egress proxy for L7 credential injection Jul 29, 2026
@kuangmi-bit

Copy link
Copy Markdown

@wylswz — really interesting approach with the MITM proxy + squid upstream architecture here. We had taken a different angle on the same credential-injection problem in #39483 (now closed as duplicated), but your approach is architecturally cleaner for the L7 interception path. Nice work.

One thought: sandbox security has two directions — egress (what leaves the sandbox, which this PR handles well) and ingress/filesystem (what the sandbox can reach into on the host).

On that second front, we have a small, focused fix open at #39581 that constrains shellctl CWD resolution to a trusted workspace root. It prevents path traversal where a shell job could be pointed at arbitrary directories outside the workspace. It's 5 files, ~200 lines, 10/10 tests green, and completely independent of the egress proxy work here.

If you or someone on the team has a moment to take a look at #39581, that would be appreciated — no urgency, just flagging it since these two PRs together cover both directions of sandbox safety. Happy to adjust anything based on feedback.

@wylswz
wylswz force-pushed the feat/egress-proxy-l7-cred-injection branch from f6e7c5d to d0b7f17 Compare July 30, 2026 06:38
@github-actions github-actions Bot added the web This relates to changes on the web. label Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Pyrefly Type Coverage

Metric Base PR Delta
Type coverage 56.84% 56.84% 0.00%
Strict coverage 56.36% 56.36% 0.00%
Typed symbols 37,147 37,147 0
Untyped symbols 28,447 28,447 0
Modules 3092 3092 0

@wylswz
wylswz force-pushed the feat/egress-proxy-l7-cred-injection branch from 7cc8501 to da44477 Compare July 30, 2026 07:18
@github-actions github-actions Bot removed the web This relates to changes on the web. label Jul 30, 2026
@wylswz
wylswz force-pushed the feat/egress-proxy-l7-cred-injection branch from da44477 to 36aa191 Compare July 30, 2026 07:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an in-sandbox egress MITM proxy to inject/resolve credentials at the HTTP/HTTPS boundary (via __secret:provider/name__ placeholders and domain-scoped header injection), plus a PUT /v1/prepare phase to register per-session credentials, and updates the Dify agent runtime/client to pass a sandbox_id through to the sandbox so credentials can be scoped per session.

Changes:

  • Add an in-process egress MITM proxy (with optional upstream proxy chaining) and credential resolver with system + session tiers.
  • Add PUT /v1/prepare and sandbox_id plumbing (schemas, SDK, runtime backend, tests) to register and scope session credentials.
  • Update local-sandbox Docker compose wiring to route HTTP(S) traffic via 127.0.0.1:18080 and mount system credential manifests.

Reviewed changes

Copilot reviewed 39 out of 40 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
docker/volumes/local_sandbox/credentials/README.md Documents system credential manifest format and placeholder behavior.
docker/volumes/local_sandbox/credentials/.gitignore Prevents committing real secrets via *.cred.* patterns.
docker/docker-compose.yaml Routes sandbox egress via in-process proxy and mounts credentials dir.
docker/docker-compose-template.yaml Same as docker-compose.yaml for templated deployments.
dify-agent/tests/local/dify_agent/runtime_backend/test_shellctl_backend.py Adds coverage for sandbox_id derivation/sanitization and shellctl lease behavior.
dify-agent/tests/local/dify_agent/runtime_backend/test_local.py Extends local runtime test harness to record sandbox_id.
dify-agent/tests/local/dify_agent/layers/shell/test_layer.py Updates shell layer test doubles to include prepare().
dify-agent/tests/local/dify_agent/adapters/shell/test_shellctl.py Extends shellctl adapter test double to accept sandbox_id + prepare().
dify-agent/src/shellctl/shared/schemas.py Introduces credential + injection schemas, plus PrepareRequest/Response and sandbox_id on run.
dify-agent/src/shellctl/client/sdk.py Adds SDK support for prepare() and passing sandbox_id on run().
dify-agent/src/dify_agent/runtime_backend/shellctl.py Derives a shellctl-safe sandbox_id from lease handles.
dify-agent/src/dify_agent/layers/shell/layer.py Adds a “prepare credentials” step on context creation and calls shellctl prepare().
dify-agent/src/dify_agent/agent_stub/shell_env.py Switches agent-stub auth env var to placeholder and builds structured credential injection policy.
dify-agent/src/dify_agent/adapters/shell/shellctl.py Plumbs sandbox_id to shellctl run() and adds prepare() to commands/protocol.
dify-agent/src/dify_agent/adapters/shell/protocols.py Extends shell command protocol with prepare(credentials).
dify-agent-runtime/tests/squid-test.conf Adds permissive Squid config for upstream-chaining integration test.
dify-agent-runtime/tests/egress_proxy_test.go Adds integration tests validating injection, placeholder replacement, and upstream chaining.
dify-agent-runtime/README.md Documents enabling the egress proxy via env var.
dify-agent-runtime/Makefile Adds docker-based integration harness (echo backend + egress proxy + upstream squid).
dify-agent-runtime/internal/server/types.go Adds credential/injection types, manifest loaders, and PrepareRequest/Response types.
dify-agent-runtime/internal/server/types_test.go Adds tests for manifest parsing, env name derivation, and system/session isolation semantics.
dify-agent-runtime/internal/server/service.go Starts proxy, manages system/session credentials, persists session manifests, injects proxy + placeholder env vars into jobs.
dify-agent-runtime/internal/server/config.go Adds egress proxy config fields and env var wiring.
dify-agent-runtime/internal/server/config_test.go Adds regression tests for egress proxy env var wiring.
dify-agent-runtime/internal/server/api.go Adds PUT /v1/prepare endpoint to register per-session credentials.
dify-agent-runtime/internal/envvar/internal_env.go Centralizes well-known env var names used by the runtime.
dify-agent-runtime/internal/envvar/envvar.go Adds egress proxy-related env var constants.
dify-agent-runtime/internal/egressproxy/resolver.go Implements resolver tiers, placeholder replacement, and domain-matched injection policy.
dify-agent-runtime/internal/egressproxy/resolver_test.go Adds unit tests for resolver replacement, injection, and tier isolation.
dify-agent-runtime/internal/egressproxy/proxy.go Implements MITM proxy via goproxy; extracts sandbox_id from proxy auth; performs injection/replacement.
dify-agent-runtime/internal/egressproxy/proxy_test.go Adds unit tests for HTTP/HTTPS MITM behavior and upstream hostname preservation.
dify-agent-runtime/internal/egressproxy/certstore.go Adds in-memory leaf cert caching for MITM.
dify-agent-runtime/internal/egressproxy/ca.go Adds CA generation and best-effort system trust installation.
dify-agent-runtime/internal/agentcli/httpclient.go Disables keep-alives to avoid proxy connection reuse issues across destinations.
dify-agent-runtime/go.sum Adds/updates dependencies for goproxy + yaml parsing and related indirect deps.
dify-agent-runtime/go.mod Adds goproxy + yaml dependency and changes Go version directive.
dify-agent-runtime/docs/egress-credential-proxy-demo.md Adds a demo guide describing the architecture and behavior.
dify-agent-runtime/docker/Dockerfile Adjusts permissions to allow CA trust store updates under non-root user.
dify-agent-runtime/cmd/runner/main.go Grants Landlock read access to the injected CA cert path.
api/Dockerfile Comments out NLTK data download step (currently unrelated to egress proxy changes).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread dify-agent-runtime/internal/egressproxy/resolver.go Outdated
Comment thread dify-agent/src/dify_agent/agent_stub/shell_env.py Outdated
Comment thread dify-agent-runtime/internal/egressproxy/proxy.go Outdated
Comment thread api/Dockerfile Outdated
Comment thread dify-agent-runtime/internal/server/service.go
Comment thread dify-agent-runtime/internal/server/service.go
@wylswz
wylswz force-pushed the feat/egress-proxy-l7-cred-injection branch from 472f282 to b26f1e4 Compare July 31, 2026 09:12
@wylswz
wylswz marked this pull request as ready for review July 31, 2026 09:13
@wylswz
wylswz force-pushed the feat/egress-proxy-l7-cred-injection branch from f35874c to 878e787 Compare July 31, 2026 10:25
@wylswz
wylswz requested a review from BeautyyuYanli July 31, 2026 10:41
*,
cwd: str | None = None,
env: dict[str, str] | None = None,
sandbox_id: str | None = None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is this sandbox id?

@wylswz wylswz Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This identifies a logical sandbox session, to locate the session scoped credentials and also used as proxy authentication, so the proxy server could identify from which session the request is sent from.

The value is derived from binding_ref

Maybe session_id is a better name?

return env


def build_shell_agent_stub_credentials(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The authorization of agent stub cli is now built upon the new Credential. Since the sandbox image allows to disable this new feature, if the agent stub cli can work correctly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Currently the feature flag is not provided, i.e., the feature is always enabled. We can add one however, which requires changes on both client and server side.

@wylswz
wylswz force-pushed the feat/egress-proxy-l7-cred-injection branch from ca97cfb to 1ab7378 Compare August 1, 2026 07:04
@wylswz
wylswz force-pushed the feat/egress-proxy-l7-cred-injection branch from 7faa8e9 to 0ed63d2 Compare August 1, 2026 08:20
@wylswz
wylswz requested a review from a team August 1, 2026 08:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants