Skip to content

Credentialed Cloudflare-fronted upstreams have no working TLS mode: unconditional termination (#533) collides with Cloudflare rejecting the re-originated handshake #2455

Description

@mill101

Problem Statement

Hi,
Since #533 made TLS termination unconditional (auto-detect + terminate, with tls: skip as the explicit opt-out for raw tunneling), there is no configuration that lets the sandbox proxy reach an upstream that is both (a) fronted by Cloudflare, which rejects the proxy's re-originated TLS handshake, and (b) requires a credential that OpenShell resolves and injects at the egress boundary (an openshell:resolve:env:* placeholder).

Concretely, reaching opencode.ai (OpenCode Zen, a Cloudflare-fronted OpenAI-compatible inference API that requires a bearer token) from inside a NemoClaw/Hermes sandbox fails in every available policy configuration:

Egress policy Result
Default / protocol: rest (auto-detect → terminate, per #533) Proxy terminates and re-originates TLS. Cloudflare resets the re-originated handshake. NET:FAIL, connection never completes.
access: full + tls: skip Raw L4 passthrough succeeds — no reset, origin reachable. But because the proxy never sees plaintext, it cannot rewrite the openshell:resolve:env:OPENCODE_ZEN_API_KEY placeholder. The literal, unresolved placeholder string is sent as the bearer token. Upstream returns 401 Invalid API key.

So today, for any endpoint matching this combination (Cloudflare or similarly TLS-fingerprint-sensitive front + boundary-resolved credential), there is no configuration that both reaches the origin and delivers a working credential. This was root-caused and reproduced against a live upstream in NVIDIA/NemoClaw#7323 (network-layer capture showing the reset under termination, and the raw placeholder under tls: skip) and NVIDIA/NemoClaw#7361 (a related but distinct client-side credential bug). Filing here at Dongni-Yang's request from that thread, since neither NemoClaw issue can be resolved without an OpenShell-side change.

Note this is a different symptom from #760: that report was about post-CONNECT stream/frame tampering under tls: skip with a verified-genuine origin certificate (WebSocket/Noise protocol breakage). Here, under tls: skip, the TCP/TLS layer works fine end-to-end — the problem is purely that credential injection requires plaintext visibility, which tls: skip by definition doesn't have.

Proposed Design

Two designs would address this; (b) is the one that fully closes the gap, (a) is a narrower, more mechanical addition:

(a) Explicit "CONNECT-passthrough with no credential resolution" mode. A policy value (e.g. tls: skip today, or a new explicit third state) that is documented as not attempting placeholder resolution, paired with a corresponding fail-closed check: if a policy in this mode targets a rule/binary that has a resolvable credential placeholder configured, refuse to apply the policy (or warn loudly) rather than silently sending the raw placeholder as if it were the credential. This wouldn't unblock credentialed Cloudflare-fronted endpoints, but it would turn today's silent 401 (leaking an internal placeholder string to the upstream) into a clear configuration-time error, which is a meaningful safety improvement on its own regardless of whether (b) ships.

(b) Make the proxy's re-originated TLS acceptable to Cloudflare (or similar fronting providers) while preserving termination for credential injection. The reset appears to be a TLS fingerprinting rejection (JA3/JA4-style, ALPN/ClientHello shape) rather than a protocol violation — a direct curl from the same host to the same origin at the same moment succeeds, so the origin and network path are otherwise healthy; only the proxy's re-issued handshake is rejected. This suggests the fix is about matching the re-originated ClientHello/ALPN/cipher-suite profile to what a normal Rustls/OpenSSL/BoringSSL client would present (or giving operators a way to select a specific TLS client fingerprint profile for re-origination), rather than a deeper architectural change. This is the only shape of fix that lets a Cloudflare-fronted endpoint receive both TLS connectivity and a rewritten credential simultaneously.

I don't have visibility into the proxy's TLS client implementation (OpenShell is a closed-source binary from this environment's vantage point, so I can't point to the exact crate/config responsible for the re-origination handshake's fingerprint) — this is offered as an external, black-box-observed root cause, not a code-level diagnosis. Happy to provide additional network captures (OCSF logs, packet-level detail if useful) against the live opencode.ai repro if that helps narrow down the TLS client behavior.

Alternatives Considered

  • Rely on tls: skip alone (status quo, documented in NemoClaw#7352): Works for credential-less upstreams (its original WhatsApp/Baileys use case) but is a dead end for anything requiring boundary-resolved credentials — it isn't an alternative to a real fix, just a workaround for a different problem shape.
  • Move credential injection to a layer other than the egress proxy (e.g., resolve the placeholder client-side inside the sandbox before the request leaves the process): rejected on the NemoClaw side as a deliberate security trade-off — the whole point of boundary-resolved credentials is that the sandboxed agent never sees the raw secret in-process. Reintroducing that would undo the security property feat(sandbox): auto-detect TLS and terminate unconditionally for credential injection #533 and the placeholder mechanism were built to provide.
  • Have the client present its own genuine TLS handshake and inject the credential via a non-TLS mechanism (e.g., a side-channel header added at the HTTP layer only when a real passthrough is detected): more invasive, likely requires protocol-specific handling per upstream, and probably harder to build than fixing the fingerprint mismatch in (b).

Agent Investigation

Not applicable in the way the template envisions — I don't have access to the OpenShell source tree from the sandbox/host environment this was observed in (confirmed via find / -iname '*openshell*' -type d, which turns up only config/state directories, no source). Everything in this report is derived from external, black-box observation: OCSF egress logs, direct host-side curl comparisons against the same origin at the same time, and the policy-application behavior described in NemoClaw#7323/#7352/#7361. If someone with access to the proxy's TLS client code can confirm the specific ClientHello/ALPN mismatch, that would convert this from a black-box hypothesis into a concrete fix target.

Checklist

  • I've reviewed existing issues and the architecture docs
  • This is a design proposal, not a "please build this" request

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:triage-neededOpened without agent diagnostics and needs triage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions