Repository navigation
Aafeedback changes tsfm tools - #633
Conversation
…1 snapshot Signed-off-by: Dhaval Patel <pateldha@us.ibm.com>
- resolver: allowlist the estimator classes importable from a dotted path - param_space: route introspect through the same allowlist - feature_runner: refuse instead of exec(); unregister register_feature / new_feature_version - main: default HF_HUB_OFFLINE=1 so weights load only from the preloaded cache - tests: test_no_code_exec.py; update feature catalog tests Signed-off-by: Dhaval Patel <pateldha@us.ibm.com>
Data confinment Signed-off-by: Dhaval Patel <pateldha@us.ibm.com>
|
@ShuxinLin, this PR is confined to the request.
|
There was a problem hiding this comment.
Thanks for closing the code-execution routes. An import allowlist plus confining file pointers in refs._path is the right approach. Three changes needed before merge (details inline):
- The allowlist blocks features the server still advertises (
resolver.py:33):transforms, tabularsktime_classtransforms andtsfm_regressionnow always fail. _pathcan still be bypassed (refs.py:46): it checks a name and the file is opened later, so a swapped symlink or a FIFO gets through.- Writes can land outside the data roots (
composition.py:209):finetune.training_args.output_dirisn't confined.
I have a few smaller follow-ups and will raise them once these land.
| """ | ||
| if "://" in ref and not ref.startswith("file://"): | ||
| raise ValueError("only local file pointers are supported") | ||
| p = os.path.realpath(ref[7:] if ref.startswith("file://") else ref) |
There was a problem hiding this comment.
Confinement can still be bypassed. realpath() checks the path here, but callers open it by name later (pd.read_csv(p)). /workspace-share is writable from the code sandbox, so a symlink swapped in between the check and the open escapes the roots. Non-regular files also pass, so a FIFO named x.csv makes read_csv block forever.
Suggested fix: open first, then validate what was opened. os.open(p, os.O_RDONLY | os.O_NONBLOCK), require stat.S_ISREG(os.fstat(fd).st_mode), check the fd's real path (os.readlink(f"/proc/self/fd/{fd}")) against the roots, and pass the open file to pandas. Every file-reading tool goes through _path, so this covers them all.
| path = os.path.abspath(os.path.expanduser(path)) | ||
| from ..io import refs | ||
|
|
||
| path = refs._path(str(path)) # confined to the data roots |
There was a problem hiding this comment.
Writes are only confined for save_to. _with_finetune (line 91) merges the finetune block into the estimator params, and TinyTimeMixer/PatchTST pass training_args straight to transformers.TrainingArguments. So finetune: {training_args: {output_dir: ...}} makes the Trainer write checkpoints, including the pickled training_args.bin, to any writable path.
Suggest overriding output_dir with a server-chosen directory under WORKDIR and rejecting any other path-valued training args.
…then-verify reads, confine training_args) Signed-off-by: Dhaval Patel <pateldha@us.ibm.com>
…s, open-then-verify reads, confine training_args)" This reverts commit 08e817b. Signed-off-by: Dhaval Patel <pateldha@us.ibm.com>
…ols advertise Signed-off-by: Dhaval Patel <pateldha@us.ibm.com>
| """ | ||
| ns: Dict[str, Any] = {} | ||
| try: | ||
| exec(code, ns) # noqa: S102 - intentional; sandbox in production |
There was a problem hiding this comment.
@ShuxinLin this is a main thing we disabled.

Summary
Closes the security issue raised in review: the TSFM recipe tools imported any Python class named in
sktime_class, so an agent could run arbitrary code inside the MCP container. In one run GLM-5.3 passed"sktime_class": "subprocess.Popen"with a shell command and read the output back throughtsfm__data_quality, exposing raw data, the scorer and the DB password.This PR keeps the recipe tools (the TSFM scenarios measure model selection and use) and removes every path by which the server runs agent-supplied code or reads files outside its data folders.
Routes closed
sktime_class/ nested_target_imported any dotted path. Reached fromrun_recipe,run_tabular_recipe,evaluate,run_plan, thefinetuneblock, tabular transforms, catalog cards rewritten viaupdate_model, andparam_space.introspectsubstrate/resolver.py, checked before import. All routes go through this one function.register_feature/new_feature_versionran feature-cardcodewithexec()feature_runner.load_programnow refuses; both tools removed from the MCP surface. The code-carrying transform cards were removed from the catalog (v1 snapshot kept).MOIRAIForecasterunpicklesmodel.ckpt(weights_only=False) from any non-Salesforce Hub repo an agent namesHF_HUB_OFFLINE=1; weights load only from the preloaded image cache.dataset_pathread any file (e.g./proc/self/environ), fetchedhttp://URLs, followed symlinks out; plan step ids path-traversed on write;save_towrote anywhereio/refs._pathconfines file pointers to the data folders (TSFM workdir, temp dir,/workspace-share,artifacts/output), resolves symlinks first, refuses URLs. Output names sanitized;save_toconfined.Files
substrate/resolver.py:ALLOWED_CLASSES+ check in_import_targetreasoning/param_space.py: routes its import through the same checkengine/feature_runner.py:load_programrefuses instead ofexecmain.py: unregisterregister_feature/new_feature_version; defaultHF_HUB_OFFLINE=1io/refs.py,engine/composition.py: file-pointer confinementtests/test_no_code_exec.py(new),tests/conftest.py,tests/test_feature_catalog_store.pyfeature_catalog_orig.jsonkept as the v1 snapshotTesting
pytest servers/tsfm/tests: 133 passed, 4 skipped.test_no_code_exec.pyreplays the GLM-5.3Popenpayload down each route and asserts both an error and that a canary file was never written. Against the pre-fix code these tests fail and the payload executes.smoke_forecast.py,smoke_tasks.py) onruntime:devwith--network noneand the full private catalog (59 cards): same coverage as the 2026-10-01 baseline (38 forecast, 11 task cards), no allowlist refusals, task suite unchanged. Moirai 2.0 reproduces byte for byte. Two cards moved PASS to WARN at the 1.0 boundary (chronos-t5-mini 1.01, moirai-1.1-R-small 1.00), from sampling noise in those models, not this change.Operator notes
ALLOWED_CLASSES, or setTSFM_EXTRA_ESTIMATOR_CLASSES(comma-separated).HF_HUB_OFFLINE=0.stirrup-agent --workspace-diroutside the temp dir: add that folder toTSFM_DATA_ROOTS(os.pathsep-separated; replaces the default list).Out of scope
sktime_classon catalog cards (the runtime check already blocks it).torchfloor to>=2.6(the lock already pins 2.10).