Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 11 additions & 9 deletions docs/security/openshell-0.0.85-migration-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ Commits: `80293213`, `392ad639`, `b4be33e5`, `21aaa895`, `3dee5570`.
| `OS85-10` | Medium-high | Supervisor TLS identity variables are no longer child environment. Stale tests/comments can normalize a credential leak. | Assert absence from entrypoint, exec, and connect children and update the source-of-truth rationale. | Hermes and Deep Agents now reject all three variables; the stable entrypoint, exec, and connect probes require their absence, with exact-head execution pending. |
| `OS85-11` | Medium-high | Live `/proc/<pid>/exe` identity changes replacement-time policy behavior. | Prove old process survives replacement and a new altered process at the same path is denied. | The stable release proof runs both processes against the real proxy and requires old=200 before/after replacement, distinct live/path hashes, and new=403; exact-head runtime result pending. |
| `OS85-12` | Medium | OpenShell declares Docker 28.0+ while #6379 is on Docker 27 and NemoClaw marks DGX Spark tested. | Either validate and document a precise downstream exception from physical proof or raise the supported floor and preflight it. | Open product/platform decision. |
| `OS85-13` | Low | Mount parsing/SELinux changes could affect the test-only tmpfs path. | Rerun the EXDEV tmpfs fixture and retain production no-mount evidence. | The stable release proof injects only the reviewed tmpfs config, requires Docker's structured tmpfs representation plus `noexec`/01777 at runtime, retains it across gateway restart, and requires a fresh remount after rebuild. The wrapper is disabled outside the explicit proof lane and production still supplies no driver mounts. Exact-head, Podman, and enforcing-SELinux results remain open. |
| `OS85-13` | Low | Mount parsing/SELinux changes could affect the test-only tmpfs path. | Rerun the EXDEV tmpfs fixture and retain production no-mount evidence. | The stable release proof injects only the reviewed tmpfs config, requires Docker's structured tmpfs representation plus `noexec`/01777 at runtime, requires an empty remount after graceful gateway restart with the same container/config/auth and retained durable state, and requires another fresh remount after rebuild. The wrapper is disabled outside the explicit proof lane and production still supplies no driver mounts. Exact-head, Podman, and enforcing-SELinux results remain open. |
| `OS85-14` | Low | Sanitized MCP tool names are newly present in logs. | Record the additive observability/privacy behavior; ensure no downstream parser assumes the old shape. | The stable release check requires the real `fake_echo` tool name and rejects argument/result canaries or an `arguments` field in JSON-RPC policy logs; exact-head runtime result pending. |
| `OS85-15` | High | The installer-hash workflow executes its checker and parser from the PR base SHA. One PR cannot safely teach that trusted base about a new release and consume the release; using the head checker would let reviewed code define its own trust rules. | First land archive safety, normalized full-script template validation, and multi-release trust while selectors remain `0.0.72`; prove the old base rejects a new release and the new base permits only structured release-data changes; then land the exact `0.0.85` manifest identities before refreshing this selector PR. | Base trust landed in #7069. #6778 and #6779 established base-owned structured manifest and sandbox-map validation; #7069 added only the three exact `0.0.85` release identities while retaining `0.0.72` and `0.0.82`. This selector PR must be based on that trusted state and pass the checker without relying on its head copy. |
| `OS85-16` | High | Capability clearing now depends on `capctl 0.2.4` and `bitflags 1.3.2`, but upstream notices are unchanged and the consumed binaries have no published SBOM or attestation covering this dependency graph. | Bind crate checksums and source identities to the stable lock and binaries; review the unsafe syscall boundary and advisories; update notices/licenses; retain a generated SBOM and provenance for every consumed binary. | The stable lock, crate checksums, source identities, licenses, unsafe boundary, current RustSec absence, and SLSA-bound archives are recorded. Upstream still publishes no binary SBOM and its unchanged notices omit the new graph; that limitation remains explicit rather than being presented as complete attribution. |
Expand Down Expand Up @@ -740,15 +740,17 @@ The proof does not treat successful onboarding as evidence by itself. It:
`Type=tmpfs` mount and must not appear in `HostConfig.Binds`, which is the
representation changed for SELinux-labelled bind mounts. Inside the sandbox,
`/proc/mounts` must report `tmpfs,noexec`, mode 01777, and a writable marker.
5. Stops and recovers the actual host OpenShell gateway through NemoClaw. The
gateway PID must change, the rendered-config digest and sandbox container ID
must not, the release binary/listeners/auth path must still match, and both
the tmpfs marker and a Deep Agents durable-state marker must remain.
5. Stops and recovers the actual host OpenShell gateway through NemoClaw. A
graceful gateway shutdown stops the managed Docker sandbox, and startup
resumes that same container. The gateway PID must change, the rendered-config
digest and sandbox container ID must not, and the release
binary/listeners/auth path must still match. The tmpfs is remounted empty,
while the Deep Agents marker under durable `/sandbox` state must remain.
6. Runs the existing managed MCP rebuild with the same test-only wrapper. A new
Docker container is required, the tmpfs must be mounted again with the same
representation/options but without its old volatile marker, and the backed-up
Deep Agents state marker must be restored. This distinguishes a fresh tmpfs
mount from an accidentally retained container.
Docker container is required, the tmpfs must again have the same
representation/options and no volatile marker, and the backed-up Deep Agents
state marker must be restored. The new container identity plus the fresh tmpfs
mount prove that the driver config was reapplied during rebuild.

The proof is intentionally Linux amd64 Docker-bridge evidence. It does not
isolate Docker Desktop/Colima, WSL, DGX Spark's Docker 27 host-gateway route,
Expand Down
9 changes: 6 additions & 3 deletions test/e2e/live/openshell-exact-main-driver-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import type { CleanupRegistry } from "../fixtures/cleanup.ts";
import { resultText } from "../fixtures/clients/command.ts";
import type { HostCliClient } from "../fixtures/clients/host.ts";
import {
sandboxAccessEnv,
type SandboxClient,
sandboxAccessEnv,
trustedSandboxShellScript,
} from "../fixtures/clients/sandbox.ts";
import { expect } from "../fixtures/e2e-test.ts";
Expand Down Expand Up @@ -662,12 +662,15 @@ export async function restartAndAssertExactMainDriverConfig(options: {
expect(snapshot.config.configSha256, "rendered selected-driver config must be stable").toBe(
baseline.config.configSha256,
);
// OpenShell's graceful gateway shutdown stops managed Docker sandboxes and
// startup resumes them. Docker therefore remounts the same container's
// volatile tmpfs empty while the durable /sandbox state survives.
await assertSandboxMountAndAuth({
phase: "after-gateway-restart",
sandbox: options.sandbox,
sandboxName: options.sandboxName,
durableMarkerValue: options.proof.durableMarkerValue!,
tmpfsMarker: "present",
tmpfsMarker: "absent",
});
await options.host.expectListed(options.sandboxName, {
artifactName: "exact-main-driver-nemoclaw-list-after-gateway-restart",
Expand All @@ -685,7 +688,7 @@ export async function restartAndAssertExactMainDriverConfig(options: {
"after-gateway-restart",
options.proof,
snapshot,
"same-container-tmpfs-and-durable-state-retained",
"same-container-tmpfs-remounted-and-durable-state-retained",
);
}

Expand Down
18 changes: 18 additions & 0 deletions test/e2e/support/openshell-exact-main-driver-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,22 @@ describe("exact-main selected-driver config proof boundary", () => {
fs.rmSync(fixture, { recursive: true, force: true });
}
});

it("expects graceful gateway recovery to remount tmpfs while retaining durable state", () => {
const source = fs.readFileSync(
path.join("test", "e2e", "live", "openshell-exact-main-driver-config.ts"),
"utf8",
);
const restart = source.match(
/export async function restartAndAssertExactMainDriverConfig[\s\S]*?(?=\nexport async function assertExactMainDriverConfigAfterRebuild)/u,
)?.[0];

expect(restart).toBeDefined();
expect(restart).toContain('tmpfsMarker: "absent"');
expect(restart).not.toContain('tmpfsMarker: "present"');
expect(restart).toContain("baseline.containerId");
expect(restart).toContain("baseline.config.configSha256");
expect(restart).toContain("durableMarkerValue: options.proof.durableMarkerValue!");
expect(restart).toContain('"same-container-tmpfs-remounted-and-durable-state-retained"');
});
});
7 changes: 5 additions & 2 deletions test/openshell-0.0.85-migration-review.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,11 @@ describe("OpenShell 0.0.85 migration review", () => {
expect(helper).toContain("fs.realpathSync(`/proc/${gatewayPid}/exe`)");
expect(helper).toContain('["-H", "-ltnp"]');
expect(helper).toContain('"{{json .HostConfig.Binds}}"');
expect(helper).toContain('tmpfsMarker: "present"');
expect(helper).toContain('tmpfsMarker: "absent"');
expect(helper).not.toContain('tmpfsMarker: "present"');
expect(helper.match(/tmpfsMarker: "absent",/gu)).toHaveLength(2);
expect(helper).toContain('"same-container-tmpfs-remounted-and-durable-state-retained"');
expect(review).toContain("graceful gateway shutdown stops the managed Docker sandbox");
expect(review).toContain("tmpfs is remounted empty");

const mcpProof = fs.readFileSync(
path.join(repoRoot, "test/e2e/live/openshell-exact-main-mcp-proof.ts"),
Expand Down
Loading