n8n-io/n8n · error · AttributeError
read-only
Error message
read-only
What it means
AttributeError('read-only') is raised by _ImmutableBuiltins.__setattr__ when sandboxed user code attempts to assign to an attribute on the builtins proxy, e.g. 'print = something' through attribute syntax. The _ImmutableBuiltins class wraps the filtered builtins dict to prevent user code from mutating global builtins, while still allowing read access.
Source
Thrown at packages/@n8n/task-runner-python/src/task_executor.py:581
return filtered.keys()
def values(self):
return filtered.values()
def items(self):
return filtered.items()
def get(self, key, default=None):
return filtered.get(key, default)
def __getattr__(self, name):
try:
return filtered[name]
except KeyError:
raise AttributeError(name) from None
def __setattr__(self, name, value):
raise AttributeError("read-only")
def __delattr__(self, name):
raise AttributeError("read-only")
def __repr__(self):
return f"ImmutableBuiltins({len(filtered)} keys)"
return _ImmutableBuiltins()
@staticmethod
def _sanitize_sys_modules(security_config: SecurityConfig):
safe_modules = {
"builtins",
"__main__",
"sys",
"traceback",
"linecache",
"importlib",View on GitHub (pinned to 5ac6606e81)
Solutions
- Do not attempt to reassign builtins; instead bind a local variable (e.g. 'my_print = ...; my_print(...)') or wrap the call inside a helper function in user code.
- If redirection of output is the goal, use the runner's supported print_args / stdout capture mechanism rather than replacing print.
- Audit any imported third-party code for builtins-mutation patterns and replace them with non-mutating equivalents.
Example fix
// before
print = lambda *a: None // mutates builtins proxy -> AttributeError
// after
_my_print = lambda *a: None
_my_print('debug')
Defensive patterns
Strategy: validation
Validate before calling
# Reject snippets that assign to builtins attribute-style.
import ast, re
forbidden = re.compile(r'^\\s*builtins\\.\\w+\\s*=')
if any(forbidden.match(line) for line in user_code.splitlines()):
raise ValueError('do not assign to builtins')
Try / catch
try:
exec_sandboxed(user_code)
except AttributeError as e:
if str(e) == 'read-only':
raise UserError('builtins are immutable in the sandbox')
raise
Prevention
- Lint user snippets for builtins-mutation patterns before submission.
- Use local variables instead of mutating globals.
- Document the sandbox's immutability guarantees for users.
- Review imported libraries for builtins-patching behavior.
When it happens
Trigger: User Python code in the sandbox does something like 'builtins.print = my_print' (or relies on attribute assignment to the builtins object exposed by the sandbox), triggering __setattr__ on the immutable proxy.
Common situations: A user tries to monkey-patch a builtin for convenience (e.g. redirecting print), imports code that patches builtins at module load, or runs a snippet copy-pasted from outside n8n that mutates globals.
Related errors
- Security violation detected
- Security violations detected
- String pattern accessing '{attr}' is disallowed, because it
- Cannot access "${name}" via expression extension due to secu
- ${label} "${candidate}" escapes ${root}
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/8a84388d2a1efce5.
Report an issue: GitHub.