NVIDIA / NVIDIA/NemoClaw

[Security] Hermes startup silently reprovisions state instead of refusing when /sandbox/.hermes is replaced by a symlink

Open
#11,380 0 comments 0 reactions 0 assignees View on GitHub
area: sandbox area: security integration: hermes platform: ubuntu
Dominant language
TypeScript
Stars
22.5k
Forks
3.1k
Avg merge
1d 1h
Merged PRs (30d)
715

Description

## Description

Hermes startup does not refuse or warn when `/sandbox/.hermes` (the trusted Hermes configuration/state root) has been replaced by a symlink pointing to an untrusted external location. Instead it silently discards the tampered path and reprovisions a completely fresh `.hermes` directory, with no error, no security-refusal message, and no operator-visible warning. This is inconsistent with the equivalent mode-permission check on the same root, which does correctly refuse startup with an explicit message.

Related: #4957 (closed) covers a different specific trigger — a raw secret-shaped value injected into `.env` — but the same general gap (a mutated-trust-boundary condition on Hermes startup state that doesn't trigger a `[SECURITY]` refusal). This issue is about the configuration-root symlink case specifically.

Platform scope: Reproduced on Ubuntu 24.04 x86_64 only; other platforms not tested.
Regression: Unknown — earlier versions not tested.

## Environment

```text
Device: Ubuntu 24.04 x86_64 host (Docker driver)
OS: Ubuntu 24.04
Architecture: x86_64
NemoClaw: v0.0.121
```

## Steps to Reproduce

1. Onboard a Ready NemoHermes sandbox: `nemoclaw onboard --agent hermes --name hermes-sec2`
2. Stop the sandbox: `nemohermes hermes-sec2 stop`
3. As root inside the container, replace the trusted root with a symlink to an untrusted location:
```bash
docker exec -u 0 sh -c 'mv /sandbox/.hermes /sandbox/.hermes.case-backup; ln -s /tmp/hermes-root-external-case /sandbox/.hermes'
```
4. Start the sandbox: `nemohermes hermes-sec2 start`

## Expected Result

Start should exit non-zero with a security refusal identifying an unsafe linked Hermes configuration root, matching the behavior already implemented for the equivalent untrusted-mode-bits check on the same `/sandbox/.hermes` root. For comparison, that sibling scenario (root ownership/mode tampered instead of symlinked) produces this in `docker logs`:

```text
[SECURITY] Refusing Hermes startup because /sandbox/.hermes does not match a trusted owner/group/mode posture
```
followed by exit code 1.

## Actual Result

Start exits 0 and reports success (status ready, gateway healthy, probe `result=ready`). No `[SECURITY]`-prefixed message appears in `docker logs` for this startup attempt. The tampered symlink at `/sandbox/.hermes` is gone afterward (`test -L` reports not-a-symlink) and a brand-new `.hermes` directory has been silently provisioned in its place (fresh `config.yaml`, fresh `sessions` dir, fresh timestamps), discarding both the untrusted redirect and the sandbox operator's prior state/history with no warning that this happened.

The external sentinel file placed at the untrusted target was not read/modified (confirmed via unchanged sha256), so this is not a follow-the-symlink data-exposure bug — the untrusted target itself was never trusted. But the fail-closed-with-explanation contract that the mode-permission check enforces on the exact same path is not applied to the symlink case: instead of refusing and telling the operator what happened, the product silently discards their sandbox state.

## Logs

```text
docker logs (relevant excerpt): no SECURITY-prefixed line at all for this startup attempt, contrasted with the sibling mode-0755 test on the same host/build, which printed:
[SECURITY] Refusing Hermes startup because /sandbox/.hermes does not match a trusted owner/group/mode posture
followed by exit code 1. The symlink-replacement startup instead completed the full non-interactive onboarding probe successfully (result=ready) with no equivalent line.
```

Contributor guide

Open the contributing guide

Research direction

Reproduce the case with `nemoclaw onboard`, `nemohermes hermes-sec2 stop`, the documented `docker exec` symlink replacement, and `nemohermes hermes-sec2 start`; compare its logs and exit status with the mode-permission scenario. Trace Hermes startup validation for `/sandbox/.hermes`; done means startup exits non-zero with a `[SECURITY]` refusal identifying the unsafe linked root instead of silently reprovisioning state.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, typescript
Domain
infrastructure, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.