Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
2e44620
chore(openshell): prepare 0.0.82 dependency upgrade
ericksoa Jul 12, 2026
225e154
fix(mcp): align credential diagnostics with OpenShell main
ericksoa Jul 12, 2026
2bd7c7c
docs(mcp): narrow CONNECT 503 diagnosis
ericksoa Jul 12, 2026
3a67bc3
chore(openshell): prepare 0.0.82 dependency upgrade
ericksoa Jul 12, 2026
9213896
fix(mcp): align credential diagnostics with OpenShell main
ericksoa Jul 12, 2026
1fc5e39
docs(mcp): narrow CONNECT 503 diagnosis
ericksoa Jul 12, 2026
6e90439
feat(exec): support multiline OpenShell commands
ericksoa Jul 13, 2026
a271c66
fix(install): validate OpenShell archives before extraction
ericksoa Jul 13, 2026
06ec576
docs(security): audit OpenShell 0.0.82 migration
ericksoa Jul 13, 2026
18189eb
fix(ci): validate trusted OpenShell release upgrades
ericksoa Jul 12, 2026
12a6b1d
fix(ci): require exact OpenShell release asset sets
ericksoa Jul 12, 2026
ece87f2
fix(ci): bind OpenShell hashes to runtime selectors
ericksoa Jul 12, 2026
36dda9a
docs(security): bind OpenShell dev artifact evidence
ericksoa Jul 13, 2026
5e5fe4b
test(exec): cover carriage-return command arguments
ericksoa Jul 13, 2026
441e0c8
test(install): model archive validation in curl fallback
ericksoa Jul 13, 2026
3658555
docs(security): require trusted upgrade bootstrap
ericksoa Jul 13, 2026
14b7560
refactor(exec): remove obsolete newline transports
ericksoa Jul 13, 2026
20feff9
fix(ci): trust complete OpenShell installer templates
ericksoa Jul 13, 2026
d78caf7
docs(security): correct OpenShell connection test audit
ericksoa Jul 13, 2026
14c246a
test(exec): exercise native multiline transports
ericksoa Jul 13, 2026
ecf1f9f
refactor(exec): remove remaining newline transports
ericksoa Jul 13, 2026
5fe4558
fix(runtime): reject supervisor TLS identity in children
ericksoa Jul 13, 2026
6e27baa
fix(ci): migrate OpenShell version bounds together
ericksoa Jul 13, 2026
b884b7a
chore(openshell): reconcile protected upgrade history
ericksoa Jul 13, 2026
f3833f5
test(install): assert safe archive extraction
ericksoa Jul 13, 2026
3e338a3
test(runtime): isolate supervisor identity assertions
ericksoa Jul 13, 2026
3788f39
test(e2e): refresh exact OpenShell main proof
ericksoa Jul 13, 2026
aef77e1
test(e2e): require the complete MCP agent matrix
ericksoa Jul 13, 2026
ecb9863
test(e2e): prove OpenShell child launch boundaries
ericksoa Jul 13, 2026
a85153f
test(e2e): prove OpenShell runtime contracts
ericksoa Jul 13, 2026
98e7aa2
test(e2e): prove OpenShell credential window
ericksoa Jul 13, 2026
217e440
docs(security): close OpenShell migration audit gaps
ericksoa Jul 13, 2026
d893377
test(e2e): prove OpenShell driver config migration
ericksoa Jul 13, 2026
8f82864
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
2f03477
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
66359f8
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
5074db2
chore(openshell): upgrade stable pin to v0.0.82
ericksoa Jul 13, 2026
7a86d7a
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
5ec6b6d
test(openshell): keep release-data trust extensible
ericksoa Jul 13, 2026
44bce49
test(exec): avoid environment-derived shell path
ericksoa Jul 13, 2026
a765301
test(openshell): keep migration fixtures linear
ericksoa Jul 13, 2026
b016b6d
test(openshell): align upgrade fixtures with 0.0.82
ericksoa Jul 13, 2026
acac53e
ci(e2e): constrain exact-main artifact downloads
ericksoa Jul 13, 2026
6467eb9
test(openshell): keep stale recovery fixture current
ericksoa Jul 13, 2026
a128d8b
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
5b7db6a
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
f7c3ab3
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
acdb3a4
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
ddf425a
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
d6f746e
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
d457b47
test(e2e): prove OpenShell v0.0.82 release
ericksoa Jul 13, 2026
cf92c03
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
f2c8cd1
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
2c4f38e
test(e2e): keep release proof on stable lane
ericksoa Jul 13, 2026
5a67f7f
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
18255c7
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 13, 2026
81895e0
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 14, 2026
7a0d811
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 15, 2026
7cd6a2b
test(e2e): deduplicate OpenShell parity mapping
ericksoa Jul 15, 2026
662638d
Merge remote-tracking branch 'origin/main' into dep/openshell-v0.0.82…
ericksoa Jul 15, 2026
218507a
fix(e2e): shard OpenShell upgrade evidence
ericksoa Jul 15, 2026
33ee348
merge(main): resolve OpenShell 0.0.82 review conflicts
prekshivyas Jul 15, 2026
f13e841
test(openshell): repair 0.0.82 migration gates
prekshivyas Jul 15, 2026
670e777
Merge remote-tracking branch 'origin/main' into codex/pr-6726-fixes
prekshivyas Jul 15, 2026
df76e19
test(e2e): cover immediate OpenShell upgrade predecessor
prekshivyas Jul 15, 2026
2dfe4da
merge(main): refresh OpenShell 0.0.82 migration
prekshivyas Jul 16, 2026
7a9d754
Merge branch 'main' into dep/openshell-v0.0.82-6379
cv Jul 16, 2026
428b8fa
fix(e2e): use controller-supported upgrade matrix
apurvvkumaria Jul 16, 2026
ff3abcf
merge(main): refresh OpenShell 0.0.85 migration
cjagwani Jul 17, 2026
654c3fd
chore(deps): upgrade OpenShell to 0.0.85
cjagwani Jul 17, 2026
76d22d2
merge(main): refresh OpenShell 0.0.85 migration
cjagwani Jul 17, 2026
b7ef7f8
Merge remote-tracking branch 'origin/main' into codex/openshell-v0.0.…
cjagwani Jul 17, 2026
9fded41
fix(openshell): repair v0.0.85 migration gates
cjagwani Jul 17, 2026
dbac1de
Merge remote-tracking branch 'origin/main' into codex/openshell-v0.0.…
cjagwani Jul 17, 2026
5c12cd7
docs(platform): repair OpenShell installer citation
cjagwani Jul 17, 2026
8971b66
Merge branch 'main' into dep/openshell-v0.0.82-6379
cjagwani Jul 17, 2026
0f15443
test(mcp): enforce credential validator parity
apurvvkumaria Jul 17, 2026
610f7cd
merge(main): integrate PR #7046 into OpenShell migration
cjagwani Jul 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ jobs:
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openshell-gateway-auth-contract
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_OPENSHELL_PIN_VERSION: "0.0.72"
NEMOCLAW_OPENSHELL_PIN_VERSION: "0.0.85"
DOCKER_GRPC_PROBE_IMAGE: "node:22-trixie-slim@sha256:2d9f5c76c8f4dd36e8f253bee5d828a83a6c09f36188f0b0414325232e0b175d"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand Down Expand Up @@ -634,7 +634,9 @@ jobs:
NEMOCLAW_E2E_SHARD: ${{ matrix.agent }}
NEMOCLAW_MCP_BRIDGE_AGENT: ${{ matrix.agent }}
NEMOCLAW_OPENSHELL_CHANNEL: stable
NEMOCLAW_OPENSHELL_EXACT_MAIN_PROOF: "1"
NEMOCLAW_RUN_LIVE_E2E: "1"
OPENSHELL_DOCKER_SUPERVISOR_IMAGE: ghcr.io/nvidia/openshell/supervisor@sha256:f4226253a3525c3832adac5b38b419a0f27d1e915effe565b5885e20f93cd5e9
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
Expand Down Expand Up @@ -678,6 +680,20 @@ jobs:
run: |
set -euo pipefail
bash scripts/install-openshell.sh
if [[ "$NEMOCLAW_MCP_BRIDGE_AGENT" == "deepagents" ]]; then
mkdir -p "$E2E_ARTIFACT_DIR/mcp-bridge-deepagents"
jq -n '{
schemaVersion: 1,
sourceRepository: "NVIDIA/OpenShell",
releaseTag: "v0.0.85",
sourceSha: "3dee5570a46076a57a3b056f35f35ebc0861ac85",
artifacts: {
cli: {binarySha256: "222d9d53a142691d7a7de2c692f38e52d24066f9f633d53746c5fef775861bc8"},
gateway: {binarySha256: "33bb479d936c3c1b17dd475df05747be9de74564fb67d69a4c33cdd01181d02f"},
standaloneSandbox: {binarySha256: "863ef21ab7ef623f5e7a8728c4e5532b46bfbae3ace3b800665a1c6353a1f7d2"}
}
}' > "$E2E_ARTIFACT_DIR/mcp-bridge-deepagents/openshell-exact-main-provenance.json"
fi

