Skip to content

Enhance local Python executor security by blocking dunder calls #1551

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 4, 2025

Conversation

nnfrog
Copy link
Contributor

@nnfrog nnfrog commented Jul 13, 2025

Updating the local_python_executor to prevent execution while Python code attempt to access dunder methods (and not just dunder attributes).
A developer can authorize a dunder method the same way as he authorizes tools, for example:

custom_executor = LocalPythonExecutor([])
custom_executor.send_tools({"__getattribute__":"__getattribute__","__subclasses__":"__subclasses__"})

nnfrog and others added 5 commits July 13, 2025 10:17
updating the local_python_executor to prevent execution while Python code attempt to access dunder methods.
A developer can authorize a dunder method the same way as he authorizes tools, for example:
custom_executor = LocalPythonExecutor([])
custom_executor.send_tools({"__getattribute__":"__getattribute__","__subclasses__":"__subclasses__"})
Copy link
Member

@albertvillanova albertvillanova left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the security fix!

I think there are some cases where this fix is not working: see failing tests.

  • For example, what about a subclass calling super().__init__()? This PR does not allow it.

@nnfrog
Copy link
Contributor Author

nnfrog commented Jul 20, 2025

@albertvillanova thanks for the response.
This fix is functioning under the presumption that dunder methods are no different than tools, if a developer want to have some dunder method available he can explicitly allow it at his own risk - otherwise all the rest will be blocked.
About the "super" method's test I must say I am a little confused as the super method is blocked regardless of my fix - as it is not authorized by default method.
I am using "smolagents" version 1.19.0 and I got this error while attempting to run code with "super", regardless of my fix:
InterpreterError: Forbidden function evaluation: 'super' is not among the explicitly allowed tools or defined/imported in the preceding code

@albertvillanova
Copy link
Member

albertvillanova commented Jul 21, 2025

Thanks for the clarification, @nnfrog!

Just to clarify, I wasn't referring to super itself, but rather to the __init__ call: InterpreterError: Forbidden access to dunder function: __init__

FAILED tests/test_local_python_executor.py::TestEvaluatePythonCode::test_classes - smolagents.local_python_executor.InterpreterError: Code execution failed at line 'dog1 = Dog("Fido", 3, "Labrador")' due to: InterpreterError: Forbidden access to dunder function: __init__

That said, you're absolutely right that super is already blocked by default, so extending that behavior to also block __init__ makes the logic consistent.

More broadly, though, I wonder if we should consider allowing some basic built-in methods by default (such as super and __init__) to support simple class definitions/instantiations and typical Python constructs out of the box. That might help make the Python executor more useful for common use cases. It's always a challenge to strike the right balance between security and usability, especially in a execution environment.

In any case, I agree this could be discussed and handled separately in a dedicated PR. Would be great to hear your thoughts on that direction.

Copy link
Member

@albertvillanova albertvillanova left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I forgot to ask: do you have a mechanism in mind for explicitly allowing methods like __init__, for example? In the current design, tools/functions are passed via static_tools as a dictionary mapping function names to their corresponding callables; but in the case of __init__, there's no standalone callable we can provide in the same way.

I think this is something we should address directly in this PR, as it ties into the broader question of how we handle access to special methods. As I mentioned earlier, it's always a delicate balance between maintaining security and enabling useful functionality.

@nnfrog
Copy link
Contributor Author

nnfrog commented Jul 28, 2025

Hi @albertvillanova and sorry for the delay in my response
while init by itself is harmless, init+super can be an issue depending on context - this is why I'm more of a "let the devs decide" guy in my approach.

About how to implement it - I think a list with method names as strings, and then checking it against a func.name result could be a valid option WDYT?

@albertvillanova
Copy link
Member

albertvillanova commented Aug 1, 2025

Thanks for the suggestion: yes, I think that could work well.

A list of explicitly allowed method names (e.g., ["__init__"]), checked against func.__name__, would give us a simple and readable mechanism for selectively enabling special methods.

I'm happy to proceed with that direction if you are.

@albertvillanova albertvillanova changed the title Enhancing the local Python sandbox Enhancie local Python executor security by blocking dunder calls Aug 1, 2025
@albertvillanova albertvillanova changed the title Enhancie local Python executor security by blocking dunder calls Enhance local Python executor security by blocking dunder calls Aug 1, 2025
Copy link
Member

@albertvillanova albertvillanova left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the security enhancement!

I just added some comprehensive vulnerability tests covering various attack vectors through dunder methods:

  • Direct dunder method calls ('string'.__dir__())
  • Complex attack chains using __getattribute__ and __subclasses__
  • Subprocess execution attempts via class hierarchy traversal

@nnfrog
Copy link
Contributor Author

nnfrog commented Aug 4, 2025

Sure! You are welcome!
Looks great

@albertvillanova albertvillanova merged commit 6a42a87 into huggingface:main Aug 4, 2025
3 checks passed
@quitbug
Copy link

quitbug commented Aug 12, 2025

Hi, all. It looks to me that this issue and fix was inspired by a security report first filed on 6/16 (see https://huntr.com/bounties/8d993108-f56c-445e-8886-282288170b29). While I appreciate this critical vulnerability gets the attention that it deserves and finally fixed, I hope that Huggingface is not setting the precedence to decline bug bounty report but later fixing the issue.

@albertvillanova
Copy link
Member

albertvillanova commented Aug 13, 2025

@quitbug, thanks for raising your concern. I'd like to clarify the timeline here to avoid any misunderstanding.

This vulnerability was first reported to us on May 27 by the JFrog team. We assessed it as informative at the time, since the local Python executor is not intended to be a secure, sandboxed environment. Shortly afterwards, the JFrog team themselves proposed to open a pull request to address the issue, which led to the eventual fix you see here.

We take all security reports seriously and have no practice of declining a valid bug bounty report only to later fix it without acknowledgment. In this case, the report you linked was not the first disclosure of the issue, and the fix originated from the earlier JFrog report and PR.

CC: @Michellehbn

@quitbug
Copy link

quitbug commented Aug 13, 2025

As a matter of fact, the report was not marked as a duplicate of the JFrog issue, but another similar issue (from jackfromeast) which required a different fix. That has created the unnecessary misunderstanding. Now, things are clear. Thank you and everyone for the fix. :)

@albertvillanova
Copy link
Member

Thanks for your understanding, @quitbug.

Note that the JFrog report didn't come through huntr.com, but it was submitted via security@huggingface.co to our internal Zendesk system. So linking it as an exact duplicate wasn't possible. I believe it was instead linked to the most similar public report.

In any case, I'm glad everything is clear now, and I appreciate the constructive discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants