Skip to content

[bug]: Windows restricted_token: Hermes tools still fail on 0.9.40 — bash children crash in Cygwin init (sigwait pipe ACCESS DENIED), PR #275 fix incomplete #34

Description

@0xReSan

Related: previous report #29
("Windows restricted_token: Hermes tools fail because MSYS2/git-bash cannot initialize (CreateFileMapping error 5)").
The PR #275 fix noted in the CHANGELOG does not resolve this on 0.9.40; this issue
tracks the post-fix reproduction with new procmon evidence (per-process msys-*-sigwait
named-pipe denials, see Actual behavior).

finsafe version

0.9.40 (output of finsafe version)

Platform

  • Windows 11 Pro, build 26200 (NT 10.0.26200.9168)
  • finsafe probe --json highlights: appcontainer_works=true, helper_installed=false, conpty_present=true, projfs_feature_enabled=false, restart_required=false

Component

Personal CLI (finsafe run, self-confine, probe, doctor)

Steps to reproduce

Environment used for the repro:

  • Agent: Hermes Agent v0.20.6 (2026.8.27, upstream 64b96bb5), installed at C:\Users\xuls2\AppData\Local\hermes\hermes-agent (git-based install). Hermes's terminal/filesystem tools spawn the bundled Git-for-Windows bash: C:\Users\xuls2\AppData\Local\hermes\git\bin\bash.exe (MSYS2/Cygwin runtime msys-2.0.dll).
  • Working dir: D:\Hermes\finsafe (contains ./workspace).

Wrapper policy hermes-windows-interactive.yaml:

schema_version: 1
kind: local-wrapper
program_mode: interactive
degrade:
  allow_fallback: false
audit:
  require_policy_digest: true
  require_resolved_posture: true
network: host
resources:
  memory_max: "2G"
  pids_max: "256"
  cpu_max: "100000 100000"
windows:
  backend: restricted_token
filesystem:
  skip_default_deny_read: true
  read_only_paths: []
  read_write_paths:
    - "./workspace"
    - "${USERPROFILE}/AppData/Local/FinSAFE/hermes"
    - "${USERPROFILE}/AppData/Local/FinSAFE/hermes/tmp"

Notes on the environment setup (so you can reproduce without guessing):

  • The real Hermes home (%LOCALAPPDATA%\hermes) is refused by FinSAFE as an inheritable write root ("Electron userData product folder"), so Hermes is pointed at a dedicated sandbox home via HERMES_HOME=C:\Users\xuls2\AppData\Local\FinSAFE\hermes (with config.yaml, .env, auth.json copied in).
  • The sandbox has no writable system temp dir, so TEMP/TMP/TMPDIR are redirected to C:\Users\xuls2\AppData\Local\FinSAFE\hermes\tmp.

Minimal isolated reproduction (no LLM involved, two side-by-side runs):

# [1] CONTROL — bash as the DIRECT broker (works, prints BASH-OK):
finsafe --policy hermes-windows-interactive.yaml self-confine -- "C:\Users\xuls2\AppData\Local\hermes\git\bin\bash.exe" -c "echo BASH-OK"

# [2] BUG — cmd spawns bash as a CHILD process (bash dies in Cygwin init):
finsafe --policy hermes-windows-interactive.yaml self-confine -- cmd /c "C:\Users\xuls2\AppData\Local\hermes\git\bin\bash.exe -c \"echo CHILD-BASH-OK\""

Full reproduction through Hermes tools (Hermes keeps retrying, so expect it to run for a while):

$env:HERMES_HOME='C:\Users\xuls2\AppData\Local\FinSAFE\hermes'
$env:TEMP='C:\Users\xuls2\AppData\Local\FinSAFE\hermes\tmp'
$env:TMP='C:\Users\xuls2\AppData\Local\FinSAFE\hermes\tmp'
$env:TMPDIR='C:\Users\xuls2\AppData\Local\FinSAFE\hermes\tmp'
finsafe --policy hermes-windows-interactive.yaml self-confine -- hermes chat -q "Use write_file to create ./workspace/tool-test.txt containing HELLO, then reply exactly: TOOL-DONE"

Expected behavior

A bash process spawned as a child of the sandboxed broker (Python/cmd/hermes) should initialize the Cygwin runtime and run normally, just like the direct-broker bash in repro [1]. Hermes's terminal, write_file, read_file, and search_files tools (all bash-backed) should complete their work inside the sandbox.

Actual behavior

