@@ -1451,22 +1451,39 @@ fn enumerate_gpu_device_nodes() -> Vec<String> {
14511451 paths
14521452}
14531453
1454- /// Collect all baseline paths for enrichment: proxy defaults + GPU (if present).
1455- /// Returns `(read_only, read_write)` as owned `String` vecs.
1456- fn baseline_enrichment_paths ( ) -> ( Vec < String > , Vec < String > ) {
1457- let mut ro: Vec < String > = PROXY_BASELINE_READ_ONLY
1458- . iter ( )
1459- . map ( |& s| s. to_string ( ) )
1460- . collect ( ) ;
1461- let mut rw: Vec < String > = PROXY_BASELINE_READ_WRITE
1462- . iter ( )
1463- . map ( |& s| s. to_string ( ) )
1464- . collect ( ) ;
1454+ fn push_unique ( paths : & mut Vec < String > , path : String ) {
1455+ if !paths. iter ( ) . any ( |p| p == & path) {
1456+ paths. push ( path) ;
1457+ }
1458+ }
14651459
1466- if has_gpu_devices ( ) {
1467- ro. extend ( GPU_BASELINE_READ_ONLY . iter ( ) . map ( |& s| s. to_string ( ) ) ) ;
1468- rw. extend ( GPU_BASELINE_READ_WRITE . iter ( ) . map ( |& s| s. to_string ( ) ) ) ;
1469- rw. extend ( enumerate_gpu_device_nodes ( ) ) ;
1460+ fn collect_baseline_enrichment_paths (
1461+ include_proxy : bool ,
1462+ include_gpu : bool ,
1463+ gpu_device_nodes : Vec < String > ,
1464+ ) -> ( Vec < String > , Vec < String > ) {
1465+ let mut ro = Vec :: new ( ) ;
1466+ let mut rw = Vec :: new ( ) ;
1467+
1468+ if include_proxy {
1469+ for & path in PROXY_BASELINE_READ_ONLY {
1470+ push_unique ( & mut ro, path. to_string ( ) ) ;
1471+ }
1472+ for & path in PROXY_BASELINE_READ_WRITE {
1473+ push_unique ( & mut rw, path. to_string ( ) ) ;
1474+ }
1475+ }
1476+
1477+ if include_gpu {
1478+ for & path in GPU_BASELINE_READ_ONLY {
1479+ push_unique ( & mut ro, path. to_string ( ) ) ;
1480+ }
1481+ for & path in GPU_BASELINE_READ_WRITE {
1482+ push_unique ( & mut rw, path. to_string ( ) ) ;
1483+ }
1484+ for path in gpu_device_nodes {
1485+ push_unique ( & mut rw, path) ;
1486+ }
14701487 }
14711488
14721489 // A path promoted to read_write (e.g. /proc for GPU) should not also
@@ -1477,14 +1494,33 @@ fn baseline_enrichment_paths() -> (Vec<String>, Vec<String>) {
14771494 ( ro, rw)
14781495}
14791496
1480- /// Ensure a proto `SandboxPolicy` includes the baseline filesystem paths
1481- /// required for proxy-mode sandboxes. Paths are only added if missing;
1482- /// user-specified paths are never removed.
1483- ///
1484- /// Returns `true` if the policy was modified (caller may want to sync back).
1485- fn enrich_proto_baseline_paths ( proto : & mut openshell_core:: proto:: SandboxPolicy ) -> bool {
1486- // Only enrich if network_policies are present (proxy mode indicator).
1487- if proto. network_policies . is_empty ( ) {
1497+ fn active_baseline_enrichment_paths ( include_proxy : bool ) -> ( Vec < String > , Vec < String > ) {
1498+ let include_gpu = has_gpu_devices ( ) ;
1499+ let gpu_device_nodes = if include_gpu {
1500+ enumerate_gpu_device_nodes ( )
1501+ } else {
1502+ Vec :: new ( )
1503+ } ;
1504+ collect_baseline_enrichment_paths ( include_proxy, include_gpu, gpu_device_nodes)
1505+ }
1506+
1507+ /// Collect all active baseline paths for tests and diagnostics.
1508+ /// Returns `(read_only, read_write)` as owned `String` vecs.
1509+ #[ cfg( test) ]
1510+ fn baseline_enrichment_paths ( ) -> ( Vec < String > , Vec < String > ) {
1511+ active_baseline_enrichment_paths ( true )
1512+ }
1513+
1514+ fn enrich_proto_baseline_paths_with < F > (
1515+ proto : & mut openshell_core:: proto:: SandboxPolicy ,
1516+ ro : & [ String ] ,
1517+ rw : & [ String ] ,
1518+ path_exists : F ,
1519+ ) -> bool
1520+ where
1521+ F : Fn ( & str ) -> bool ,
1522+ {
1523+ if ro. is_empty ( ) && rw. is_empty ( ) {
14881524 return false ;
14891525 }
14901526
@@ -1495,17 +1531,10 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
14951531 ..Default :: default ( )
14961532 } ) ;
14971533
1498- let ( ro, rw) = baseline_enrichment_paths ( ) ;
1499-
1500- // Baseline paths are system-injected, not user-specified. Skip paths
1501- // that do not exist in this container image to avoid noisy warnings from
1502- // Landlock and, more critically, to prevent a single missing baseline
1503- // path from abandoning the entire Landlock ruleset under best-effort
1504- // mode (see issue #664).
15051534 let mut modified = false ;
1506- for path in & ro {
1535+ for path in ro {
15071536 if !fs. read_only . iter ( ) . any ( |p| p == path) && !fs. read_write . iter ( ) . any ( |p| p == path) {
1508- if !std :: path :: Path :: new ( path) . exists ( ) {
1537+ if !path_exists ( path) {
15091538 debug ! (
15101539 path,
15111540 "Baseline read-only path does not exist, skipping enrichment"
@@ -1516,11 +1545,11 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
15161545 modified = true ;
15171546 }
15181547 }
1519- for path in & rw {
1548+ for path in rw {
15201549 if fs. read_only . iter ( ) . any ( |p| p == path) || fs. read_write . iter ( ) . any ( |p| p == path) {
15211550 continue ;
15221551 }
1523- if !std :: path :: Path :: new ( path) . exists ( ) {
1552+ if !path_exists ( path) {
15241553 debug ! (
15251554 path,
15261555 "Baseline read-write path does not exist, skipping enrichment"
@@ -1531,6 +1560,26 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
15311560 modified = true ;
15321561 }
15331562
1563+ modified
1564+ }
1565+
1566+ /// Ensure a proto `SandboxPolicy` includes the baseline filesystem paths
1567+ /// required by proxy-mode sandboxes and GPU runtimes. Paths are only added if
1568+ /// missing; user-specified paths are never removed.
1569+ ///
1570+ /// Returns `true` if the policy was modified (caller may want to sync back).
1571+ fn enrich_proto_baseline_paths ( proto : & mut openshell_core:: proto:: SandboxPolicy ) -> bool {
1572+ let ( ro, rw) = active_baseline_enrichment_paths ( !proto. network_policies . is_empty ( ) ) ;
1573+
1574+ // Baseline paths are system-injected, not user-specified. Skip paths
1575+ // that do not exist in this container image to avoid noisy warnings from
1576+ // Landlock and, more critically, to prevent a single missing baseline
1577+ // path from abandoning the entire Landlock ruleset under best-effort
1578+ // mode (see issue #664).
1579+ let modified = enrich_proto_baseline_paths_with ( proto, & ro, & rw, |path| {
1580+ std:: path:: Path :: new ( path) . exists ( )
1581+ } ) ;
1582+
15341583 if modified {
15351584 ocsf_emit ! (
15361585 ConfigStateChangeBuilder :: new( ocsf_ctx( ) )
@@ -1546,15 +1595,15 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
15461595}
15471596
15481597/// Ensure a `SandboxPolicy` (Rust type) includes the baseline filesystem
1549- /// paths required for proxy-mode sandboxes. Used for the local-file code
1550- /// path where no proto is available.
1598+ /// paths required by proxy-mode sandboxes and GPU runtimes. Used for the
1599+ /// local-file code path where no proto is available.
15511600fn enrich_sandbox_baseline_paths ( policy : & mut SandboxPolicy ) {
1552- if !matches ! ( policy. network. mode, NetworkMode :: Proxy ) {
1601+ let ( ro, rw) =
1602+ active_baseline_enrichment_paths ( matches ! ( policy. network. mode, NetworkMode :: Proxy ) ) ;
1603+ if ro. is_empty ( ) && rw. is_empty ( ) {
15531604 return ;
15541605 }
15551606
1556- let ( ro, rw) = baseline_enrichment_paths ( ) ;
1557-
15581607 let mut modified = false ;
15591608 for path in & ro {
15601609 let p = std:: path:: PathBuf :: from ( path) ;
@@ -1709,6 +1758,31 @@ mod baseline_tests {
17091758 ) ;
17101759 }
17111760
1761+ #[ test]
1762+ fn proto_gpu_enrichment_adds_devices_without_network_policy ( ) {
1763+ let mut policy = openshell_policy:: restrictive_default_policy ( ) ;
1764+ assert ! (
1765+ policy. network_policies. is_empty( ) ,
1766+ "regression setup must exercise the no-network default path"
1767+ ) ;
1768+ let ( ro, rw) =
1769+ collect_baseline_enrichment_paths ( false , true , vec ! [ "/dev/nvidia0" . to_string( ) ] ) ;
1770+
1771+ let enriched = enrich_proto_baseline_paths_with ( & mut policy, & ro, & rw, |path| {
1772+ matches ! ( path, "/proc" | "/dev/nvidia0" )
1773+ } ) ;
1774+
1775+ let filesystem = policy. filesystem . expect ( "filesystem policy" ) ;
1776+ assert ! (
1777+ enriched,
1778+ "GPU enrichment should not require network policies"
1779+ ) ;
1780+ assert ! (
1781+ filesystem. read_write. contains( & "/dev/nvidia0" . to_string( ) ) ,
1782+ "GPU enrichment should add enumerated device nodes without network policies"
1783+ ) ;
1784+ }
1785+
17121786 #[ test]
17131787 fn gpu_baseline_read_write_contains_dxg ( ) {
17141788 // /dev/dxg must be present so WSL2 sandboxes get the Landlock
0 commit comments