{"record":{"id":"51abb1d97f4ef1da","repo":"Zie619/n8n-workflows","slug":"access-denied","errorCode":null,"errorMessage":"Access denied","messagePattern":"Access denied","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"warning","filePath":"api_server.py","lineNumber":421,"sourceCode":"                        )\n                        continue\n\n        if not json_files:\n            print(f\"File {filename} not found in workflows directory\")\n            raise HTTPException(\n                status_code=404, detail=f\"Workflow file '{filename}' not found\"\n            )\n\n        file_path = json_files[0]\n\n        # Final security check: Ensure file is within workflows directory\n        try:\n            file_path.resolve().relative_to(workflows_path)\n        except ValueError:\n            print(\n                f\"Security: Blocked final attempt to access file outside workflows: {file_path}\"\n            )\n            raise HTTPException(status_code=403, detail=\"Access denied\")\n\n        return FileResponse(\n            str(file_path), media_type=\"application/json\", filename=filename\n        )\n    except HTTPException:\n        raise\n    except Exception as e:\n        print(f\"Error downloading workflow {filename}: {str(e)}\")\n        raise HTTPException(\n            status_code=500, detail=f\"Error downloading workflow: {str(e)}\"\n        )\n\n\n@app.get(\"/api/workflows/{filename}/diagram\")\nasync def get_workflow_diagram(filename: str, request: Request):\n    \"\"\"Get Mermaid diagram code for workflow visualization.\"\"\"\n    try:\n        # Security: Validate filename to prevent path traversal","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/Zie619/n8n-workflows/blob/94007c1445d9258a7da116646b79473e7c7c3282/api_server.py#L403-L439","documentation":"A 403 from GET /api/workflows/{filename}/download raised by the final defense-in-depth check: after a candidate file is found, file_path.resolve().relative_to(workflows_path) must succeed. If resolving the path (following symlinks and normalizing '..') lands anywhere outside the workflows directory, ValueError is caught and HTTPException(403, 'Access denied') is raised, with a server log 'Security: Blocked final attempt to access file outside workflows'.","triggerScenarios":"A symlink inside workflows/<subdir>/ pointing to a file elsewhere on disk; a race where the path component is swapped for a symlink between the scan and the final check; encoded traversal that survived the earlier scan check.","commonSituations":"Users 'organizing' workflows with symlinks into another repo or home directory; shared mounts; adversarial probing of the download endpoint.","solutions":["Replace symlinks with real files (or hardlinks/copies) inside workflows/ subdirectories.","Audit workflows/ with: find workflows -type l — every link targeting outside the tree will trip this check.","Keep the containment check intact; it is the last line of defense for the download route.","If symlinked content is a legitimate requirement, configure a dedicated copy or bind-mount the source under workflows/."],"exampleFix":"# before (symlink causes 403)\nln -s ~/elsewhere/flow.json workflows/cat/flow.json\n\n# after (real file inside the tree)\ncp ~/elsewhere/flow.json workflows/cat/flow.json","handlingStrategy":"validation","validationCode":"import os\n\ndef no_escaping_symlinks(workflows_dir):\n    root = os.path.realpath(workflows_dir)\n    for dirpath, _dirs, files in os.walk(workflows_dir):\n        for f in files:\n            p = os.path.realpath(os.path.join(dirpath, f))\n            if not p.startswith(root + os.sep):\n                return False\n    return True","typeGuard":null,"tryCatchPattern":"try:\n    client.get(f'/api/workflows/{name}/download')\nexcept HTTPError as e:\n    if e.response.status_code == 403:\n        audit_for_symlinks('workflows')  # containment blocked this path","preventionTips":["Never symlink workflow files from outside workflows/.","Run 'find workflows -type l' in CI to catch escaping links.","Treat 403 'Access denied' as a deployment hygiene issue, not a client bug."],"tags":["fastapi","security","symlink","path-traversal","http-403"],"backgroundTag":null,"analyzedSha":"94007c1445d9258a7da116646b79473e7c7c3282","analyzedAt":"2026-08-15T04:10:37.591Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}