@@ -42,6 +42,8 @@ class SDKContext:
4242 engine_id : Optional [str ] = None
4343 plugin_id : Optional [str ] = None
4444 artifacts : list [str ] = field (default_factory = list )
45+ # ACASL-only scope: when set, all file operations are restricted to this directory
46+ allowed_dir : Optional [Path ] = None
4547 redact_logs : bool = True
4648 debug_enabled : bool = False
4749 _iter_cache : dict [tuple [tuple [str , ...], tuple [str , ...]], list [Path ]] = field (
@@ -181,10 +183,27 @@ def is_within_workspace(self, p: Path) -> bool:
181183 except Exception :
182184 return False
183185
186+ def is_within_allowed (self , p : Path ) -> bool :
187+ """Return True if path is within the allowed_dir (ACASL scope) when set; otherwise True."""
188+ try :
189+ if self .allowed_dir is None :
190+ return True
191+ _ = p .resolve ().relative_to (self .allowed_dir .resolve ())
192+ return True
193+ except Exception :
194+ return False
195+
196+ def is_within_scope (self , p : Path ) -> bool :
197+ """Scope check used by file helpers: within workspace and, when set, within allowed_dir."""
198+ try :
199+ return self .is_within_workspace (p ) and self .is_within_allowed (p )
200+ except Exception :
201+ return False
202+
184203 def safe_path (self , * parts : Pathish ) -> Path :
185204 p = self .path (* parts ).resolve ()
186- if not self .is_within_workspace (p ):
187- raise ValueError (f"Path escapes workspace: { p } " )
205+ if not self .is_within_scope (p ):
206+ raise ValueError (f"Path not allowed (outside output_dir/ workspace) : { p } " )
188207 return p
189208
190209 def require_files (self , paths : Sequence [Pathish ]) -> list [Path ]:
@@ -220,14 +239,14 @@ def iter_files(
220239 enforce_workspace : bool = True ,
221240 max_files : Optional [int ] = None ,
222241 ) -> Iterable [Path ]:
223- root = self .workspace_root
242+ root = self .allowed_dir or self . workspace_root
224243 ex_patterns = list (exclude or [])
225244 count = 0
226245 for p in root .rglob ("*" ):
227246 try :
228247 if not p .is_file ():
229248 continue
230- if enforce_workspace and not self .is_within_workspace (p ):
249+ if enforce_workspace and not self .is_within_scope (p ):
231250 continue
232251 rel = p .relative_to (root ).as_posix ()
233252 if ex_patterns and any (fnmatch .fnmatch (rel , ex ) for ex in ex_patterns ):
@@ -263,14 +282,14 @@ def iter_project_files(
263282 return
264283 except Exception :
265284 key = None
266- root = self .workspace_root
285+ root = self .allowed_dir or self . workspace_root
267286 matches : list [Path ] = []
268287 count = 0
269288 for p in root .rglob ("*" ):
270289 try :
271290 if not p .is_file ():
272291 continue
273- if not self .is_within_workspace (p ):
292+ if not self .is_within_scope (p ):
274293 continue
275294 rel = p .relative_to (root ).as_posix ()
276295 if exc and any (fnmatch .fnmatch (rel , ex ) for ex in exc ):
0 commit comments