Windows sandbox HTTPS via Schannel fails with `SEC_E_NO_CREDENTIALS`

Open 💬 8 comments Opened Apr 11, 2026 by eliotcougar
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Summary

On Windows, HTTPS requests that use the Windows TLS stack (Schannel) fail inside the current sandboxed shell with:

  • PowerShell / .NET: Authentication failed, see inner exception
  • inner Win32 error: SEC_E_NO_CREDENTIALS (0x8009030e)
  • curl.exe (Schannel build): schannel: AcquireCredentialsHandle failed

At the same time:

  • Python HTTPS works in the same sandbox
  • the same PowerShell HTTPS call works outside the sandbox
  • sandbox users can do Schannel HTTPS successfully after their profiles are initialized

I started investigating sandbox-user profile initialization because the Schannel failure was SEC_E_NO_CREDENTIALS, which strongly suggested missing or inaccessible per-user Windows state. The fact that the sandbox users initially had no profile directories made that look like a credible lead.

Related issue: #17458 ("Windows sandbox users are created but their profiles are not initialized until manual first logon").

Version / platform

  • Date observed: April 11, 2026
  • Platform: Windows 11 / Windows 10.0.26200
  • PowerShell: 7.5.5
  • curl.exe: 8.18.0 with Schannel

Failing behavior

Inside the sandboxed shell:

Invoke-WebRequest -UseBasicParsing -Uri 'https://api.ipify.org'

fails with:

The SSL connection could not be established, see inner exception.
Authentication failed, see inner exception.
SEC_E_NO_CREDENTIALS (0x8009030e)

Raw .NET TLS fails too:

$tcp = [System.Net.Sockets.TcpClient]::new('api.ipify.org',443)
$ssl = [System.Net.Security.SslStream]::new($tcp.GetStream(), $false, ({$true}))
$ssl.AuthenticateAsClient('api.ipify.org')

and curl.exe fails with:

curl: (35) schannel: AcquireCredentialsHandle failed: SEC_E_NO_CREDENTIALS

What works

  • Python HTTPS in the same sandbox
  • raw TCP connectivity to port 443
  • PowerShell HTTPS outside the sandbox
  • Schannel HTTPS when run manually as CodexSandboxOnline after profile creation
  • Schannel HTTPS when run manually as CodexSandboxOffline after profile creation

Important observations

1. The failing current sandboxed shell is running as the real user

Inside the failing shell:

  • whoami -> ELIOT-PC2\Eliot
  • USERPROFILE -> C:\Users\Eliot

So the failing shell appears to be a restricted real-user context, not an actual dedicated sandbox-user process.

2. Dedicated sandbox users succeed after profile creation

After manually forcing first logon for:

  • CodexSandboxOnline
  • CodexSandboxOffline

both can do:

  • PowerShell HTTPS
  • raw .NET SslStream
  • curl.exe HTTPS

successfully.

That manual profile initialization is tracked separately in #17458.

3. There is also an env inheritance bug

In those sandbox-user sessions, env is still inherited from the real user:

  • USERNAME=Eliot
  • USERPROFILE=C:\Users\Eliot
  • APPDATA=C:\Users\Eliot\AppData\Roaming
  • LOCALAPPDATA=C:\Users\Eliot\AppData\Local
  • TEMP/TMP=C:\Users\Eliot\AppData\Local\Temp

That looks like a real bug in the elevated Windows sandbox path, but it is not sufficient by itself to explain the Schannel failure, because the sandbox users still succeed despite the wrong env.

Why this looks like a sandbox-specific Schannel issue

The failure is specific to the Windows TLS stack:

  • PowerShell / .NET / Schannel curl.exe fail
  • Python HTTPS succeeds

That points to a Schannel / SSPI / token / user-profile interaction, not general network failure.

Repro

Inside sandbox:

whoami
Invoke-WebRequest -UseBasicParsing -Uri 'https://api.ipify.org'
curl.exe https://api.ipify.org

Observed:

  • whoami = real user
  • Schannel HTTPS fails with SEC_E_NO_CREDENTIALS

Outside sandbox, the same PowerShell HTTPS call succeeds.

Expected behavior

HTTPS requests using Schannel should work in the Windows sandbox when network access is allowed.

Actual behavior

Schannel HTTPS fails in the current sandboxed shell with SEC_E_NO_CREDENTIALS.