- name: Run MCP OpenShell provider live test
run: |
Expand All @@ -694,6 +710,13 @@ jobs:
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/mcp-bridge.test.ts
if [[ "$NEMOCLAW_MCP_BRIDGE_AGENT" == "deepagents" ]]; then
npx vitest run --project e2e-live \
test/e2e/live/openshell-credential-generation-window.test.ts \
-t '^openshell-credential-generation-window$' \
--no-file-parallelism \
--silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts
fi

- id: mcp_artifact_secret_scan
name: Scan MCP artifacts for fixture credentials
Expand Down Expand Up @@ -3575,6 +3598,15 @@ jobs:
sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:10433a8cd2f2b809dd0fdf983514679e04c0f8aa1ff5bbff675029046033b108"
openshell_version: 0.0.44
openclaw_version: 2026.5.22
- id: v0.0.74-x86_64
runner: ubuntu-latest
shard: v0-0-74-x86-64
nemoclaw_ref: v0.0.74
nemoclaw_commit: "3a05b54e8ec3e1d5550ec5c728de54af872bffe3"
installer_sha256: "a0cd3feca488d247e53d59d7d8246d2b86e75e95acb5e7d78504b3c0c60fd7db"
sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:104151ffadc2ff0b6c815e3c95c2783ced61aee0d0f83fc327cc02be9b7e14e6"
openshell_version: 0.0.72
openclaw_version: 2026.5.27
env:
E2E_JOB: "1"
E2E_TARGET_ID: "openshell-gateway-upgrade"
Expand Down Expand Up @@ -3605,7 +3637,8 @@ jobs:

