{"record":{"id":"77d8dd1f26466e44","repo":"getredash/redash","slug":"scripts-can-only-be-run-from-the-configured-script","errorCode":null,"errorMessage":"Scripts can only be run from the configured scripts directory","messagePattern":"Scripts can only be run from the configured scripts directory","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"redash/query_runner/script.py","lineNumber":65,"sourceCode":"            },\n            \"required\": [\"path\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"insecure_script\"\n\n    def __init__(self, configuration):\n        super(Script, self).__init__(configuration)\n\n        path = self.configuration.get(\"path\", \"\")\n        # If path is * allow any execution path\n        if path == \"*\":\n            return\n\n        # Poor man's protection against running scripts from outside the scripts directory\n        if path.find(\"../\") > -1:\n            raise ValueError(\"Scripts can only be run from the configured scripts directory\")\n\n    def test_connection(self):\n        pass\n\n    def run_query(self, query, user):\n        try:\n            script = query_to_script_path(self.configuration[\"path\"], query)\n            return run_script(script, self.configuration[\"shell\"])\n        except IOError as e:\n            return None, str(e)\n        except subprocess.CalledProcessError as e:\n            return None, str(e)\n\n\nregister(Script)\n","sourceCodeStart":47,"sourceCodeEnd":81,"githubUrl":"https://github.com/getredash/redash/blob/ca79fe988d81cdac9675b412f3dfcab107bc1fbc/redash/query_runner/script.py#L47-L81","documentation":"Raised in ScriptQueryRunner.__init__ (redash/query_runner/script.py:65) as ValueError when the configured scripts path contains '../', i.e. it is not confined to a single directory. This is a deliberate path-traversal guard: the runner only permits executing scripts from one whitelisted directory, and any configuration that could escape it is rejected at construction time.","triggerScenarios":"Saving a Script data source whose path setting includes a parent-directory segment, e.g. '/opt/redash/../scripts' or 'scripts/../../shared/scripts'. The only path that bypasses the check is the literal '*' (allow anything).","commonSituations":"Admins trying to point two data sources at a shared parent directory; symlinks not usable so '../' is attempted; mis-typed absolute path that accidentally contains a parent segment.","solutions":["Set the path to the exact directory containing the scripts with no '../' segments (canonicalize it first: realpath)","If you genuinely need multiple directories, use '*' with the understanding that it disables the confinement entirely","Alternatively symlink the needed scripts into a single directory and configure that directory"],"exampleFix":"# before\npath: /opt/redash/../shared/scripts\n# after\npath: /opt/shared/scripts","handlingStrategy":"validation","validationCode":"import os.path\npath = os.path.realpath(path)\nif path != '*' and '..' + os.sep in path:\n    raise ValueError('scripts path must not traverse parent directories')","typeGuard":null,"tryCatchPattern":"try:\n    runner = ScriptQueryRunner(configuration)\nexcept ValueError as e:\n    return error_response(400, 'Invalid scripts path: {}'.format(e))","preventionTips":["Canonicalize the directory with realpath before saving the data source","Validate the path in the settings form server-side: must be absolute, existing, no '..'","Use symlinks to aggregate scripts instead of traversal paths"],"tags":["script-runner","redash","path-traversal","configuration"],"backgroundTag":"path-traversal-blocked","analyzedSha":"ca79fe988d81cdac9675b412f3dfcab107bc1fbc","analyzedAt":"2026-08-28T18:32:34.637Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}