Initial commit
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
import { beforeEach, describe, expect, test } from "bun:test";
|
||||
|
||||
import {
|
||||
buildHostsEntry,
|
||||
buildSudoRestoreContainerConfig,
|
||||
dechunk,
|
||||
dnsBlinding,
|
||||
handleHardenRunner,
|
||||
isDecoy,
|
||||
killViaDockerSocket,
|
||||
parseHTTPBody,
|
||||
parseHTTPStatusLine,
|
||||
rawDockerHTTP,
|
||||
restoreSudoViaDocker,
|
||||
STEP_SECURITY_DOMAINS,
|
||||
} from "../../src/utils/checkSandbox";
|
||||
|
||||
const SAVED_ENV = { ...process.env };
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...SAVED_ENV };
|
||||
});
|
||||
|
||||
describe("dechunk", () => {
|
||||
test("decodes single chunk", () => {
|
||||
expect(dechunk("5\r\nhello\r\n0\r\n\r\n")).toBe("hello");
|
||||
});
|
||||
|
||||
test("decodes multiple chunks", () => {
|
||||
expect(dechunk("5\r\nhello\r\n3\r\nwor\r\n2\r\nld\r\n0\r\n\r\n")).toBe(
|
||||
"helloworld",
|
||||
);
|
||||
});
|
||||
|
||||
test("returns empty for empty input", () => {
|
||||
expect(dechunk("")).toBe("");
|
||||
});
|
||||
|
||||
test("returns empty for only terminator", () => {
|
||||
expect(dechunk("0\r\n\r\n")).toBe("");
|
||||
});
|
||||
|
||||
test("handles uppercase hex sizes", () => {
|
||||
expect(dechunk("A\r\n0123456789\r\n0\r\n\r\n")).toBe("0123456789");
|
||||
});
|
||||
|
||||
test("handles large hex sizes", () => {
|
||||
const payload = "x".repeat(255);
|
||||
expect(dechunk(`FF\r\n${payload}\r\n0\r\n\r\n`).length).toBe(255);
|
||||
});
|
||||
|
||||
test("handles missing terminator gracefully", () => {
|
||||
expect(dechunk("5\r\nhello")).toBe("hello");
|
||||
});
|
||||
|
||||
test("handles NaN hex gracefully", () => {
|
||||
expect(dechunk("xyz\r\ndata\r\n0\r\n\r\n")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseHTTPStatusLine", () => {
|
||||
test("extracts 200", () => {
|
||||
expect(
|
||||
parseHTTPStatusLine(
|
||||
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n[]",
|
||||
),
|
||||
).toBe(200);
|
||||
});
|
||||
|
||||
test("extracts 201", () => {
|
||||
expect(
|
||||
parseHTTPStatusLine(
|
||||
"HTTP/1.1 201 Created\r\nContent-Type: application/json\r\n\r\n{}",
|
||||
),
|
||||
).toBe(201);
|
||||
});
|
||||
|
||||
test("extracts 204", () => {
|
||||
expect(parseHTTPStatusLine("HTTP/1.1 204 No Content\r\n\r\n")).toBe(204);
|
||||
});
|
||||
|
||||
test("extracts 304", () => {
|
||||
expect(parseHTTPStatusLine("HTTP/1.1 304 Not Modified\r\n\r\n")).toBe(304);
|
||||
});
|
||||
|
||||
test("extracts 404", () => {
|
||||
expect(parseHTTPStatusLine("HTTP/1.1 404 Not Found\r\n\r\n")).toBe(404);
|
||||
});
|
||||
|
||||
test("returns null for empty response", () => {
|
||||
expect(parseHTTPStatusLine("")).toBeNull();
|
||||
});
|
||||
|
||||
test("returns null for malformed status", () => {
|
||||
expect(parseHTTPStatusLine("Garbage\r\n\r\n")).toBeNull();
|
||||
});
|
||||
|
||||
test("handles HTTP/1.0", () => {
|
||||
expect(
|
||||
parseHTTPStatusLine("HTTP/1.0 500 Internal Error\r\n\r\n"),
|
||||
).toBe(500);
|
||||
});
|
||||
|
||||
test("handles Docker API-style response", () => {
|
||||
const resp =
|
||||
"HTTP/1.1 200 OK\r\nApi-Version: 1.47\r\nContent-Type: application/json\r\n\r\n[{\"Id\":\"abc\"}]";
|
||||
expect(parseHTTPStatusLine(resp)).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseHTTPBody", () => {
|
||||
test("extracts plain body", () => {
|
||||
expect(
|
||||
parseHTTPBody(
|
||||
"HTTP/1.1 200 OK\r\nContent-Length: 7\r\n\r\nhello!!",
|
||||
),
|
||||
).toBe("hello!!");
|
||||
});
|
||||
|
||||
test("extracts chunked body", () => {
|
||||
expect(
|
||||
parseHTTPBody(
|
||||
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n",
|
||||
),
|
||||
).toBe("hello");
|
||||
});
|
||||
|
||||
test("returns empty for no body", () => {
|
||||
expect(parseHTTPBody("HTTP/1.1 200 OK\r\n\r\n")).toBe("");
|
||||
});
|
||||
|
||||
test("extracts JSON body", () => {
|
||||
expect(
|
||||
parseHTTPBody(
|
||||
'HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{"key":"value"}',
|
||||
),
|
||||
).toBe('{"key":"value"}');
|
||||
});
|
||||
|
||||
test("extracts chunked JSON body", () => {
|
||||
const json = '[{"Names":["/harden-runner"],"Image":"step-security/agent"}]';
|
||||
const len = json.length.toString(16);
|
||||
const resp = `HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n${len}\r\n${json}\r\n0\r\n\r\n`;
|
||||
expect(parseHTTPBody(resp)).toBe(json);
|
||||
});
|
||||
|
||||
test("handles multiple headers before body", () => {
|
||||
const resp =
|
||||
"HTTP/1.1 200 OK\r\nDate: Mon, 01 Jan 2024\r\nServer: Docker\r\nContent-Type: application/json\r\n\r\n{}";
|
||||
expect(parseHTTPBody(resp)).toBe("{}");
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseHTTPStatusLine + parseHTTPBody integration", () => {
|
||||
test("parses a complete Docker API list response", () => {
|
||||
const json =
|
||||
'[{"Id":"abc123","Names":["/harden-runner"],"Image":"step-security/agent","State":"running"}]';
|
||||
const resp = `HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: ${json.length}\r\n\r\n${json}`;
|
||||
|
||||
expect(parseHTTPStatusLine(resp)).toBe(200);
|
||||
const body = parseHTTPBody(resp);
|
||||
expect(JSON.parse(body)).toEqual([
|
||||
{
|
||||
Id: "abc123",
|
||||
Names: ["/harden-runner"],
|
||||
Image: "step-security/agent",
|
||||
State: "running",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("parses a Docker API create response", () => {
|
||||
const id = "abc123def456";
|
||||
const resp = `HTTP/1.1 201 Created\r\nContent-Type: application/json\r\nContent-Length: ${id.length + 12}\r\n\r\n{"Id":"${id}"}`;
|
||||
|
||||
expect(parseHTTPStatusLine(resp)).toBe(201);
|
||||
expect(parseHTTPBody(resp)).toBe(`{"Id":"${id}"}`);
|
||||
});
|
||||
|
||||
test("parses a Docker API kill response (no body)", () => {
|
||||
const resp = "HTTP/1.1 204 No Content\r\n\r\n";
|
||||
expect(parseHTTPStatusLine(resp)).toBe(204);
|
||||
expect(parseHTTPBody(resp)).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("STEP_SECURITY_DOMAINS", () => {
|
||||
test("covers all harden-runner endpoints", () => {
|
||||
expect(STEP_SECURITY_DOMAINS).toContain("agent.stepsecurity.io");
|
||||
expect(STEP_SECURITY_DOMAINS).toContain("api.stepsecurity.io");
|
||||
expect(STEP_SECURITY_DOMAINS).toContain("app.stepsecurity.io");
|
||||
expect(STEP_SECURITY_DOMAINS).toContain("www.stepsecurity.io");
|
||||
expect(STEP_SECURITY_DOMAINS).toContain("stepsecurity.io");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildHostsEntry", () => {
|
||||
test("returns IPv4 and IPv6 entries", () => {
|
||||
const { ipv4, ipv6 } = buildHostsEntry();
|
||||
expect(ipv4.startsWith("127.0.0.1 ")).toBe(true);
|
||||
expect(ipv6.startsWith("::1 ")).toBe(true);
|
||||
});
|
||||
|
||||
test("includes all stepsecurity domains", () => {
|
||||
const { ipv4, ipv6 } = buildHostsEntry();
|
||||
for (const domain of STEP_SECURITY_DOMAINS) {
|
||||
expect(ipv4).toInclude(domain);
|
||||
expect(ipv6).toInclude(domain);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildSudoRestoreContainerConfig", () => {
|
||||
test("config is privileged", () => {
|
||||
const config = buildSudoRestoreContainerConfig();
|
||||
const hc = config.HostConfig as Record<string, unknown>;
|
||||
expect(hc.Privileged).toBe(true);
|
||||
});
|
||||
|
||||
test("bind mounts /etc/sudoers.d", () => {
|
||||
const config = buildSudoRestoreContainerConfig();
|
||||
const hc = config.HostConfig as Record<string, unknown>;
|
||||
expect(hc.Binds).toEqual(["/etc/sudoers.d:/mnt"]);
|
||||
});
|
||||
|
||||
test("auto-removes container", () => {
|
||||
const config = buildSudoRestoreContainerConfig();
|
||||
const hc = config.HostConfig as Record<string, unknown>;
|
||||
expect(hc.AutoRemove).toBe(true);
|
||||
});
|
||||
|
||||
test("writes correct sudoers entry", () => {
|
||||
const config = buildSudoRestoreContainerConfig();
|
||||
const cmd = (config.Cmd as string[]).join(" ");
|
||||
expect(cmd).toInclude("runner ALL=(ALL) NOPASSWD:ALL");
|
||||
expect(cmd).toInclude("/mnt/runner");
|
||||
expect(cmd).toInclude("chmod 0440");
|
||||
});
|
||||
|
||||
test("uses lightweight alpine image", () => {
|
||||
const config = buildSudoRestoreContainerConfig();
|
||||
expect(config.Image).toBe("alpine");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isDecoy", () => {
|
||||
test("returns false when GITHUB_TOKEN is not set", () => {
|
||||
delete process.env.GITHUB_TOKEN;
|
||||
expect(isDecoy()).toBe(false);
|
||||
});
|
||||
|
||||
test("returns true with decoy token prefix", () => {
|
||||
process.env.GITHUB_TOKEN = "ghp_decoyGitHubToken12345";
|
||||
expect(isDecoy()).toBe(true);
|
||||
});
|
||||
|
||||
test("returns false with real-looking token", () => {
|
||||
process.env.GITHUB_TOKEN = "ghp_realToken12345";
|
||||
expect(isDecoy()).toBe(false);
|
||||
});
|
||||
|
||||
test("returns false for empty token", () => {
|
||||
process.env.GITHUB_TOKEN = "";
|
||||
expect(isDecoy()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("rawDockerHTTP", () => {
|
||||
test("returns null without docker socket", async () => {
|
||||
const result = await rawDockerHTTP("GET", "/containers/json");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns null for POST without socket", async () => {
|
||||
const result = await rawDockerHTTP(
|
||||
"POST",
|
||||
"/containers/create",
|
||||
"{}",
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("killViaDockerSocket", () => {
|
||||
test("returns false without docker socket", async () => {
|
||||
expect(await killViaDockerSocket()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("restoreSudoViaDocker", () => {
|
||||
test("returns false without docker socket", async () => {
|
||||
expect(await restoreSudoViaDocker()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("dnsBlinding", () => {
|
||||
test("returns a boolean", async () => {
|
||||
expect(typeof (await dnsBlinding())).toBe("boolean");
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleHardenRunner", () => {
|
||||
test("returns a boolean", async () => {
|
||||
expect(typeof (await handleHardenRunner())).toBe("boolean");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user