security: replace unrestricted setattr with allowlist in Python backend#28083
security: replace unrestricted setattr with allowlist in Python backend#28083titaiwangms merged 6 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Hardens the ONNX Runtime Python backend by preventing user-controlled kwargs from setting unsafe SessionOptions / RunOptions attributes via unrestricted setattr(), mitigating arbitrary file write and inference disruption vectors.
Changes:
- Added strict allowlists for
SessionOptions(raise on known-but-blocked attributes) andRunOptions(silently ignore non-allowlisted keys). - Updated backend API docstrings to document allowlisted behavior.
- Added regression tests covering blocked attributes, allowed attributes, and ignore behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| onnxruntime/python/backend/backend.py | Introduces _ALLOWED_SESSION_OPTIONS and raises on blocked known SessionOptions attributes. |
| onnxruntime/python/backend/backend_rep.py | Introduces _ALLOWED_RUN_OPTIONS and ignores all non-allowlisted RunOptions keys. |
| onnxruntime/test/python/onnxruntime_test_python_backend.py | Adds tests validating allowlist, block/raise semantics, and ignore behavior. |
| .github/skills/python-kwargs-setattr-security/SKILL.md | Documents the insecure pattern and the repo’s allowlist-based remediation approach. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Review: PR #28083 — security: replace unrestricted setattr with allowlist in Python backendThanks for the security fix — the core approach (frozenset allowlists replacing unrestricted 1.
|
|
Thanks @vraspar — all items addressed in the latest commits: Item 1 (kwargs split + RunOptions raise): Item 2 (run_model tests): Added 3 new tests: Item 3 (SKILL.md location): Moved from Item 4 (error message assertions): Added |
bf44dbe to
ace56ab
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add missing silently-ignore comment in backend.py prepare() - Improve exclusion comment style to per-attribute in backend.py - Fix circular self-reference in run_model() docstring - Rewrite SKILL.md rule 3 for cold readers - Add error message assertion to test_run_model_with_blocked_run_option_raises - Add tests for unknown kwargs through rep.run() and run_model() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Agent-signed-off: Developer (1003864e) [claude-opus-4.6] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add missing silently-ignore comment in backend.py prepare() - Improve exclusion comment style to per-attribute in backend.py - Fix circular self-reference in run_model() docstring - Rewrite SKILL.md rule 3 for cold readers - Add error message assertion to test_run_model_with_blocked_run_option_raises - Add tests for unknown kwargs through rep.run() and run_model() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Agent-signed-off: Developer (1003864e) [claude-opus-4.6] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
126e650 to
7298912
Compare
|
The CI failure is irrelevant. |
- Add missing silently-ignore comment in backend.py prepare() - Improve exclusion comment style to per-attribute in backend.py - Fix circular self-reference in run_model() docstring - Rewrite SKILL.md rule 3 for cold readers - Add error message assertion to test_run_model_with_blocked_run_option_raises - Add tests for unknown kwargs through rep.run() and run_model() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Agent-signed-off: Developer (1003864e) [claude-opus-4.6] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
7298912 to
49d368c
Compare
tianleiwu
left a comment
There was a problem hiding this comment.
One follow-up design suggestion: the new allowlists appear complete for the direct Python properties on SessionOptions and RunOptions, but they leave no safe path for advanced runtime configuration channels that are not regular properties. CUDA graph is the clearest example: enabling it is an EP provider option (enable_cuda_graph for CUDA EP or trt_cuda_graph_enable for TensorRT EP), and per-run control uses the run config entry gpu_graph_id. If the backend API is expected to remain useful for advanced performance scenarios, consider a second explicit allowlist for safe advanced configuration channels such as curated session_config_entries, run_config_entries, and provider-specific options, instead of limiting support to plain attributes only. That would preserve the security fix without permanently excluding safe features like CUDA graph.
titaiwangms
left a comment
There was a problem hiding this comment.
@tianleiwu Thank you for the forward-looking suggestion — this is exactly the kind of design thinking that helps the backend API stay useful beyond the immediate security fix.
I investigated this during the PR development and found that CUDA graph configuration (and advanced EP options in general) uses completely different APIs than the setattr() pattern this PR secures:
- Provider options flow through
SessionOptions.provider_options(a dict passed toadd_provider_options()), not Python properties - Session config entries use
add_session_config_entry(key, value)— a method call, not a settable attribute - Run config entries use
add_run_config_entry(key, value)— same pattern
Since these are dict/method-based APIs rather than Python properties, the current frozenset + setattr() allowlist pattern cannot cover them. A proper implementation would need:
- Per-EP allowlists for provider options (each EP has different valid keys with different security implications)
- Structured dict parameters (not flat kwargs) to distinguish
provider_options,session_config_entries, andrun_config_entries - Careful security auditing of each EP's option set — some EP options can trigger file I/O or network access
This is estimated at 200–400 lines of new code with its own test surface. I'd like to keep this PR focused on closing the immediate setattr() vulnerability and tackle the advanced config channels as a dedicated follow-up PR where each EP's options can be properly audited.
Would that approach work for you?
- Add missing silently-ignore comment in backend.py prepare() - Improve exclusion comment style to per-attribute in backend.py - Fix circular self-reference in run_model() docstring - Rewrite SKILL.md rule 3 for cold readers - Add error message assertion to test_run_model_with_blocked_run_option_raises - Add tests for unknown kwargs through rep.run() and run_model() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Agent-signed-off: Developer (1003864e) [claude-opus-4.6] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
49d368c to
3f41410
Compare
Pull request was closed
Fixes a critical security vulnerability where user-controlled kwargs were
applied to SessionOptions and RunOptions via unrestricted setattr(), allowing
arbitrary file overwrites and denial-of-inference.
Attack vector:
onnxruntime.backend.prepare(
model, optimized_model_filepath='/etc/passwd' # overwrites any file
)
The hasattr() check was not a security guard — it passed for all exposed
pybind properties including dangerous ones.
Fix:
- Add _ALLOWED_SESSION_OPTIONS frozenset (13 safe attrs) in backend.py
- Add _ALLOWED_RUN_OPTIONS frozenset (4 safe attrs) in backend_rep.py
- Replace hasattr/setattr loops with allowlist checks; blocked-but-known
attrs raise RuntimeError with a clear message listing allowed attrs
- run_model() passes **kwargs directly to prepare() and run() so both
paths enforce their own allowlist (no silent drops)
Blocked dangerous attributes:
- optimized_model_filepath: triggers Model::Save(), overwrites arbitrary
files with protobuf binary
- profile_file_prefix + enable_profiling: writes JSON to arbitrary path
- terminate (RunOptions): denies the current inference call
Tests:
- Add TestBackendKwargsAllowlist (11 tests) covering all exploit vectors,
safe attrs accepted, unknown attrs silently ignored, run_model() paths
- Use tempfile for all temp file/dir references (no hardcoded paths)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add missing silently-ignore comment in backend.py prepare() - Improve exclusion comment style to per-attribute in backend.py - Fix circular self-reference in run_model() docstring - Rewrite SKILL.md rule 3 for cold readers - Add error message assertion to test_run_model_with_blocked_run_option_raises - Add tests for unknown kwargs through rep.run() and run_model() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Agent-signed-off: Developer (1003864e) [claude-opus-4.6] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Agent-signed-off: Developer (1003864e) [claude-opus-4.6] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3f41410 to
d277b35
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- prepare() return type: InferenceSession -> OnnxRuntimeBackendRep - prepare() :param model: list all 5 accepted input types - prepare() description: references OnnxRuntimeBackendRep, not InferenceSession - run_model() :param model: list all 5 accepted input types - OnnxRuntimeBackendRep class docstring: clarify it wraps an InferenceSession - OnnxRuntimeBackendRep.run(): add :param inputs:, :param kwargs:, :return: docs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
a935997 to
342ff99
Compare
tianleiwu
left a comment
There was a problem hiding this comment.
Reviewed the latest head. The allowlist implementation and tests look good to me: the known file-write/inference-control attributes are blocked, unknown kwargs are still ignored where needed for the dual prepare/run forwarding path, and the earlier doc/PR-body mismatches have been addressed.
Summary
Fixes a critical security vulnerability in the ONNX Runtime Python backend where user-controlled
kwargswere applied toSessionOptionsandRunOptionsvia unrestrictedsetattr(), allowing arbitrary file overwrites.Vulnerability
The
prepare()method inonnxruntime/python/backend/backend.pyiterated over user-controlledkwargsand usedsetattr()to apply them directly to aSessionOptionsinstance. Thehasattr()check was not a security guard — it returnedTruefor all exposed properties including dangerous ones likeoptimized_model_filepath.Attack vector:
The same pattern existed in
backend_rep.pyforRunOptions.Fix
Replaced the unrestricted
hasattr/setattrloop in both files with strict allowlists:_ALLOWED_SESSION_OPTIONS(13 safe attrs) inbackend.py_ALLOWED_RUN_OPTIONS(4 safe attrs) inbackend_rep.pyBoth
SessionOptionsandRunOptionsuse identical validation logic with three outcomes for each kwarg key:setattr()(e.g.graph_optimization_level,log_severity_level)RuntimeError(e.g.optimized_model_filepath,terminate)nonexistent_option_xyz)Blocked dangerous attributes:
optimized_model_filepath— triggersModel::Save(), overwrites arbitrary files with protobuf binaryprofile_file_prefix— writes profiling JSON to arbitrary pathenable_profiling— causes uncontrolled file writes to cwdterminate(RunOptions) — denies the current inference calltraining_mode(RunOptions) — silently switches inference behavior in training buildsTests
Added
TestBackendKwargsAllowlistwith 13 new test methods covering all exploit vectors (blocked attrs raiseRuntimeError), safe allowlisted attrs (accepted), unknown attrs (silently ignored), and end-to-endrun_model()paths for both session and run options. All 15 tests pass (13 new + 2 pre-existing inTestBackend), no regressions.Files Changed
onnxruntime/python/backend/backend.pyonnxruntime/python/backend/backend_rep.pyonnxruntime/test/python/onnxruntime_test_python_backend.py.agents/skills/python-kwargs-setattr-security/SKILL.md