Any bash spawned by the broker (not exec'd directly) dies during Cygwin runtime initialization with a Win32 error 5 (access denied):

  • Via cmd (repro [2]):

    0 [main] bash (PID) C:\Users\xuls2\AppData\Local\hermes\git\bin\..\usr\bin\bash.exe: *** fatal error - CreateFileMapping S-1-5-21-497800499-2125783479-1825478815-1001.1, Win32 error 5. Terminating.
    
  • Via Hermes tools (same class of failure, different Cygwin object):

    0 [main] bash (PID) C:\Users\xuls2\AppData\Local\hermes\git\bin\..\usr\bin\bash.exe: *** fatal error - couldn't create signal pipe, Win32 error 5
    

    with child exit code 3221225794 (0xC0000142, STATUS_DLL_INIT_FAILED).

Consequences inside Hermes (from C:\Users\xuls2\AppData\Local\FinSAFE\hermes\logs\errors.log, session 20260831_213024_a20862):

agent.tool_executor: Tool terminal returned error: {"output": "0 [main] bash (...) ... *** fatal error - couldn't create signal pipe, Win32 error 5", "exit_code": 3221225794, ...}
agent.tool_executor: Tool write_file returned error: {"bytes_written": 0, ..., "error": "Failed to write file: 0 [main] bash (...) ... fatal error - ..."}
agent.tool_executor: Tool read_file returned error: {"content": "", ..., "error": "File not found: D:\\Hermes\\finsafe\\workspace\\tool-test.txt"}
agent.tool_executor: Tool search_files returned error: {"total_count": 0, "error": "File search requires 'rg' (ripgrep) or 'find'. ..."}
tools.environments.base: init_session failed (session=...): snapshot bootstrap failed with exit code 256 - falling back to bash -l per command

execute_code fails independently with the same restricted-token write class:

tools.code_kernel: session kernel failed: PermissionError: [Errno 13] Permission denied:
'C:\Users\xuls2\AppData\Local\FinSAFE\hermes\tmp\hermes_kernel_2oq_ydc0\hermes_tools.py'

Plain LLM queries (no tools) work fine under the sandbox: agent starts, outbound HTTPS to the LLM provider works, session state persists to the dedicated home.

Additional context

Process Monitor capture (attached: 00.CSV excerpt below)

Captured during the repro window (21:30-21:31, 12,806 events, 12,368 from bash.exe). Exactly 19 events with Result = ACCESS DENIED, all from bash.exe, all CreateFile:

Path   : \Device\NamedPipe\msys-dc23a859c8284af1-<PID>-sigwait   (per-process Cygwin signal pipe)
Result : ACCESS DENIED
Detail : Desired Access: Generic Write, Read Attributes
         Disposition: Open   Options: Synchronous IO Non-Alert, Non-Directory File

Failing sequence for one bash (PID 14252) — the process creates its own pipe, then its own open-for-write is denied:

CreateFile  ...\git\usr\bin\msys-2.0.dll                      SUCCESS   (Cygwin DLL load)
CreatePipe  \Device\NamedPipe\msys-dc23a859c8284af1-14252-sigwait  SUCCESS
CreateFile  \Device\NamedPipe\msys-dc23a859c8284af1-14252-sigwait  ACCESS DENIED   <- bash dies here
CloseFile   (pipe)                                             SUCCESS

Control (direct-broker bash, PID 5688, repro [1]) — same pipe, same access, all SUCCESS:
CreatePipe -> CreateFile -> ReadFile -> CloseFile.

The 286 CreateFileMapping events in this capture are all image/DLL section mappings (e.g. imm32.dll, bash.exe, .sdb) with SUCCESS / FILE LOCKED WITH ONLY READERS — normal. The SID-named shared-memory mapping denial (Win32 error 5) appeared in the cmd -> bash repro instead, i.e. both Cygwin objects fail depending on which one Cygwin init hits first.

The denied bash PIDs in the capture (15012, 10064, 14732, 10916, 12296, ...) exactly match the PIDs in Hermes's errors.log for the same window.

Root-cause analysis (evidence-backed)

Cygwin creates per-process IPC objects (the msys-<hash>-<pid>-sigwait named pipe, and the SID-named shared-memory mapping S-1-5-21-...-1001.1) whose DACLs grant the normal user SID. Under FinSAFE's RestrictedToken backend the user SID is not usable for writes — writes require the FinSAFE WRITE_RESTRICTED capability SID, which is absent from these Cygwin-created DACLs — so the process's own open-for-write is denied with Win32 error 5 and Cygwin aborts.

This is the same class as issue #29. The current CHANGELOG entry ("RestrictedToken + Git for Windows bash", PR #275) grants capability SIDs on existing MSYS2 SID-named file mappings before CreateRestrictedToken, which covers the directly exec'd broker only — it does not cover the per-process msys-*-sigwait named pipes (or the mappings when they must be created by the sandboxed child), which is why bash children spawned later by the broker still crash.

Suggested fix direction: extend the pre-restriction capability-SID grant to the per-process msys-*-sigwait named pipes created by sandboxed Cygwin processes (and re-check the shared-memory mapping case when the child, not the host, is the creator), or otherwise arrange for Cygwin-created IPC object DACLs to include the WRITE_RESTRICTED SID.

Environment detail

  • Hermes: v0.20.6Install directory: C:\Users\xuls2\AppData\Local\hermes\hermes-agent, Install method: git, Python: 3.11.15, OpenAI SDK: 2.24.0

  • Bundled git-bash: C:\Users\xuls2\AppData\Local\hermes\git\bin\bash.exe -> ...\usr\bin\bash.exe, msys-2.0.dll

  • finsafe probe --json full output:

    {"schema_version":3,"host_os":"windows","host_arch":"x86_64","windows":{"appcontainer_works":true,"helper_installed":false,"conpty_present":true,"projfs_feature_enabled":false,"projection_smoke_works":false,"restart_required":false,"learning_mode_trace_api_available":false,"learning_mode_security_environment_api_available":false}}

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions