{"record":{"id":"8a84388d2a1efce5","repo":"n8n-io/n8n","slug":"read-only","errorCode":null,"errorMessage":"read-only","messagePattern":"read-only","errorType":"exception","errorClass":"AttributeError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/task-runner-python/src/task_executor.py","lineNumber":581,"sourceCode":"                return filtered.keys()\n\n            def values(self):\n                return filtered.values()\n\n            def items(self):\n                return filtered.items()\n\n            def get(self, key, default=None):\n                return filtered.get(key, default)\n\n            def __getattr__(self, name):\n                try:\n                    return filtered[name]\n                except KeyError:\n                    raise AttributeError(name) from None\n\n            def __setattr__(self, name, value):\n                raise AttributeError(\"read-only\")\n\n            def __delattr__(self, name):\n                raise AttributeError(\"read-only\")\n\n            def __repr__(self):\n                return f\"ImmutableBuiltins({len(filtered)} keys)\"\n\n        return _ImmutableBuiltins()\n\n    @staticmethod\n    def _sanitize_sys_modules(security_config: SecurityConfig):\n        safe_modules = {\n            \"builtins\",\n            \"__main__\",\n            \"sys\",\n            \"traceback\",\n            \"linecache\",\n            \"importlib\",","sourceCodeStart":563,"sourceCodeEnd":599,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/task-runner-python/src/task_executor.py#L563-L599","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nprint = lambda *a: None  // mutates builtins proxy -> AttributeError\n// after\n_my_print = lambda *a: None\n_my_print('debug')\n","handlingStrategy":"validation","validationCode":"# Reject snippets that assign to builtins attribute-style.\nimport ast, re\nforbidden = re.compile(r'^\\\\s*builtins\\\\.\\\\w+\\\\s*=')\nif any(forbidden.match(line) for line in user_code.splitlines()):\n    raise ValueError('do not assign to builtins')\n","typeGuard":null,"tryCatchPattern":"try:\n    exec_sandboxed(user_code)\nexcept AttributeError as e:\n    if str(e) == 'read-only':\n        raise UserError('builtins are immutable in the sandbox')\n    raise\n","preventionTips":["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."],"tags":["python","sandbox","security","builtins","immutability"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}