Related issue discovered during investigation

The offline firewall setup also appears broken:

setup_error.json contains:

{
  "code": "helper_firewall_rule_create_or_add_failed",
  "message": "SetRemotePorts failed: Error { code: HRESULT(0x80070057), message: \"Параметр задан неверно.\" }"
}

That may be a separate Windows sandbox bug.

Suggested investigation areas

  1. Determine whether the active Windows shell sandbox path is using:
  • dedicated sandbox users
  • or a restricted-token-on-real-user path
  1. Compare the failing shell token/context with the successful CodexSandboxOnline / CodexSandboxOffline sessions.
  2. Investigate what Schannel/SSPI state is missing in the restricted real-user sandbox context.
  3. Separately fix the incorrect env inheritance in the elevated sandbox path.
  4. Consider whether #17458 is a prerequisite or partial trigger for this issue, even if it is not the full root cause.

View original on GitHub ↗

8 Comments

github-actions[bot] contributor · 4 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #17458

Powered by Codex Action

arandomhooman · 1 month ago

Still an issue.

Revolt3738 · 1 month ago

Additional reproduction: Schannel fails under CodexSandboxOnline

I can reproduce this issue with the current Windows elevated sandbox.

Environment

  • Windows 11 25H2, build 26200.8894
  • Codex command runner 0.146.0-alpha.3.1
  • Native Windows sandbox:
[windows]
sandbox = "elevated"
  • Active project sandbox configuration:
sandbox_mode = "workspace-write"

[sandbox_workspace_write]
network_access = true

Sandbox identity

Unlike the restricted real-user context described in the original report, the current command process is running as the dedicated sandbox account:

Identity: <machine>\CodexSandboxOnline
AuthenticationType: NTLM
Integrity level: Medium

Outside the sandbox, the same process runs as the normal Windows user with CloudAP authentication.

USERPROFILE inside the sandbox still points to the normal user's profile directory rather than a dedicated CodexSandboxOnline profile.

Reproduction

Inside the sandbox:

Invoke-RestMethod -Uri https://api.github.com/ -TimeoutSec 20
curl.exe -I https://www.microsoft.com/

Both fail before the TLS handshake:

SEC_E_NO_CREDENTIALS (0x8009030E)
schannel: AcquireCredentialsHandle failed

This reproduces with:

  • Windows PowerShell 5.1
  • PowerShell 7.6.4
  • curl 8.21.0 using Schannel
  • Explicitly forcing either TLS 1.2 or TLS 1.3

Control tests

The same commands executed outside the sandbox succeed:

Invoke-RestMethod: success
curl.exe: HTTP 200
Windows PowerShell 5.1: success

Node.js using OpenSSL succeeds inside the sandbox:

Node.js 24.18.0
OpenSSL 3.5.7
HTTP 200

This rules out general DNS, TCP, firewall, certificate trust, and network access problems.

Additional checks:

  • CryptSvc and KeyIso are running
  • FIPS mode is disabled
  • No cipher-suite or ECC policy overrides are configured
  • 28 TLS cipher suites are available
  • SHA-256, AES, RSA, and ECDH-P256 operations succeed
  • The Microsoft RSA Schannel Cryptographic Provider opens successfully
  • Sandbox setup refresh reports errors=[]

Current conclusion

The failure is specific to Schannel/SSPI credential acquisition under the CodexSandboxOnline logon context. The dedicated sandbox identity does not appear to have the per-user or logon state required for Schannel to acquire its default outbound credential handle.

Because network_access = true, standard Windows HTTPS clients should work inside the sandbox, or this limitation should be documented explicitly.

bbingz · 1 month ago

Analysis: current elevated-sandbox path launches CodexSandboxOnline with dwLogonFlags=0 and no profile initialization anywhere — still present at current HEAD

Issue anchor: #17459, with the 2026-07-27 reproduction by @Revolt3738 on command runner 0.146.0-alpha.3.1 (process identity CodexSandboxOnline, errors=[] from setup refresh, PowerShell 5.1 / 7.6.4 / Schannel curl all failing pre-handshake with SEC_E_NO_CREDENTIALS, Node/OpenSSL fine in the same sandbox).

The current-source precondition for that reproduction is live (verified 2026-07-28 at main = 4c434651; paths below are under codex-rs/):

  1. Setup creates the dedicated accounts without initializing a profile. provision_sandbox_users → ensure_local_user calls NetUserAdd (codex-rs/windows-sandbox-rs/src/bin/setup_main/win/sandbox_users.rs:70-115, NetUserAdd at :115). Nothing in the setup path calls LoadUserProfile.
  1. The elevated runner launches the dedicated account with dwLogonFlags=0. CreateProcessWithLogonW(..., /*dwlogonflags*/ 0, ...) with the in-code comment "Sandbox users have no profile state that commands should inherit" (codex-rs/windows-sandbox-rs/src/elevated/runner_client.rs:346-352). Microsoft's documentation for CreateProcessWithLogonW states the profile is not loaded by default and callers needing normal HKCU state must use LOGON_WITH_PROFILE (or load it explicitly). A repo-wide grep confirms LoadUserProfile, LOGON_WITH_PROFILE, and CreateEnvironmentBlock appear nowhere in windows-sandbox-rs/src.
  1. The child then runs under a restricted token derived from that profile-less logon. The command runner derives the restricted token from its current dedicated-user token (codex-rs/windows-sandbox-rs/src/bin/command_runner/win.rs:269-287) and spawns the child via CreateProcessAsUserW (codex-rs/windows-sandbox-rs/src/conpty/mod.rs:146-158). The restricted token still uses DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED (codex-rs/windows-sandbox-rs/src/token.rs:480).
  1. No test coverage on this axis. token_tests.rs asserts restricting-SID presence only; nothing exercises Schannel credential acquisition, profile loading, or HTTPS from inside the sandbox.

Honest causal boundary. The bug and the current no-profile launch precondition are high-confidence; the exact SSPI causality is not proven:

  • SEC_E_NO_CREDENTIALS is a credential-acquisition failure, but that alone doesn't identify which missing profile/logon/token property causes it in each mode.
  • #17458 shows profile-directory existence alone can be insufficient (profiles present, still failing).
  • The three restricted-token flags and the restricting-SID set remain confounded with the profile state — no per-flag A/B has ever been run.
  • The failing AcquireCredentialsHandle call lives in external child software (curl/PowerShell/Git), not in Codex itself; the repo contains no Schannel/SSPI handling at all.

So this is not a claim that restoring LOGON_WITH_PROFILE fixes everything. A definitive fix decision needs a Windows A/B matrix:

  1. current dwLogonFlags=0 baseline;
  2. LOGON_WITH_PROFILE;
  3. explicit LoadUserProfile / CreateEnvironmentBlock;
  4. the restricted-token flags varied one at a time;
  5. assert Schannel AcquireCredentialsHandle + HTTPS behavior and inspect USERPROFILE/HKCU in the child.

For completeness: merged PR #27944 (stale-credential recovery) touches this same launch call but only handles ERROR_LOGON_FAILURE retry — it doesn't change profile loading.

Analysis only; no PR attached per contribution policy.

No unsolicited PR from me; happy to refine or draft fail-first tests if maintainers invite a fix direction.

Root-cause hypothesis: see mechanism chain above.

EvilHumphrey · 27 days ago

Additional reproduction: a nested Codex CLI loses both Windows TLS credentials and the host ChatGPT login

Reproduced on 2026-07-31 after restarting the app.

Environment
  • Windows 11, build 26200
  • Codex app package 26.721.11231.0
  • Codex CLI 0.146.0-alpha.3.1
  • sandbox_mode = "workspace-write"
  • [windows] sandbox = "elevated"
  • sandbox_workspace_write.network_access = true
  • approval policy on-request
Reproduction and controls
  1. A shell command launched by Codex Desktop runs as:

``text
<machine>\CodexSandboxOffline
``

  1. Schannel HTTPS fails before the handshake:

``text
curl: (35) schannel: AcquireCredentialsHandle failed:
SEC_E_NO_CREDENTIALS (0x8009030e)
``

  1. The configured CLI under that sandbox identity has no usable cached login:

``text
codex login status
Not logged in
exit 1
``

  1. The exact same CLI outside the Desktop sandbox, under the normal Windows user, reports:

``text
codex login status
Logged in using ChatGPT
exit 0
``

  1. Launching a second codex exec from the Desktop sandbox fails on both WebSocket and HTTP fallback with:

``text
native_root_error_count=1
invalid peer certificate: UnknownIssuer
``

  1. Setting CODEX_CA_CERTIFICATE to a valid PEM CA bundle changes the failure rather than fixing it: TLS reaches the service, then the nested CLI receives HTTP 401 Missing bearer or basic authentication. This separates the certificate-trust problem from the missing-login problem.
  1. Launching the exact same non-sensitive, read-only codex exec through an explicitly approved host boundary succeeds. Two clean control runs returned exactly:

``text
TLS_PROBE_OK
RESTART_HOST_OK
``

  1. Adding the intended workspace roots and restarting with the elevated Windows sandbox fixed split-root file patching, but did not change the CodexSandboxOffline TLS/login behavior.
Impact

Codex Desktop cannot directly launch an authenticated child Codex CLI from its sandbox, even though the Desktop app and host CLI are already signed in. The safe current workaround is to approve a host-boundary launch and then keep the child CLI inside its own read-only/workspace-write sandbox.

Expected behavior / product question

Either:

  • Schannel and the authenticated CLI should work under the documented Windows sandbox when network access is enabled; or
  • Desktop should expose a supported, scoped host-launch/auth-brokering mechanism for nested codex exec workflows.

Copying auth.json, injecting long-lived tokens, or disabling TLS verification would weaken the security boundary and should not be necessary.

Related: #21470 and #31073. Matching in-app feedback with logs was submitted. Feedback ID: 019fb996-7f0e-7421-9136-2083b18cf271.

bbingz · 8 days ago

Analysis (community)

@EvilHumphrey — your nested Codex CLI case (app 26.721.11231.0, CLI 0.146.0-alpha.3.1, elevated sandbox, network_access = true) is the same launch precondition I posted on #17459, plus a second symptom (host ChatGPT login missing inside the nested CLI).

Root-cause hypothesis (re-checked 2026-08-20 at main 3b45c2906): the elevated runner still starts CodexSandboxOnline with no profile load. Microsoft TLS/Schannel then fails AcquireCredentialsHandle (SEC_E_NO_CREDENTIALS). A nested CLI in that identity can also fail to see the host login store for the same reason: HKCU/profile/logon state was never initialized.

Still live:

  • CreateProcessWithLogonW(..., /*dwlogonflags*/ 0, ...) with the comment that sandbox users have no profile state (codex-rs/windows-sandbox-rs/src/elevated/runner_client.rs:348-352).
  • Setup still NetUserAdds the dedicated account without LoadUserProfile (sandbox_users.rs provision path).
  • Repo grep: LOGON_WITH_PROFILE / LoadUserProfile still absent under windows-sandbox-rs/src.

Honest boundary (unchanged): this does not prove that LOGON_WITH_PROFILE alone fixes every mode. #17458 already showed profile directories can exist and Schannel can still fail. Nested CLI losing ChatGPT login is consistent with missing HKCU, but I have not A/B'd that on Windows here.

No unsolicited PR. Useful A/B if anyone has a Windows box: current dwLogonFlags=0 vs LOGON_WITH_PROFILE vs explicit LoadUserProfile, asserting both Schannel HTTPS and whether ~/.codex/host login files are visible to the nested CLI.

braze-taffo · 8 days ago

Additional current reproduction: both elevated and unelevated restricted tokens fail Schannel

I independently reproduced this issue on 2026-08-20 with the current native Windows sandbox.

Environment
  • Windows 11, build 26200
  • ChatGPT/Codex desktop package: 26.814.5517.0
  • Codex CLI: 0.148.0-alpha.15
  • sandbox_mode = "workspace-write"
  • sandbox_workspace_write.network_access = true
  • Tested both:
  • [windows] sandbox = "elevated"
  • [windows] sandbox = "unelevated"
Reproduction matrix

| Execution context | Windows token | PowerShell / Schannel curl | Node.js HTTPS |
|---|---|---|---|
| Normal host process | IsTokenRestricted=False | succeeds (HTTP 200) | succeeds |
| Elevated Codex sandbox (CodexSandboxOnline) | IsTokenRestricted=True | fails with SEC_E_NO_CREDENTIALS | succeeds (HTTP 200) |
| Unelevated Codex sandbox (normal user identity) | IsTokenRestricted=True | fails with SEC_E_NO_CREDENTIALS | succeeds (HTTP 200) |

The failing commands include:

Invoke-WebRequest -UseBasicParsing -Uri 'https://www.microsoft.com/'
curl.exe -I https://www.microsoft.com/

