Skip to content

Commit c067833

Browse files
committed
test(cua): verify inference policy boundaries
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
1 parent fc38989 commit c067833

1 file changed

Lines changed: 73 additions & 0 deletions

File tree

test/package-contract/openshell-policy-boundary.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,48 @@ function packageFiles(packageRoot: string): string[] {
2424
return packageJson.files ?? [];
2525
}
2626

27+
type ManagedInferenceRequest = {
28+
binary: string;
29+
host: string;
30+
port: number;
31+
method: string;
32+
path: string;
33+
};
34+
35+
type ManagedInferencePolicy = {
36+
binaries?: Array<{ path?: string }>;
37+
endpoints?: Array<{
38+
host?: string;
39+
port?: number;
40+
rules?: Array<{ allow?: { method?: string; path?: string } }>;
41+
}>;
42+
};
43+
44+
function restPathMatches(pattern: string, requestPath: string): boolean {
45+
return (
46+
pattern === requestPath ||
47+
(pattern.endsWith("/**") && requestPath.startsWith(pattern.slice(0, -2)))
48+
);
49+
}
50+
51+
function managedInferenceAllows(
52+
policy: ManagedInferencePolicy,
53+
request: ManagedInferenceRequest,
54+
): boolean {
55+
const endpoint = (policy.endpoints ?? []).find(
56+
(candidate) => candidate.host === request.host && candidate.port === request.port,
57+
);
58+
return (
59+
(policy.binaries ?? []).some((binary) => binary.path === request.binary) &&
60+
endpoint?.rules?.some(
61+
(rule) =>
62+
rule.allow?.method === request.method &&
63+
typeof rule.allow.path === "string" &&
64+
restPathMatches(rule.allow.path, request.path),
65+
) === true
66+
);
67+
}
68+
2769
describe("OpenShell policy boundary package contract", () => {
2870
it.each([repoRoot, path.join(repoRoot, "nemoclaw")])(
2971
"pins the YAML parser used by both production package boundaries [case %#]",
@@ -235,6 +277,37 @@ describe("OpenShell policy boundary package contract", () => {
235277
);
236278
});
237279

280+
it.each(["/usr/bin/python3", "/usr/local/bin/python3", "/usr/bin/curl"])(
281+
"keeps NemoCUA managed inference deny-by-default for %s (#9649)",
282+
(binary) => {
283+
const rawPolicy = YAML.parse(
284+
fs.readFileSync(path.join(repoRoot, "agents", "nemocua", "policy-additions.yaml"), "utf8"),
285+
) as { network_policies?: { managed_inference?: ManagedInferencePolicy } };
286+
const policy = rawPolicy.network_policies?.managed_inference;
287+
expect(policy).toBeDefined();
288+
289+
const decide = (overrides: Partial<ManagedInferenceRequest> = {}) =>
290+
managedInferenceAllows(policy!, {
291+
binary,
292+
host: "inference.local",
293+
port: 443,
294+
method: "POST",
295+
path: "/v1/chat/completions",
296+
...overrides,
297+
});
298+
299+
expect(decide()).toBe(true);
300+
expect(decide({ method: "GET" })).toBe(false);
301+
expect(decide({ path: "/v1/embeddings" })).toBe(false);
302+
expect(decide({ host: "api.example.invalid" })).toBe(false);
303+
expect(decide({ port: 80 })).toBe(false);
304+
expect(decide({ binary: "/bin/sh" })).toBe(false);
305+
expect(decide({ method: "GET", path: "/v1/models" })).toBe(true);
306+
expect(decide({ method: "GET", path: "/v1/models/example" })).toBe(true);
307+
expect(decide({ method: "POST", path: "/v1/models/example" })).toBe(false);
308+
},
309+
);
310+
238311
it("ships an out-of-tree runtime sandbox-policy schema validator", { timeout: 240_000 }, () => {
239312
const productionDependencyTree = spawnSync(
240313
"npm",

0 commit comments

Comments
 (0)