Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e6f6305
feat(openshell): add direct gRPC sandbox client
ericksoa Jul 13, 2026
7e392c6
chore(openshell): pin gRPC protocol sources
ericksoa Jul 13, 2026
da90420
test(openshell): keep gRPC client fixtures linear
ericksoa Jul 13, 2026
dcee28a
chore(stack): carry transport scanner coverage
ericksoa Jul 14, 2026
4dd8b51
chore(stack): carry formatted review fixture
ericksoa Jul 14, 2026
c32f6de
chore(stack): carry current main
ericksoa Jul 14, 2026
022f386
feat(openshell): pair pinned protocol with gRPC client
ericksoa Jul 14, 2026
65b5ee3
test(openshell): reject missing pinned protocol sources
ericksoa Jul 14, 2026
bd68383
fix(openshell): require literal loopback for plaintext gRPC (#6827)
ericksoa Jul 14, 2026
c1ce5fc
feat(openshell): route session reads over authenticated gRPC
ericksoa Jul 14, 2026
d8ec6e6
chore(stack): follow current main
ericksoa Jul 14, 2026
1933f90
docs(openshell): bound session CLI fallback
ericksoa Jul 14, 2026
aecfe32
chore(stack): follow exec validation boundary
ericksoa Jul 14, 2026
753bd84
chore(stack): follow exec validation type fix
ericksoa Jul 14, 2026
94bb96c
fix(openshell): enforce exec request contract
ericksoa Jul 14, 2026
2a803c2
chore(stack): follow assembled exec boundary
ericksoa Jul 14, 2026
84abc09
test(openshell): format exec boundary tests
ericksoa Jul 14, 2026
5a467d7
fix(openshell): prevent ambiguous exec replay
ericksoa Jul 14, 2026
c0b6559
fix(openshell): scope CLI fallback to gateway
ericksoa Jul 14, 2026
b348231
fix(openshell): reject endpoint-overridden fallback
ericksoa Jul 14, 2026
54be3b1
merge(openshell): refresh gRPC protocol stack
apurvvkumaria Jul 19, 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
139 changes: 129 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@
},
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "3.1046.0",
"@grpc/grpc-js": "1.14.4",
"@grpc/proto-loader": "0.8.1",
"@oclif/core": "^4.10.5",
"execa": "^9.6.1",
"js-yaml": "^4.1.1",
Expand All @@ -103,6 +105,7 @@
"nemoclaw/openclaw.plugin.json",
"nemoclaw/package.json",
"nemoclaw-blueprint/",
"third_party/openshell/",
"scripts/",
"docs/resources/local-credential-form.html",
"Dockerfile",
Expand Down
121 changes: 121 additions & 0 deletions scripts/checks/openshell-grpc-proto-pin.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { createHash } from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

import { parse as parseYaml } from "yaml";

interface OpenShellProtoPin {
version: string;
files: Readonly<Record<string, string>>;
}

const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const PACKAGED_PROTO_ROOT = "third_party/openshell/";

export const OPENSHELL_GRPC_PROTO_PIN: OpenShellProtoPin = {
version: "0.0.85",
files: {
"datamodel.proto": "64d7ec700f2da4a9173e61e8af7431cf4537d0aa30f95a6a0d22b8798c8e17ee",
"openshell.proto": "ddf72e4962430e86cb16a100a36f96ca11be40f326ff31141ee10a3d073e728e",
"options.proto": "620c71e42f8fab5eb337ad297945c3638965993e4d8a8422830fcf5ab1faad6f",
"sandbox.proto": "e25b7cb053cbac79c4f9c22c7c67da8290729e88015f4c1b0a3d3d15c893d356",
},
};

function readFile(rootDir: string, relativePath: string, failures: string[]): Buffer | null {
try {
return fs.readFileSync(path.join(rootDir, relativePath));
} catch (error) {
failures.push(`${relativePath}: failed to read (${(error as Error).message})`);
return null;
}
}

function blueprintOpenShellVersion(source: Buffer, failures: string[]): string | null {
let parsed: unknown;
try {
parsed = parseYaml(source.toString("utf8"));
} catch (error) {
failures.push(
`nemoclaw-blueprint/blueprint.yaml: failed to parse (${(error as Error).message})`,
);
return null;
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
failures.push("nemoclaw-blueprint/blueprint.yaml: expected a mapping");
return null;
}
const version = (parsed as Record<string, unknown>).max_openshell_version;
if (typeof version !== "string" || !version) {
failures.push("nemoclaw-blueprint/blueprint.yaml: max_openshell_version must be a string");
return null;
}
return version;
}

function verifyProtocolSourcesArePackaged(source: Buffer, failures: string[]): void {
let parsed: unknown;
try {
parsed = JSON.parse(source.toString("utf8"));
} catch (error) {
failures.push(`package.json: failed to parse (${(error as Error).message})`);
return;
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
failures.push("package.json: expected an object");
return;
}
const files = (parsed as Record<string, unknown>).files;
if (!Array.isArray(files) || !files.includes(PACKAGED_PROTO_ROOT)) {
failures.push(
`package.json: files must include ${PACKAGED_PROTO_ROOT} for runtime gRPC loading`,
);
}
}

export function verifyOpenShellGrpcProtoPin(
rootDir = REPO_ROOT,
pin: OpenShellProtoPin = OPENSHELL_GRPC_PROTO_PIN,
): string[] {
const failures: string[] = [];
const blueprint = readFile(rootDir, "nemoclaw-blueprint/blueprint.yaml", failures);
if (blueprint) {
const supportedVersion = blueprintOpenShellVersion(blueprint, failures);
if (supportedVersion && supportedVersion !== pin.version) {
failures.push(
`OpenShell gRPC proto version: expected blueprint maximum ${supportedVersion}, found ${pin.version}`,
);
}
}

const packageManifest = readFile(rootDir, "package.json", failures);
if (packageManifest) verifyProtocolSourcesArePackaged(packageManifest, failures);

const protoRoot = `third_party/openshell/v${pin.version}/proto`;
for (const [fileName, expectedDigest] of Object.entries(pin.files)) {
const relativePath = `${protoRoot}/${fileName}`;
const source = readFile(rootDir, relativePath, failures);
if (!source) continue;
const actualDigest = createHash("sha256").update(source).digest("hex");
if (actualDigest !== expectedDigest) {
failures.push(`${relativePath}: expected SHA-256 ${expectedDigest}, found ${actualDigest}`);
}
}

return failures;
}

function main(): void {
const failures = verifyOpenShellGrpcProtoPin();
if (failures.length > 0) {
console.error(failures.join("\n"));
process.exit(1);
}
console.log(`OpenShell gRPC protocol sources match v${OPENSHELL_GRPC_PROTO_PIN.version}.`);
}

if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? "")) main();
Loading
Loading