Skip to content

Commit f60cda6

Browse files
la14-1louisgvclaude
authored
test: add validateMetadataValue tests for GCP metadata injection protection (#2467)
Agent: test-engineer Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent d82dea8 commit f60cda6

1 file changed

Lines changed: 90 additions & 1 deletion

File tree

packages/cli/src/__tests__/security-connection-validation.test.ts

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44
*/
55

66
import { describe, expect, it } from "bun:test";
7-
import { validateConnectionIP, validateLaunchCmd, validateServerIdentifier, validateUsername } from "../security.js";
7+
import {
8+
validateConnectionIP,
9+
validateLaunchCmd,
10+
validateMetadataValue,
11+
validateServerIdentifier,
12+
validateUsername,
13+
} from "../security.js";
814

915
describe("validateConnectionIP", () => {
1016
describe("valid inputs", () => {
@@ -274,3 +280,86 @@ describe("validateLaunchCmd", () => {
274280
});
275281
});
276282
});
283+
284+
describe("validateMetadataValue", () => {
285+
describe("valid inputs", () => {
286+
it("should accept valid GCP zones", () => {
287+
expect(() => validateMetadataValue("us-central1-a", "zone")).not.toThrow();
288+
expect(() => validateMetadataValue("europe-west1-b", "zone")).not.toThrow();
289+
expect(() => validateMetadataValue("asia-east1-c", "zone")).not.toThrow();
290+
});
291+
292+
it("should accept valid project IDs", () => {
293+
expect(() => validateMetadataValue("my-project-123", "project")).not.toThrow();
294+
expect(() => validateMetadataValue("gcp_project.name", "project")).not.toThrow();
295+
expect(() => validateMetadataValue("prod-app-42", "project")).not.toThrow();
296+
});
297+
298+
it("should accept alphanumeric values with allowed special characters", () => {
299+
expect(() => validateMetadataValue("simple", "field")).not.toThrow();
300+
expect(() => validateMetadataValue("with.dots", "field")).not.toThrow();
301+
expect(() => validateMetadataValue("with_underscores", "field")).not.toThrow();
302+
expect(() => validateMetadataValue("with-hyphens", "field")).not.toThrow();
303+
expect(() => validateMetadataValue("MixedCase123", "field")).not.toThrow();
304+
});
305+
306+
it("should allow empty string (caller provides defaults)", () => {
307+
expect(() => validateMetadataValue("", "zone")).not.toThrow();
308+
});
309+
310+
it("should allow whitespace-only string (treated as empty)", () => {
311+
expect(() => validateMetadataValue(" ", "project")).not.toThrow();
312+
});
313+
});
314+
315+
describe("invalid inputs", () => {
316+
it("should reject values exceeding 128 characters", () => {
317+
const longValue = "a".repeat(129);
318+
expect(() => validateMetadataValue(longValue, "zone")).toThrow(/too long/);
319+
});
320+
321+
it("should accept values at exactly 128 characters", () => {
322+
const exactValue = "a".repeat(128);
323+
expect(() => validateMetadataValue(exactValue, "zone")).not.toThrow();
324+
});
325+
326+
it("should reject command substitution with $()", () => {
327+
expect(() => validateMetadataValue("$(whoami)", "zone")).toThrow(/Invalid zone/);
328+
});
329+
330+
it("should reject backtick command substitution", () => {
331+
expect(() => validateMetadataValue("`id`", "project")).toThrow(/Invalid project/);
332+
});
333+
334+
it("should reject semicolon injection", () => {
335+
expect(() => validateMetadataValue("zone;rm -rf /", "zone")).toThrow(/Invalid zone/);
336+
});
337+
338+
it("should reject pipe injection", () => {
339+
expect(() => validateMetadataValue("zone|cat /etc/passwd", "project")).toThrow(/Invalid project/);
340+
});
341+
342+
it("should reject ampersand chaining", () => {
343+
expect(() => validateMetadataValue("zone&echo pwned", "zone")).toThrow(/Invalid zone/);
344+
});
345+
346+
it("should reject path traversal", () => {
347+
expect(() => validateMetadataValue("../../../etc/passwd", "zone")).toThrow(/Invalid zone/);
348+
});
349+
350+
it("should reject spaces", () => {
351+
expect(() => validateMetadataValue("us central1", "zone")).toThrow(/Invalid zone/);
352+
});
353+
354+
it("should reject quotes", () => {
355+
expect(() => validateMetadataValue("zone'injection", "field")).toThrow(/Invalid field/);
356+
expect(() => validateMetadataValue('zone"injection', "field")).toThrow(/Invalid field/);
357+
});
358+
359+
it("should include field name in error messages", () => {
360+
expect(() => validateMetadataValue("$(evil)", "gcp_zone")).toThrow(/Invalid gcp_zone/);
361+
expect(() => validateMetadataValue("bad;value", "gcp_project")).toThrow(/Invalid gcp_project/);
362+
expect(() => validateMetadataValue("a".repeat(129), "my_field")).toThrow(/my_field is too long/);
363+
});
364+
});
365+
});

0 commit comments

Comments
 (0)