- name: Run OpenShell gateway upgrade live Vitest test
# Keep the original v0.0.36 fixture on x86_64 and validate the exact
# v0.0.55/OpenShell 0.0.44 regression shape on x86_64 and arm64.
# v0.0.55/OpenShell 0.0.44 regression shape on x86_64 and arm64, plus
# the immediate v0.0.74/OpenShell 0.0.72 predecessor to this bump.
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
Expand Down
8 changes: 4 additions & 4 deletions agents/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ COPY agents/hermes/runtime-config-guard.py /usr/local/lib/nemoclaw/hermes-runtim
COPY agents/hermes/finalize-tirith-marker.py /usr/local/lib/nemoclaw/finalize-tirith-marker.py
COPY agents/hermes/build-mcp-digest.py /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py
COPY agents/hermes/mcp-config-transaction.py /usr/local/lib/nemoclaw/hermes-mcp-config-transaction.py
COPY src/lib/actions/sandbox/openshell-child-visible-credentials.v0.0.72.json /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.72.json
COPY src/lib/actions/sandbox/openshell-child-visible-credentials.v0.0.85.json /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json
COPY scripts/state-dir-guard.py /usr/local/lib/nemoclaw/state-dir-guard.py
COPY nemoclaw-blueprint/scripts/*.js /usr/local/lib/nemoclaw/preloads/
# Dockerfile.base is the source of truth for rlimit hooks. This Hermes replay
Expand All @@ -143,11 +143,11 @@ COPY nemoclaw-blueprint/scripts/*.js /usr/local/lib/nemoclaw/preloads/
# minimum supported Hermes sandbox base tag guarantees those artifacts and
# test/sandbox-rlimit-hooks.test.ts covers that base.
RUN chmod 755 /usr/local/bin/nemoclaw-start /usr/local/lib/nemoclaw/sandbox-init.sh /usr/local/lib/nemoclaw/validate-hermes-env-secret-boundary.py /usr/local/lib/nemoclaw/patch-hermes-session-list-preview.py /usr/local/lib/nemoclaw/seed-hermes-dashboard-config.py /usr/local/lib/nemoclaw/hermes-runtime-config-guard.py /usr/local/lib/nemoclaw/finalize-tirith-marker.py /usr/local/lib/nemoclaw/hermes-mcp-config-transaction.py \
&& chown root:root /usr/local/bin/nemoclaw-gateway-control /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/lib/nemoclaw/managed-gateway-control.py /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.72.json \
&& chown root:root /usr/local/bin/nemoclaw-gateway-control /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/lib/nemoclaw/managed-gateway-control.py /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json \
&& chmod 700 /usr/local/bin/nemoclaw-gateway-control \
&& chmod 500 /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/lib/nemoclaw/managed-gateway-control.py \
&& chmod 444 /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py \
&& chmod 444 /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.72.json \
&& chmod 444 /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json \
&& if [ -d /usr/local/lib/nemoclaw/preloads ]; then \
chown -R 0:0 /usr/local/lib/nemoclaw/preloads \
&& find /usr/local/lib/nemoclaw/preloads -type f -exec chmod 444 {} + \
Expand Down Expand Up @@ -207,7 +207,7 @@ PY
# file has to also rewrite the Dockerfile-committed hash, which reviewers gate).
# Regenerate with `sha256sum agents/hermes/{hermes-wrapper.py,validate-env-secret-boundary.py,finalize-tirith-marker.py}`.
ARG NEMOCLAW_HERMES_WRAPPER_SHA256=ec8b0e4d6254175929d5a02240acd6af25e94774a30b36ba5c6f5a69c32fb9d7
ARG NEMOCLAW_HERMES_VALIDATOR_SHA256=970d7ff03bc409ff1d5ca46bfdbd2a42ac28a32a810ccc147a508301bff38496
ARG NEMOCLAW_HERMES_VALIDATOR_SHA256=d7371a84099b204346e0bfecfacb0086fdab690e5fadcf5e12417b6038927d19
ARG NEMOCLAW_HERMES_TIRITH_FINALIZER_SHA256=a1e6b1c53ab297569abb87c29d15c294d729e46005bfd022136b4c447a791819
# hadolint ignore=DL4006
RUN printf '%s %s\n' \
Expand Down
17 changes: 11 additions & 6 deletions agents/hermes/mcp-config-transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
ENV_PLACEHOLDER_RE = re.compile(
r"^Bearer openshell:resolve:env:([A-Za-z_][A-Za-z0-9_]{0,127})$"
)
BOUNDARY_MANIFEST_NAME = "openshell-child-visible-credentials.v0.0.72.json"
OPENSHELL_REVISIONED_CREDENTIAL_NAME_RE = re.compile(r"^v[0-9]+_[A-Za-z0-9_]+$")
BOUNDARY_MANIFEST_NAME = "openshell-child-visible-credentials.v0.0.85.json"
ANSI_ESCAPE_RE = re.compile(
r"\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\)|[@-_])"
)
Expand Down Expand Up @@ -116,7 +117,7 @@ def _load_credential_boundary_manifest() -> dict[str, object]:
# corrupt, or wrong-version OpenShell boundary manifest.
# sourceBoundary: NemoClaw owns one reviewed manifest installed beside this
# helper in images; the second path is the deterministic source-checkout layout.
# whyNotSourceFix: OpenShell v0.0.72 has no machine-readable child-env contract.
# whyNotSourceFix: OpenShell v0.0.85 has no machine-readable child-env contract.
# It also deliberately hides the supervisor identity mount from workload
# children and the Hermes image contains no OpenShell CLI. Executing
# ``openshell --version`` here would therefore either fail every real
Expand All @@ -142,7 +143,10 @@ def _load_credential_boundary_manifest() -> dict[str, object]:
if manifest_path is None:
raise RuntimeError("Hermes MCP credential boundary manifest is missing")
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
if not isinstance(manifest, dict) or manifest.get("openshellVersion") != "0.0.72":
if (
not isinstance(manifest, dict)
or manifest.get("openshellVersion") != "0.0.85"
):
raise RuntimeError("Hermes MCP credential boundary manifest is invalid")
return manifest

Expand Down Expand Up @@ -175,7 +179,8 @@ def _manifest_strings(manifest: dict[str, object], key: str) -> frozenset[str]:

def _credential_name_is_reserved(name: str) -> bool:
return (
name in _RAW_CHILD_VALUE_KEYS
OPENSHELL_REVISIONED_CREDENTIAL_NAME_RE.fullmatch(name) is not None
or name in _RAW_CHILD_VALUE_KEYS
or name in _REWRITTEN_CHILD_VALUE_KEYS
or name in _RUNTIME_CONTROL_KEYS
or any(name.startswith(prefix) for prefix in _RUNTIME_CONTROL_PREFIXES)
Expand Down Expand Up @@ -324,7 +329,7 @@ def _validate_payload(action: str, payload: dict[str, object]) -> None:
}
if action == "add" and hostname in host_aliases:
raise ValueError(
"Authenticated MCP OpenShell host aliases are unavailable with OpenShell v0.0.72"
"Authenticated MCP OpenShell host aliases are unavailable with OpenShell v0.0.85"
)
if not (action == "remove" and hostname in host_aliases) and (
hostname in {"localhost", "local", "internal", "metadata"}
Expand Down Expand Up @@ -1068,7 +1073,7 @@ def _assert_non_root_lifecycle_identity() -> None:
# topology.
# sourceBoundary: OpenShell owns workload topology; NemoClaw owns the
# immutable root-lifecycle marker and validates it before mutation.
# whyNotSourceFix: OpenShell 0.0.72 supports both topologies but exposes no
# whyNotSourceFix: OpenShell 0.0.85 supports both topologies but exposes no
# attested same-UID capability that this packaged helper can query.
# regressionTest: hermes-mcp-config-transaction.test.ts rejects both probe
# and add when the root-lifecycle marker identifies the legacy topology.
Expand Down
2 changes: 1 addition & 1 deletion agents/hermes/runtime-config-guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ def _pinned_process_matches_supervised_nonroot_start(
supervisor_identity: tuple[str, int | None],
expected_effective_uid: int,
) -> bool:
# OpenShell 0.0.72 keeps its supervisor at PID 1 and launches the non-root
# OpenShell 0.0.85 keeps its supervisor at PID 1 and launches the non-root
# NemoClaw entrypoint as a child, so startup authority must be proved from
# pinned procfs identity rather than a PID-1 equality check. Remove this
# compatibility proof when #6256 provides authenticated supervisor/runtime
Expand Down
2 changes: 1 addition & 1 deletion agents/hermes/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3062,7 +3062,7 @@ fi
# while Hermes actually runs in the legacy root-separated topology.
# sourceBoundary: OpenShell owns workload topology; NemoClaw owns the immutable
# root-lifecycle marker and stamps it before starting the root-separated gateway.
# whyNotSourceFix: OpenShell 0.0.72 supports both topologies but exposes no
# whyNotSourceFix: OpenShell 0.0.85 supports both topologies but exposes no
# attested same-UID capability that this packaged entrypoint can query.
# regressionTest: hermes-mcp-config-transaction.test.ts rejects both probe and
# add when the root-lifecycle marker identifies the legacy topology.
Expand Down
34 changes: 22 additions & 12 deletions agents/hermes/validate-env-secret-boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@
}
)
RUNTIME_ALLOWED_RAW_SECRET_KEYS = frozenset({"OPENCLAW_GATEWAY_TOKEN"})
# OpenShell's Docker/Podman supervisor owns this variable and injects a mounted
# file path, not private-key material. Keep the allowance exact and runtime-only
# so a caller cannot use the secret-shaped name to smuggle an arbitrary value or
# persist it in Hermes' mutable .env file.
RUNTIME_ALLOWED_PLATFORM_PATH_VALUES = frozenset(
{("OPENSHELL_TLS_KEY", "/etc/openshell/tls/client/tls.key")}
# OpenShell 8eacb477 (candidate 0.0.85) makes these supervisor-only identity
# variables and removes them from entrypoint, exec, and connect children. Their
# presence in Hermes is therefore contract drift even when the value is only a
# mounted path.
OPENSHELL_SUPERVISOR_ONLY_ENV_KEYS = frozenset(
{"OPENSHELL_TLS_CA", "OPENSHELL_TLS_CERT", "OPENSHELL_TLS_KEY"}
)
ALLOWED_LITERALS = frozenset({"", "[STRIPPED_BY_MIGRATION]"})
MAX_ENV_BYTES = 4 * 1024 * 1024
Expand Down Expand Up @@ -451,6 +451,11 @@ def validate_env_file(path: str) -> int:
key = key.strip()
if not KEY_NAME_RE.fullmatch(key):
continue
if key in OPENSHELL_SUPERVISOR_ONLY_ENV_KEYS:
violation_count += 1
if len(violations) < MAX_VIOLATIONS:
violations.append(f"{key} (line {lineno})")
continue
if key in ENV_FILE_ALLOWED_NONSECRET_KEYS:
continue
if key in ENV_FILE_ALLOWED_RAW_SECRET_KEYS and is_allowed_raw_secret_value(
Expand All @@ -468,8 +473,9 @@ def validate_env_file(path: str) -> int:
return 0
_emit_violations(
"[SECURITY] Refusing Hermes startup because /sandbox/.hermes/.env "
"contains raw secret-shaped values. Store credentials in OpenShell "
"providers and keep only openshell resolver placeholders in the sandbox.",
"contains raw secret-shaped values or OpenShell supervisor-only identity "
"variables. Store credentials in OpenShell providers and keep only "
"openshell resolver placeholders in the sandbox.",
violations,
violation_count - len(violations),
)
Expand All @@ -481,14 +487,17 @@ def validate_runtime_env(env: dict[str, str] | None = None) -> int:
violations: list[str] = []
violation_count = 0
for key, value in sorted(source.items()):
if key in OPENSHELL_SUPERVISOR_ONLY_ENV_KEYS:
violation_count += 1
if len(violations) < MAX_VIOLATIONS:
violations.append(key)
continue
if key in RUNTIME_ALLOWED_NONSECRET_KEYS:
continue
if key in RUNTIME_ALLOWED_RAW_SECRET_KEYS and is_allowed_raw_secret_value(
key, value
):
continue
if (key, value) in RUNTIME_ALLOWED_PLATFORM_PATH_VALUES:
continue
if not KEY_NAME_RE.fullmatch(key):
continue
if not SECRET_KEY_RE.search(key):
Expand All @@ -502,8 +511,9 @@ def validate_runtime_env(env: dict[str, str] | None = None) -> int:
return 0
_emit_violations(
"[SECURITY] Refusing Hermes startup because the process environment "
"contains raw secret-shaped values. Store credentials in OpenShell "
"providers and keep only openshell resolver placeholders in the sandbox.",
"contains raw secret-shaped values or OpenShell supervisor-only identity "
"variables. Store credentials in OpenShell providers and keep only "
"openshell resolver placeholders in the sandbox.",
violations,
violation_count - len(violations),
)
Expand Down
29 changes: 17 additions & 12 deletions agents/langchain-deepagents-code/dcode-wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ unset PYTHONHOME PYTHONPATH
readonly DEEPAGENTS_ENV_FILE="/sandbox/.deepagents/.env"
readonly OPENSHELL_ENV_PLACEHOLDER_PREFIX="openshell:resolve:env:"
readonly DEEPAGENTS_CONFIG_FILE="/sandbox/.deepagents/config.toml"
readonly OPENSHELL_TLS_KEY_PATH="/etc/openshell/tls/client/tls.key"
readonly DEEPAGENTS_AUTH_FILE="/sandbox/.deepagents/.state/auth.json"
readonly DEEPAGENTS_CODEX_AUTH_FILE="/sandbox/.deepagents/.state/chatgpt-auth.json"
readonly MANAGED_DCODE_AUTO_APPROVAL_FILE="/usr/local/share/nemoclaw/dcode-auto-approval"
Expand Down Expand Up @@ -351,14 +350,15 @@ has_credential_name_context() {
return 1
}

# SECURITY: OpenShell's supervisor injects this mounted TLS key path into the
# runtime environment. Allow only the exact name/value pair after the generic
# value scan. Never allow the name alone, and never apply this exception to the
# mutable Deep Agents Code .env file.
is_allowed_openshell_runtime_value() {
local name="$1"
local value="$2"
[ "$name" = "OPENSHELL_TLS_KEY" ] && [ "$value" = "$OPENSHELL_TLS_KEY_PATH" ]
# OpenShell 8eacb477 (candidate 0.0.85) strips these supervisor identity
# variables from entrypoint, exec, and connect children. Reject their presence
# regardless of value so a runtime regression cannot silently expose mounted
# mTLS identity to dcode.
is_openshell_supervisor_only_env_name() {
case "$1" in
OPENSHELL_TLS_CA | OPENSHELL_TLS_CERT | OPENSHELL_TLS_KEY) return 0 ;;
esac
return 1
}

# OTLP endpoint variables carry the collector URL, not a credential. The
Expand Down Expand Up @@ -440,8 +440,7 @@ is_openshell_env_placeholder_for_name() {
local canonical revision_prefix revision_suffix versioned revision

# OPENSHELL_TLS_KEY is supervisor infrastructure, not a provider credential.
# Only its exact mounted path is accepted from the runtime environment below;
# never let a provider placeholder bypass that name/value allowlist.
# Never let a provider placeholder bypass that supervisor-only boundary.
[ "$name" != "OPENSHELL_TLS_KEY" ] || return 1

# Keep this identifier contract aligned with OpenShell provider env keys.
Expand Down Expand Up @@ -506,6 +505,9 @@ assert_no_secret_runtime_env() {
name="${pair%%=*}"
[ "$name" != "$pair" ] || continue
value="${pair#*=}"
if is_openshell_supervisor_only_env_name "$name"; then
refuse_secret_env "runtime environment variable" "$name"
fi
if [[ "$value" == *"$OPENSHELL_ENV_PLACEHOLDER_PREFIX"* ]]; then
if is_openshell_env_placeholder_for_name "$name" "$value"; then
continue
Expand All @@ -526,7 +528,7 @@ assert_no_secret_runtime_env() {
fi
continue
fi
if has_credential_name_context "$name" && [ ${#value} -ge 10 ] && ! is_allowed_openshell_runtime_value "$name" "$value"; then
if has_credential_name_context "$name" && [ ${#value} -ge 10 ]; then
refuse_secret_env "runtime environment variable" "$name"
fi
done < <(env -0)
Expand Down Expand Up @@ -578,6 +580,9 @@ assert_no_secret_env_file() {
;;
esac
value="$(trim_whitespace "$value")"
if is_openshell_supervisor_only_env_name "$key"; then
refuse_secret_env "$env_file" "$key"
fi
if is_dynamic_dotenv_value "$value"; then
refuse_dynamic_env "$env_file" "$key"
fi
Expand Down
Loading
Loading