The curl failure is:

curl: (35) schannel: AcquireCredentialsHandle failed:
SEC_E_NO_CREDENTIALS (0x8009030e)
No credentials are available in the security package

A Node.js HTTPS request to the same destination succeeds from the same sandboxed process context.

Additional controls:

  • DNS resolution succeeds.
  • TCP connectivity to port 443 succeeds.
  • The same PowerShell and curl.exe commands succeed outside the sandbox.
  • Elevated sandbox setup completed without reported errors (errors=[]).
  • No Windows error 1385 or sandbox-user logon failure was observed.
Diagnostic implication

This reproduction follows the restricted sandbox token in both native sandbox implementations:

  • Elevated mode reproduces under the dedicated CodexSandboxOnline identity.
  • Unelevated mode reproduces under the normal Windows user identity.
  • The normal host token does not reproduce it.

That makes missing dedicated-user profile state a plausible contributing factor for elevated mode, but not a complete explanation by itself. The shared condition across both failing modes is the restricted token, while the success of Node/OpenSSL rules out a general DNS, TCP, firewall, or certificate-trust failure.

A useful next A/B would be to vary the restricted-token flags and restricting SID set one at a time, while separately testing profile loading (LOGON_WITH_PROFILE / LoadUserProfile) and asserting Schannel AcquireCredentialsHandle behavior.

No credentials, private repository names, or user-specific paths are included in this report.

bbingz · 6 days ago

Analysis (community)

@braze-taffo — your 2026-08-20 matrix on #17459 (Desktop 26.814.5517.0, CLI 0.148.0-alpha.15) is the first public split I have seen that unconfounds missing profile from restricted-token. Both elevated CodexSandboxOnline and unelevated (real-user identity) fail Schannel with SEC_E_NO_CREDENTIALS while IsTokenRestricted=True; the unrestricted host process succeeds; Node.js HTTPS in the same sandboxed process succeeds. That is a different residual than the nested-CLI / no-profile note I posted earlier.

Root-cause hypothesis (re-checked 2026-08-21 at main 536f86e5c): Schannel/AcquireCredentialsHandle is breaking on the restricted token, not only on a missing CodexSandboxOnline profile. Unelevated never calls CreateProcessWithLogonW. It takes the interactive user’s token, runs CreateRestrictedToken with DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED, then CreateProcessAsUserW. The child is still the signed-in user (so HKCU/profile can exist) and still IsTokenRestricted=True. That matches your unelevated row. Node/OpenSSL never talk to Schannel, so they stay green in every row.

Live path (paths under codex-rs/):

  1. Unelevated / restricted-token spawn (your new row). CreateRestrictedToken flags are DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED (windows-sandbox-rs/src/token.rs:42-44, 480-483). The restricting SID list is capabilities + logon + Everyone (:461-477). Spawn is CreateProcessAsUserW (windows-sandbox-rs/src/process.rs:153-165), not CreateProcessWithLogonW. Repo grep: LOGON_WITH_PROFILE / LoadUserProfile still absent under windows-sandbox-rs/src.
  2. Elevated / dedicated-account spawn (previous row). Still CreateProcessWithLogonW(..., /*dwlogonflags*/ 0, ...) with the comment that sandbox users have no profile state (windows-sandbox-rs/src/elevated/runner_client.rs:346-352). After that logon the command runner still derives a restricted token with the same three flags. So elevated is no profile plus restricted token; unelevated is real profile plus restricted token. Your matrix says both fail Schannel, which is why I no longer treat dwLogonFlags=0 as the only live precondition.
  3. What this does not prove. I have not A/B’d the three flags one at a time on a Windows box. WRITE_RESTRICTED plus extra restricting SIDs is the most plausible Schannel/CAPI break, but #17458 already showed “profile directory exists” is not sufficient. Node succeeding is TLS-stack evidence, not a proof that network_access = true is a no-op (the capability SID is still attached).

No unsolicited PR. Useful next A/B if anyone has a Windows host: same workspace-write + network_access = true command under (a) current restricted token, (b) restricted token without WRITE_RESTRICTED, (c) unrestricted CreateProcessAsUserW of the same user, asserting Invoke-WebRequest / curl.exe Schannel vs Node HTTPS. Elevated dwLogonFlags=0 vs LOGON_WITH_PROFILE remains a second axis, not a replacement for (a)–(c).