Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
14effb2
fix(dcode): route fetch_url through managed proxy
ericksoa Jul 8, 2026
9a1745b
fix(dcode): verify managed fetch proxy source
ericksoa Jul 8, 2026
799b184
fix(dcode): preserve managed proxy TLS trust
ericksoa Jul 8, 2026
8a3fcb0
fix(dcode): clarify fetch URL trust boundaries
ericksoa Jul 8, 2026
ab9b708
fix(sandbox): classify interleaved policy denials
ericksoa Jul 8, 2026
4a51559
Merge branch 'main' into ericksoa/fix-dcode-fetch-proxy
ericksoa Jul 8, 2026
f41b435
fix(dcode): close managed fetch proxy review gaps
cjagwani Jul 8, 2026
c26f259
test(dcode): cover managed fetch redirect boundaries
ericksoa Jul 8, 2026
9576812
Merge remote-tracking branch 'origin/ericksoa/fix-dcode-fetch-proxy' …
ericksoa Jul 8, 2026
1254b75
Merge remote-tracking branch 'origin/main' into ericksoa/fix-dcode-fe…
ericksoa Jul 8, 2026
663fee5
Merge branch 'main' into ericksoa/fix-dcode-fetch-proxy
cjagwani Jul 8, 2026
8a875e8
Merge remote-tracking branch 'origin/ericksoa/fix-dcode-fetch-proxy' …
ericksoa Jul 8, 2026
882db5d
Merge remote-tracking branch 'origin/main' into ericksoa/fix-dcode-fe…
ericksoa Jul 8, 2026
b2d0dd1
Merge branch 'main' into ericksoa/fix-dcode-fetch-proxy
cv Jul 8, 2026
d5d8717
Merge remote-tracking branch 'origin/main' into ericksoa/fix-dcode-fe…
ericksoa Jul 8, 2026
3601ff1
Merge remote-tracking branch 'origin/ericksoa/fix-dcode-fetch-proxy' …
ericksoa Jul 8, 2026
06fc276
test(e2e): restore Tavily policy after opt-in
cjagwani Jul 8, 2026
5cf86d2
Merge remote-tracking branch 'origin/ericksoa/fix-dcode-fetch-proxy' …
ericksoa Jul 8, 2026
62d34d1
test(e2e): restore observability after policy cleanup
cjagwani Jul 8, 2026
dddc000
fix(dcode): harden managed fetch validation
ericksoa Jul 8, 2026
d0b1871
test(e2e): exercise policy cleanup boundary
cjagwani Jul 8, 2026
5b35c3b
test(e2e): document deferred shell expansion
cjagwani Jul 8, 2026
65933be
fix(e2e): clean up Tavily after early exit
ericksoa Jul 8, 2026
8a9f95f
Merge origin/main into ericksoa/fix-dcode-fetch-proxy
ericksoa Jul 8, 2026
7c0c911
test(e2e): snapshot observability after policy add
cjagwani Jul 8, 2026
e62db5e
Merge remote-tracking branch 'origin/ericksoa/fix-dcode-fetch-proxy' …
ericksoa Jul 8, 2026
4bbe769
test(dcode): cover concurrent CA bundle mutation
ericksoa Jul 8, 2026
4b07586
fix(dcode): persist observability marker
ericksoa Jul 8, 2026
714c981
docs(dcode): record lifecycle repair boundaries
ericksoa Jul 8, 2026
b041b37
test(dcode): isolate durable marker fixture
ericksoa Jul 8, 2026
629dc8f
Merge origin/main into ericksoa/fix-dcode-fetch-proxy
cjagwani Jul 8, 2026
4680ea2
fix(dcode): preserve observability across policy restarts
ericksoa Jul 8, 2026
a790f0c
Merge remote updates into ericksoa/fix-dcode-fetch-proxy
ericksoa Jul 8, 2026
0aef02c
Merge origin/main into ericksoa/fix-dcode-fetch-proxy
ericksoa Jul 8, 2026
9f3f817
test(dcode): close fetch and observability review gaps
ericksoa Jul 8, 2026
5f226ab
Merge origin/main into ericksoa/fix-dcode-fetch-proxy
ericksoa Jul 9, 2026
13489a7
ci: align CodeQL action versions
ericksoa Jul 9, 2026
0222e28
Merge branch 'main' into ericksoa/fix-dcode-fetch-proxy
cjagwani Jul 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions agents/langchain-deepagents-code/dcode-launcher.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ fi

_PROXY_URL="http://${PROXY_HOST}:${PROXY_PORT}"
_NO_PROXY_VAL="localhost,127.0.0.1,::1,${PROXY_HOST}"
# fetch_url cannot use its direct DNS-pinning transport inside OpenShell's
# proxy-only network namespace. Opt only this managed launch into the explicit
# trusted-proxy transport, using the same root-owned values as inference and
# shell egress. The managed package patch still ignores ambient proxy values.
export DEEPAGENTS_CODE_FETCH_URL_TRUSTED_PROXY_URL="$_PROXY_URL"
export HTTP_PROXY="$_PROXY_URL"
export HTTPS_PROXY="$_PROXY_URL"
export NO_PROXY="$_NO_PROXY_VAL"
Expand Down
36 changes: 36 additions & 0 deletions agents/langchain-deepagents-code/dependency-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,42 @@ Update it whenever `requirements.lock` changes.

The Dockerfile installs this lockfile with `pip3 install --require-hashes`, so this review covers the exact package versions selected for the managed image install.

## Managed `fetch_url` Proxy Adapter

Deep Agents Code `0.1.34` deliberately disables ambient proxies and resolves
destination DNS locally before pinning the address used by `fetch_url`. That is
the wrong transport inside a NemoClaw-managed sandbox: ordinary egress and
destination resolution must pass through the policy proxy, so the direct path
fails even when the same approved URL works through the managed route.

NemoClaw owns the managed image, launchers, and policy boundary, but not the
hash-locked third-party `fetch_url` implementation. The exact-version build
patch therefore delegates only managed launches to a proxy URL independently
derived from the image's root-owned host and port files. The runtime rejects a
missing, unsafe, or mismatched file/environment contract, disables Requests'
ambient proxy, `NO_PROXY`, netrc, and CA discovery, and supplies the verified
proxy explicitly on every redirect hop. It separately validates the fixed,
root-owned CA-bundle mount injected into the sandbox and passes it as explicit
TLS transport trust; that bundle cannot select a proxy or authorize a
destination. Imports outside the managed launcher retain the upstream direct
DNS-pinning behavior.

Redirect validation rejects authority userinfo (`user:password@host`). It does
not treat `@` or `:` in a path segment as credentials: RFC 3986 defines those
characters as ordinary path data, and coding tasks can legitimately encounter
them in repository refs or filenames. Focused redirect coverage pins that
distinction, while validation errors avoid echoing candidate URLs and the
policy proxy remains authoritative for every destination.

Focused tests patch the released wheel, exercise managed and unmanaged paths,
reject forged proxy environments and malformed redirects, and prove that
credential-bearing URLs are not reflected. The live Deep Agents Code egress
check requires a nonempty 2xx response from an approved raw GitHub URL and
denial for an unapproved host, cloud metadata, and loopback. Remove this adapter
rather than refreshing it when a pinned Deep Agents Code release exposes a
supported policy-proxy transport with equivalent redirect and fail-closed
behavior.

## Released Nemotron 3 Ultra Profile

Deep Agents Code `0.1.34` pins `deepagents==0.7.0a6`, whose official wheel
Expand Down
278 changes: 277 additions & 1 deletion agents/langchain-deepagents-code/managed-dcode-runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
import re
import stat
import sys
from collections.abc import Callable
from pathlib import Path
from urllib.parse import urlparse, urlsplit
from typing import Any
from urllib.parse import urljoin, urlparse, urlsplit

_MANAGED_STATE_DIR = Path("/sandbox/.deepagents/.state")
_AUTH_FILE = _MANAGED_STATE_DIR / "auth.json"
Expand All @@ -24,6 +26,12 @@
_INFERENCE_BASE_URL_FILE = Path(
"/usr/local/share/nemoclaw/dcode-inference-base-url"
)
_MANAGED_PROXY_HOST_FILE = Path(
"/usr/local/share/nemoclaw/dcode-proxy-host"
)
_MANAGED_PROXY_PORT_FILE = Path(
"/usr/local/share/nemoclaw/dcode-proxy-port"
)
_AUTO_APPROVAL_FILE = Path(
"/usr/local/share/nemoclaw/dcode-auto-approval"
)
Expand Down Expand Up @@ -59,11 +67,22 @@
)
_OPENSHELL_ENV_PLACEHOLDER_PREFIX = "openshell:resolve:env:"
_UPSTREAM_PROVIDER_ENV = "NEMOCLAW_UPSTREAM_PROVIDER"
_FETCH_URL_TRUSTED_PROXY_ENV = (
"DEEPAGENTS_CODE_FETCH_URL_TRUSTED_PROXY_URL"
)
_MANAGED_FETCH_CA_BUNDLE_FILE = Path(
"/etc/openshell-tls/ca-bundle.pem"
)
_MANAGED_ADAPTER_PROVIDER = "openai"
_NVIDIA_DISPLAY_PROVIDER_ALIASES = frozenset(
{"nvidia", "nvidia-prod", "nvidia-nim", "nvidia-router"}
)
_DISPLAY_PROVIDER_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}")
# Match the launchers' root-owned, image-baked proxy validator. Its deliberate
# RFC 1123 deviation permits underscores only for controlled internal/container
# aliases such as `proxy_name`; the cross-boundary cases in
# test/langchain-deepagents-code-proxy-launcher.test.ts prevent validator drift.
_MANAGED_PROXY_HOST = re.compile(r"[A-Za-z0-9._-]+")
_MCP_SERVER_NAME = re.compile(r"[A-Za-z][A-Za-z0-9_-]{0,63}")
_MCP_ENV_NAME = re.compile(r"[A-Za-z_][A-Za-z0-9_]{0,127}")
_MCP_DNS_NAME = re.compile(
Expand Down Expand Up @@ -903,6 +922,262 @@ def managed_inference_base_url() -> str:
return value


def managed_fetch_proxy_url() -> str | None:
"""Return the explicit OpenShell proxy delegated to managed ``fetch_url``.

The variable is absent when this helper is imported outside the managed
launcher, in which case the upstream direct DNS-pinning transport remains
authoritative. When present, every conventional HTTP(S) proxy variable
must carry the same launcher-derived value. This prevents a mutable ambient
proxy or ``NO_PROXY`` rule from silently replacing the root-owned route.
"""
value = os.environ.get(_FETCH_URL_TRUSTED_PROXY_ENV)
if value is None:
return None
expected_proxy_url = _managed_fetch_proxy_url_from_files()
if (
not value
or len(value) > 2048
or value != value.strip()
or any(ord(character) < 32 for character in value)
):
raise RuntimeError("managed fetch URL proxy is invalid")
try:
parsed = urlparse(value)
port = parsed.port
except ValueError as exc:
raise RuntimeError("managed fetch URL proxy is invalid") from exc
if (
parsed.scheme != "http"
or not parsed.hostname
or port is None
or port < 1
or parsed.username is not None
or parsed.password is not None
or parsed.path not in {"", "/"}
or parsed.params
or parsed.query
or parsed.fragment
or _MANAGED_PROXY_HOST.fullmatch(parsed.hostname) is None
):
raise RuntimeError("managed fetch URL proxy is invalid")
if value != expected_proxy_url:
raise RuntimeError(
"managed fetch URL proxy does not match root-owned proxy"
)
for name in ("HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"):
if os.environ.get(name) != value:
raise RuntimeError("managed fetch URL proxy does not match runtime proxy")
return value


def _read_managed_proxy_value(path: Path, label: str) -> str:
"""Read one immutable proxy component from the managed image."""
if not path.is_file() or path.is_symlink():
raise RuntimeError(f"managed proxy {label} file is missing or unsafe")
try:
metadata = path.stat()
raw = path.read_text(encoding="utf-8")
except OSError as exc:
raise RuntimeError(f"managed proxy {label} file is unreadable") from exc
if (
metadata.st_uid != _MANAGED_FILE_OWNER_UID
or stat.S_IMODE(metadata.st_mode) != 0o444
):
raise RuntimeError(
f"managed proxy {label} file has unsafe ownership or mode"
)
value = raw.rstrip("\n")
if (
not value
or len(value) > 2048
or raw not in {value, f"{value}\n"}
or value != value.strip()
or any(ord(character) < 32 for character in value)
):
raise RuntimeError(f"managed proxy {label} file has invalid contents")
return value


def _managed_fetch_proxy_url_from_files() -> str:
"""Derive the trusted proxy URL independently from root-owned files."""
host = _read_managed_proxy_value(_MANAGED_PROXY_HOST_FILE, "host")
port = _read_managed_proxy_value(_MANAGED_PROXY_PORT_FILE, "port")
if _MANAGED_PROXY_HOST.fullmatch(host) is None:
raise RuntimeError("managed proxy host file has invalid contents")
if (
re.fullmatch(r"[0-9]{1,5}", port) is None
or not 1 <= int(port, 10) <= 65535
):
raise RuntimeError("managed proxy port file has invalid contents")
return f"http://{host}:{port}"


def _managed_fetch_ca_bundle() -> tuple[int, str]:
"""Open and validate fixed OpenShell TLS trust without a pathname race."""
path = _MANAGED_FETCH_CA_BUNDLE_FILE
no_follow = getattr(os, "O_NOFOLLOW", None)
if no_follow is None:
raise RuntimeError("managed fetch CA bundle is invalid")
flags = os.O_RDONLY | no_follow | getattr(os, "O_CLOEXEC", 0)
try:
descriptor = os.open(path, flags)
except FileNotFoundError:
raise RuntimeError("managed fetch CA bundle is unavailable") from None
except OSError:
raise RuntimeError("managed fetch CA bundle is invalid") from None

try:
metadata = os.fstat(descriptor)
if (
not stat.S_ISREG(metadata.st_mode)
or metadata.st_uid != _MANAGED_FILE_OWNER_UID
or stat.S_IMODE(metadata.st_mode) & 0o022
or metadata.st_size <= 0
):
raise RuntimeError("managed fetch CA bundle is invalid")
descriptor_path = next(
(
candidate
for root in ("/proc/self/fd", "/dev/fd")
if os.path.exists(candidate := f"{root}/{descriptor}")
),
None,
)
if descriptor_path is None:
raise RuntimeError("managed fetch CA bundle is invalid")
return descriptor, descriptor_path
except OSError:
os.close(descriptor)
raise RuntimeError("managed fetch CA bundle is invalid") from None
except RuntimeError:
os.close(descriptor)
raise


def _close_managed_fetch_ca_bundle(descriptor: int) -> None:
try:
os.close(descriptor)
except OSError:
# Closing the read-only trust snapshot cannot expand authority.
pass


def _rewind_managed_fetch_ca_bundle(descriptor: int) -> None:
"""Reset fd-backed trust before each synchronous transport read."""
try:
os.lseek(descriptor, 0, os.SEEK_SET)
except OSError:
raise RuntimeError("managed fetch CA bundle is invalid") from None


def managed_fetch_with_redirects(
url: str,
*,
timeout: int,
max_redirects: int,
original_fetch: Callable[..., Any],
validation_error: type[ValueError],
) -> Any:
"""Fetch through only the launcher-delegated OpenShell proxy.

Outside the managed launcher, preserve the pinned upstream transport. In
the managed image, avoid forbidden direct DNS while keeping requests'
ambient proxy discovery and ``NO_PROXY`` disabled. OpenShell's proxy then
remains the authoritative network-policy and SSRF boundary for every hop.
"""
try:
proxy_url = managed_fetch_proxy_url()
except RuntimeError as exc:
# Keep runtime-integrity failures inside fetch_url's structured
# validation result instead of surfacing an opaque tool exception.
raise validation_error(str(exc)) from exc
if proxy_url is None:
return original_fetch(url, timeout=timeout)

try:
import requests
except ImportError:
# Keep an optional dependency failure inside fetch_url's structured
# validation result without exposing import paths or stack details.
raise validation_error(
"managed fetch transport dependency is unavailable"
) from None
try:
ca_descriptor, ca_bundle = _managed_fetch_ca_bundle()
except RuntimeError as exc:
raise validation_error(str(exc)) from None

def validate_url(candidate: str) -> None:
try:
parsed = urlparse(candidate)
hostname = parsed.hostname
# Force malformed ports through the same structured validation path
# even though requests, rather than this helper, uses the value.
_ = parsed.port
except ValueError as exc:
raise validation_error("URL is malformed") from exc
if parsed.scheme not in {"http", "https"}:
raise validation_error(
f"URL scheme not allowed: {parsed.scheme!r} (must be http or https)"
)
if not hostname:
raise validation_error("URL is missing a hostname")
# RFC 3986 credentials are authority userinfo, exposed by username and
# password. An `@` or `:` after the authority is ordinary path data
# (including valid repository refs/files), never authentication;
# rejecting that shape would create false positives. These validation
# errors never echo the candidate URL, and the explicit OpenShell proxy
# remains the destination-policy and SSRF authority for every hop.
if parsed.username is not None or parsed.password is not None:
raise validation_error("URL credentials are not allowed")
try:
hostname.encode("idna").decode("ascii")
except UnicodeError:
raise validation_error("URL hostname is not valid IDNA") from None

current_url = url
proxies = {"http": proxy_url, "https": proxy_url}
try:
with requests.Session() as session:
# Disable every requests environment-derived session setting, including
# proxy/NO_PROXY, netrc, and CA-bundle discovery. Each request receives
# the sole root-verified proxy mapping explicitly below. The separately
# selected CA bundle establishes TLS transport trust only; it cannot
# choose a proxy or authorize a destination under OpenShell policy.
session.trust_env = False
for _hop in range(max_redirects + 1):
validate_url(current_url)
try:
_rewind_managed_fetch_ca_bundle(ca_descriptor)
except RuntimeError as exc:
raise validation_error(str(exc)) from None
response = session.get(
current_url,
timeout=timeout,
headers={"User-Agent": "Mozilla/5.0 (compatible; DeepAgents/1.0)"},
allow_redirects=False,
proxies=proxies,
verify=ca_bundle,
)
if 300 <= response.status_code < 400:
location = response.headers.get("Location")
if not location:
raise validation_error(
f"Redirect response (status {response.status_code}) is missing a Location header"
)
current_url = urljoin(current_url, location)
continue
response.raise_for_status()
return response

raise requests.exceptions.TooManyRedirects(
f"Exceeded {max_redirects} redirects"
)
finally:
_close_managed_fetch_ca_bundle(ca_descriptor)


def _disabled_auto_approval(reason: str) -> str:
if os.environ.get("NEMOCLAW_DEBUG") == "1":
print(
Expand Down Expand Up @@ -994,6 +1269,7 @@ def assert_safe_runtime() -> None:
"""Reject unmanaged runtime credentials before dcode bootstraps settings."""
_assert_safe_environment()
_assert_safe_auth_state()
managed_fetch_proxy_url()
base_url = managed_inference_base_url()
os.environ["OPENAI_BASE_URL"] = base_url
os.environ["NEMOCLAW_INFERENCE_BASE_URL"] = base_url
Expand Down
Loading
Loading