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
18 changes: 11 additions & 7 deletions src/lib/adapters/openshell/grpc-sandbox-control.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,25 @@
import { EventEmitter } from "node:events";
import path from "node:path";
import {
type CallOptions,
loadPackageDefinition,
Metadata,
Server,
ServerCredentials,
type CallOptions,
type sendUnaryData,
type ServerUnaryCall,
type ServerWritableStream,
type ServiceClientConstructor,
type ServiceError,
type sendUnaryData,
} from "@grpc/grpc-js";
import { loadPackageDefinition } from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
import { describe, expect, it, vi } from "vitest";

import {
createGrpcOpenShellSandboxControl,
createOpenShellGrpcApi,
OpenShellGrpcOutputLimitError,
type OpenShellGrpcApi,
OpenShellGrpcOutputLimitError,
} from "./grpc-sandbox-control";

class FakeStream extends EventEmitter {
Expand Down Expand Up @@ -251,7 +251,7 @@ describe("gRPC OpenShell sandbox control", () => {
it("rejects sandbox responses without a stable id", async () => {
const fake = fakeApi({ sandboxId: "" });
const control = createGrpcOpenShellSandboxControl(
{ endpoint: "http://localhost:8080" },
{ endpoint: "http://127.0.0.1:8080" },
fake.api,
);

Expand Down Expand Up @@ -348,7 +348,11 @@ describe("gRPC OpenShell sandbox control", () => {

it.each([
["ftp://localhost:8080", "must use http:// or https://"],
["http://localhost:8080", "restricted to loopback"],
["http://gateway.example:8080", "restricted to loopback"],
["http://128.0.0.1:8080", "restricted to loopback"],
["http://[::2]:8080", "restricted to loopback"],
["http://127.999.999.999:8080", "Invalid OpenShell gRPC endpoint"],
["http://localhost:8080/path", "must not contain"],
])("rejects unsafe endpoint %s", (endpoint, message) => {
expect(() => createOpenShellGrpcApi({ endpoint })).toThrow(message);
Expand All @@ -357,13 +361,13 @@ describe("gRPC OpenShell sandbox control", () => {
it("rejects bearer tokens or TLS material on plaintext endpoints", () => {
expect(() =>
createOpenShellGrpcApi({
endpoint: "http://localhost:8080",
endpoint: "http://127.0.0.1:8080",
bearerToken: "token",
}),
).toThrow("requires TLS");
expect(() =>
createOpenShellGrpcApi({
endpoint: "http://localhost:8080",
endpoint: "http://127.0.0.1:8080",
caCertificate: Buffer.from("ca"),
}),
).toThrow("require an https:// endpoint");
Expand Down
4 changes: 3 additions & 1 deletion src/lib/adapters/openshell/grpc-sandbox-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import type { EventEmitter } from "node:events";
import { isIP } from "node:net";
import path from "node:path";
import { StringDecoder } from "node:string_decoder";

Expand Down Expand Up @@ -74,8 +75,9 @@ export class OpenShellGrpcOutputLimitError extends Error {

function isLoopback(hostname: string): boolean {
const normalized = hostname.replace(/^\[|\]$/g, "").toLowerCase();
const version = isIP(normalized);
return (
normalized === "localhost" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized)
(version === 4 && normalized.startsWith("127.")) || (version === 6 && normalized === "::1")
);
}

Expand Down
Loading