@@ -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+
2769describe ( "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