security: verify runtime OpenShell version against credential boundary manifest
- Dominant language
- TypeScript
- Stars
- 22.5k
- Forks
- 3.1k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 715
Description
## Source
Identified in [PR #5876 — feat(mcp): add OpenShell-managed MCP servers](https://github.com/NVIDIA/NemoClaw/pull/5876), specifically the [PR Review Advisor findings](https://github.com/NVIDIA/NemoClaw/pull/5876#issuecomment-4812614670).
## Problem
The credential boundary manifest `openshell-child-visible-credentials.v0.0.72.json` is baked for OpenShell 0.0.72 and supplies the deny-lists used to protect child-visible credentials. The TypeScript and Python consumers validate the manifest structure, but neither verifies that the running `openshell` binary matches `manifest.openshellVersion`. The Hermes image and startup path also lack an equivalent runtime gate.
A sandbox can therefore use credential-boundary rules for OpenShell 0.0.72 while actually running a newer or older OpenShell after an image, installer, or manual runtime change.
## Findings consolidated
### PRA-4 — TypeScript validation boundary
Affected paths:
- `src/lib/actions/sandbox/mcp-bridge-validation.ts`
- `src/lib/security/mcp-url-target.ts`
The manifest is imported and its `runtimeControlKeys`, `runtimeControlPrefixes`, `rawChildValueKeys`, and `rewrittenChildValueKeys` are used without executing `openshell --version`.
### PRA-5 — Python Hermes transaction boundary
Affected path:
- `agents/hermes/mcp-config-transaction.py`
`_load_credential_boundary_manifest()` checks the baked manifest version but never compares it with the installed OpenShell binary.
### PRA-6 — Hermes runtime/startup boundary
Affected paths:
- `agents/hermes/Dockerfile`
- `agents/hermes/start.sh`
- `agents/hermes/mcp-config-transaction.py`
The Dockerfile checks MCP Python imports at build time, but Hermes startup does not verify the runtime OpenShell version before using the credential deny-list.
## Security impact
When the manifest and runtime diverge:
- newly reserved OpenShell credential names may be accepted and exposed across the child credential boundary;
- names that are legitimate for the running version may be rejected;
- the credential isolation contract silently depends on stale metadata.
## Required behavior
- Execute and parse `openshell --version` in the TypeScript credential-validation path.
- Execute and parse `subprocess.run(["openshell", "--version"], capture_output=True, text=True)` in the Python/Hermes path.
- Compare the parsed version exactly with `manifest.openshellVersion`.
- Fail closed when the binary is missing, exits unsuccessfully, produces unparseable output, or reports a different version.
- Ensure the Hermes check runs for every relevant startup. If the Python transaction is guaranteed to execute before credential configuration on every startup, it may satisfy the startup boundary; otherwise add an entrypoint check.
- A short-lived cache may be used in frequently invoked TypeScript validation, but it must not allow a stale result to survive a runtime change indefinitely.
- Error output should identify expected versus actual version without exposing credential values.
## Regression coverage
- Add TypeScript tests in `src/lib/actions/sandbox/mcp-bridge-input-validation.test.ts` for:
- matching runtime and manifest versions;
- runtime version 0.0.73 with a 0.0.72 manifest;
- missing binary;
- non-zero exit;
- unparseable version output.
- Add equivalent Python transaction coverage in `test/hermes-mcp-config-transaction.test.ts`.
- Add a Hermes runtime/E2E test in `test/e2e/live/rebuild-hermes.test.ts` that substitutes a mismatched OpenShell version and proves startup or credential configuration fails closed.
## Acceptance criteria
- TypeScript and Hermes/Python credential-boundary paths verify the actual runtime OpenShell version before applying manifest deny-lists.
- Exact matches proceed normally.
- Mismatch, missing binary, command failure, and parse failure are rejected before credentials are written or the affected runtime starts.
- Unit tests cover both implementations.
- Hermes runtime coverage proves the check cannot be bypassed by starting an image with a different OpenShell binary.
Contributor guide
Assessment
This issue has not been assessed yet.