hermes approvals test --env-type docker --json -- reboot exited 0. The verdict was allow. The rule field was empty. The detail said an isolated container backend skips all command guards except approvals.deny.
The same words with the env type left at local exited 3. The verdict was hardline-deny. The rule was system shutdown/reboot. The detail said the hardline blocklist is never bypassable, including under --yolo and approvals.mode: off.
Those two details are both printed by the same handler. They are not two views of one floor. The local line is the floor. The docker line is a backend shortcut that the dry-run always takes for docker and modal. The shortcut does not ask whether that backend has a host path mounted. The runtime does.
What was measured
The binary was Hermes Agent v0.22.5+471.g5894e75, dated 2026.9.24 in its own version line, commit 5894e750c3, on Linux x86_64. The run was 3 November 2026. No container was started. reboot was not executed. rm was not executed. The command strings were handed to the approval evaluator and discarded.
HERMES_HOME pointed at a new directory. Its config.yaml started as:
approvals:
mode: manual
cron_mode: deny
The JSON came from hermes_cli.approvals_test.approvals_test_command, the handler behind hermes approvals test --json, loaded by the interpreter already installed for that checkout. Pointing the hermes launcher at that new data root did not print the JSON. It started a dependency install for the new root and failed looking for clang++ while building python-olm. The handler and the launcher are the same test once a home can start. The numbers below are the handler’s numbers, not a second implementation.
The security page shipped with this checkout, and the same page on the project site read the same day, says the hardline floor refuses a command regardless of --yolo, /yolo, approvals.mode: off, a cron job in approve mode, or an “allow always” click. It says there is no override flag. It does not name docker or modal in that list. The hermes approvals test --help text on this binary does name them: isolated container backends such as docker skip the guards. The help does not say the skip depends on mounts, and it does not say a user deny rule is kept.
The flag clears the floor, not the deny list
On the manual config above, the handler returned these verdicts.
reboot on local was hardline-deny, exit 3. reboot on ssh was the same hardline deny. reboot on docker was allow, exit 0. reboot on modal was allow, exit 0.
rm -rf ./build on local was ask-approval, exit 2, rule recursive delete. The detail said the runtime would raise an interactive approval prompt. rm -rf ./build on docker was allow, exit 0. The recursive-delete pattern is not hardline. The docker shortcut removed it anyway. The dry-run does not keep a softer prompt just because the command is less than a floor hit.
approvals.mode: off did not clear the local reboot deny. Exit stayed 3, verdict stayed hardline-deny. The same off mode turned the local rm -rf ./build result from ask-approval into allow. HERMES_YOLO_MODE=1, set in the process environment before the handler imported, did the same split: local reboot stayed a hardline deny, and the recursive delete became an allow. The security page is right about yolo and mode off on the local floor. It is not a description of --env-type docker.
A deny list is the part the shortcut keeps. The home was then given:
approvals:
mode: manual
cron_mode: deny
deny:
- 'git push --force*'
- 'reboot*'
git push --force origin main was user-deny, exit 3, on both local and docker. The rule string was git push --force*. reboot on docker, which had been an allow, became user-deny, exit 3, rule reboot*. echo ok stayed allow, exit 0, on both env types. The deny globs are not a second hardline list. A command that matches none of them still takes the docker shortcut.
That is the miss if you read only the hardline sentence and then point a docker backend at the agent. The sentence says the floor holds. The dry-run says the floor does not hold on docker unless you wrote a deny rule that matches the command. The security page does say user deny rules apply to isolated containers before any backend shortcut. It does not say the hardline floor is one of the checks that shortcut skips. The JSON detail says the skip is “all command guards” except approvals.deny. On a docker backend with no host mount, the runtime agreed with that detail.
A host mount is not in the flag
The dry-run calls the container-skip check with the env type alone. It has no argument for a bind mount. The terminal tool does. Before it asks the guard, it treats docker as having host access when docker_mount_cwd_to_workspace is on and a host working directory is set, or when docker_volumes contains a host path. The default in this checkout is docker_mount_cwd_to_workspace: false and docker_volumes: []. Passing that default shape to the mount check returned false. Passing a cwd mount returned true. Passing a volume written as /work:/data returned true. A local env type with the cwd mount still returned false. The mount check is a docker check.
The runtime guard was then called with the same command strings and an explicit host-access bit. No container was created. The bit is the argument the terminal tool would have passed.
reboot on docker with host access false was approved. That matches the dry-run allow. reboot on docker with host access true was not approved. The message was the hardline block, and the text said the command cannot be executed via the agent, not even with --yolo, /yolo, approvals.mode: off, or cron approve mode. That text still does not mention the no-mount docker path, which had just approved the same string.
rm -rf ./build on docker with host access false was approved. With host access true it was not approved, and the message said it was asking for approval for a recursive delete. So a host-mounted docker backend does not only restore the floor. It also restores the ordinary dangerous-command prompt that the dry-run’s docker line had cleared.
reboot on modal with host access true was still approved. Modal does not have the docker exception. The dry-run and the runtime agree on modal: the shortcut stays on. ssh never took the shortcut in the dry-run, so a host path on an ssh backend is not this flag’s problem. If the backend is ssh, read the local floor. If the backend is modal, do not expect a mount to bring the floor back. If the backend is docker, read the mount before you trust exit 0.
The default mount setting is false, so a stock docker backend matches the dry-run. Turning terminal.docker_mount_cwd_to_workspace on, or adding a host volume, changes the runtime and does not change hermes approvals test --env-type docker. The flag cannot represent that config. An allow from the flag is the no-mount answer. It is not a certificate that the docker backend you actually start will skip the floor.
Ask-approval is not the cron verdict
The local dry-run for rm -rf ./build said the runtime would raise an interactive prompt. That sentence is the handler’s detail string. It does not read HERMES_CRON_SESSION, and it does not read approvals.cron_mode.
The guard was called twice in one process, with the manual config still set to cron_mode: deny. Without HERMES_CRON_SESSION, the recursive delete was not approved, and the message said it was asking for approval. With HERMES_CRON_SESSION=1 in that same process, the recursive delete was not approved, and the message began BLOCKED: Command flagged as dangerous (recursive delete) but cron jobs run without a user present to approve it. No scheduler was started. The variable is what the guard uses to decide it is inside cron.
The dry-run cannot print that block. Both situations are ask-approval, exit 2, if you only run the test command. Exit 2 means the pattern is in the dangerous list and no earlier rule allowed or denied it. It does not mean a cron job will stop and wait. On this config a cron-marked call does not wait. It blocks, and the text tells the agent to find another command. cron_mode: approve was not the config under test. The security page says approve mode auto-approves dangerous commands in cron. This run does not show that mode. It shows that the dry-run’s prompt sentence is the wrong sentence for a deny-mode cron mark.
reboot stayed a hardline deny in the cron-marked call as well as in the unmarked local call. The cron variable did not have to do that work. The floor did. The docker no-mount call had already shown that the floor is not what a container shortcut uses. Put the cron mark and the docker shortcut in the same review: one changes a prompt into a block, the other removes the floor unless a deny rule or a host mount puts a check back.
Verify four results, not the exit code alone
Use a Hermes home that already starts. On this binary a brand-new data root began a dependency install before the test handler could run. Write the manual config, then run the test command twice.
hermes approvals test --json -- reboot
hermes approvals test --env-type docker --json -- reboot
Read verdict, exit_code, rule, and detail. Exit 3 with hardline-deny is the floor. Exit 0 with the isolated-backend detail is the shortcut. Exit 3 with user-deny is your deny list, and that one should survive --env-type docker. Exit 2 is a prompt pattern. It is not evidence that cron will prompt.
Repeat the docker line after you add a deny glob that matches a command you do not want the shortcut to allow. reboot* turned the docker allow back into user-deny here. A glob that does not match leaves the shortcut in place. echo ok stayed an allow beside reboot* and git push --force*.
If terminal.docker_mount_cwd_to_workspace is true, or terminal.docker_volumes names a host path, do not treat the docker dry-run as the runtime verdict. The test command has no mount flag. The guard used by the terminal tool keeps the floor and the ordinary prompt when that host-access bit is true. The default in this checkout leaves the bit false, which is why a stock docker dry-run and a stock docker guard agreed on reboot. Agreement on the default is not agreement after you mount a directory.
modal is the case where a host-access bit did not matter. The dry-run allowed reboot, and the guard allowed reboot even with the bit set. If the backend is modal, a volume note in some other config file will not restore the floor that --env-type modal skipped. Write the deny rule if the command must not run.
ssh stayed on the local floor. Do not add --env-type docker to an ssh review and then delete the hardline deny from your notes. The env type is the backend the guard will use, not a label you can borrow to see the strictest answer.
The security page is still the right page for yolo, mode off, and cron approve mode on a local or ssh backend. It is the wrong page if the question is whether a docker dry-run will keep reboot on the floor. This binary’s test command will not, unless a deny rule matches. This binary’s docker guard will not either, unless a host path is mounted or that same deny rule matches. Read the detail line. Then read the mount.
Sources
- Hermes Agent v0.22.5+471.g5894e75, commit
5894e750c3,hermes approvals testhandler, Linux x86_64, 3 November 2026. DisposableHERMES_HOME. No container, no login, no model turn. - The same checkout’s security page, hardline floor and
approvals.deny, compared with the project page read the same day: Security. hermes approvals test --helpon that binary, andcheck_all_command_guardswith an explicit host-access bit. No Docker daemon call.