feat: introduced an egress proxy for L7 credential injection - #39771
feat: introduced an egress proxy for L7 credential injection#39771wylswz wants to merge 27 commits into
Conversation
|
TODO:
|
|
@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 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. |
f6e7c5d to
d0b7f17
Compare
Pyrefly Type Coverage
|
7cc8501 to
da44477
Compare
da44477 to
36aa191
Compare
There was a problem hiding this comment.
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/prepareandsandbox_idplumbing (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:18080and 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.
472f282 to
b26f1e4
Compare
f35874c to
878e787
Compare
| *, | ||
| cwd: str | None = None, | ||
| env: dict[str, str] | None = None, | ||
| sandbox_id: str | None = None, |
There was a problem hiding this comment.
What is this sandbox id?
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
ca97cfb to
1ab7378
Compare
7faa8e9 to
0ed63d2
Compare
Important
Fixes #<issue number>.Summary
This is an implementation of #39278.
Open for further discussion.
Goals
DIFY_AGENT_STUB_AUTH_JWE.Non-Goals
Architecture
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/prepareAPI (withsandbox_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:sandbox_idfrom theProxy-Authorizationheader (embedded as Basic-Auth userinfo in the proxy URL).Authorization: Bearer <token>forapi.tavily.com).__secret:tavily/api_key__in request headers and URL query parameters with resolved credential values.Proxy-Authorizationheader 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 viaupdate-ca-certificates(Dockerfile grants the non-rootdifyuser write access to the necessary paths). This means all tools — includingapt-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/goproxyis one option.Limitations
Cloud edition considerations
co-authored with Claude Sonnet 5
Screenshots
Checklist
make lint && make type-check(backend) andcd web && pnpm exec vp staged(frontend) to appease the lint gods