Skip to content

Deny import paths that allow code execution when given as values - #959

Merged
mauvilsa merged 2 commits into
mainfrom
import-path-denylist
Aug 21, 2026
Merged

Deny import paths that allow code execution when given as values#959
mauvilsa merged 2 commits into
mainfrom
import-path-denylist

Conversation

@mauvilsa

Copy link
Copy Markdown
Owner

What does this PR do?

Motivation

Resolving a class_path imports a module and instantiates a class with config-supplied init_args, so a config effectively decides what code runs. That's fine for trusted configs but a risk when configs come from an untrusted source. This adds an import-path denylist that limits what a value can reach.

What's new

  • Two settings in set_parsing_settings: import_path_denylist and import_path_allowlist. Entries are dot paths; an entry covers everything beneath it (os also denies os.system), the most specific entry wins (so functools.partial can be allowed out of a denied functools), and * denies everything so only the allowlist is importable.
  • Default denylist of standard-library paths that grant code execution, untrusted deserialization, import/installation, callable reflection, destructive filesystem/process access, file access, and network/exfil (e.g. os, subprocess, pickle, importlib, functools, shutil, builtins.open/io/sqlite3/logging.FileHandler, urllib/ftplib/socket).
  • Scope: only paths coming from values are checked — class_path, Callable, type[...], types.ModuleType, and type expressions given via config/CLI/env. Paths from code (annotations, defaults) are never checked.
  • Definition-site re-check: a value is denied by where its object is actually defined, not just the path used to reach it. This catches a denied module/function re-exported under a benign name (some.module.os.systemposix.system), and, for objects with no import path of their own, the callable they reach — the bound function of a functools.partial and the defining class of a callable instance — so a denied callable can't be smuggled past by binding or wrapping it under an allowed name.
  • Deprecation transition: until v5.0.0 a denied path only warns and the import proceeds, so existing configs don't break. Passing any value to import_path_denylist/import_path_allowlist (an empty list included) opts into the failing behavior now; from v5.0.0 it always fails.

Also fixed (related type handling)

  • Callable[..., SomeClass] and instance-factory protocols now accept only subclasses of the return type / functions that return it, instead of any callable-instance class or any function.
  • Instance-factory protocols with a zero-parameter __call__ now yield a factory instead of instantiating the class.

Docs & tests

  • New "Untrusted configs" section in DOCUMENTATION.rst, CHANGELOG entries, and an explicit note that a denylist is a mitigation, not a sandbox — only * plus a narrow allowlist gives a real bound.
  • New test_import_paths.py covering settings validation, policy resolution, *-mode, the definition-site re-check (including partials/instances), the default file/network entries, and end-to-end parsing; plus deprecation-transition tests.

Before submitting

  • Did you read the contributing guideline?
  • If you used a coding agent, did you fully understand and validate all generated code and ensure it follows the contributing guidelines?
  • Did you update the documentation? (readme and public docstrings)
  • Did you write unit tests such that there is 100% coverage on related code? (required for bug fixes and new features)
  • Did you verify that new and existing tests pass locally?
  • If this is a bug fix, did you verify that the tests fail without the code fix?
  • Did you make sure that all changes preserve backward compatibility?
  • Did you update the CHANGELOG including a pull request link? (not for typos, docs, test updates, or minor internal changes/refactors)

@mauvilsa mauvilsa added bug Something isn't working enhancement New feature or request labels Aug 21, 2026
Comment thread jsonargparse_tests/test_typehints.py Dismissed
@mauvilsa
mauvilsa deployed to sonarcloud August 21, 2026 13:11 — with GitHub Actions Active
@codecov

codecov Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (a348bbf) to head (9be964e).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #959   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           27        27           
  Lines         8671      8762   +91     
=========================================
+ Hits          8671      8762   +91     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@mauvilsa
mauvilsa deployed to sonarcloud August 21, 2026 14:11 — with GitHub Actions Active
@sonarqubecloud

Copy link
Copy Markdown

@mauvilsa
mauvilsa merged commit 09f2428 into main Aug 21, 2026
32 checks passed
@mauvilsa
mauvilsa deleted the import-path-denylist branch August 21, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants