{"record":{"id":"2be3f4687041c985","repo":"HKUDS/Vibe-Trading","slug":"invalid-shadow-id","errorCode":null,"errorMessage":"invalid shadow_id","messagePattern":"invalid shadow_id","errorType":"validation","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"agent/src/api/uploads_routes.py","lineNumber":103,"sourceCode":"        import sys as _sys\n\n        host = _sys.modules.get(\"api_server\") or _sys.modules.get(\"agent.api_server\")\n        return host.MAX_UPLOAD_SIZE if host else MAX_UPLOAD_SIZE\n\n    def _host_chunk_size() -> int:\n        import sys as _sys\n\n        host = _sys.modules.get(\"api_server\") or _sys.modules.get(\"agent.api_server\")\n        return host._UPLOAD_CHUNK_SIZE if host else _UPLOAD_CHUNK_SIZE\n\n    @app.get(\"/shadow-reports/{shadow_id}\", dependencies=[Depends(require_auth)])\n    async def get_shadow_report(shadow_id: str, format: str = \"html\"):\n        \"\"\"Serve a rendered Shadow Account report.\n\n        Reports live under ``~/.vibe-trading/shadow_reports/<shadow_id>.{html,pdf}``.\n        \"\"\"\n        if not _SHADOW_ID_RE.match(shadow_id):\n            raise HTTPException(status_code=400, detail=\"invalid shadow_id\")\n        if format not in (\"html\", \"pdf\"):\n            raise HTTPException(status_code=400, detail=\"format must be html or pdf\")\n\n        reports_dir = Path.home() / \".vibe-trading\" / \"shadow_reports\"\n        path = reports_dir / f\"{shadow_id}.{format}\"\n        if not path.exists():\n            raise HTTPException(status_code=404, detail=f\"Shadow report not found: {shadow_id}.{format}\")\n\n        media_type = \"text/html; charset=utf-8\" if format == \"html\" else \"application/pdf\"\n        return FileResponse(\n            path,\n            media_type=media_type,\n            headers={\"Content-Disposition\": f'inline; filename=\"{shadow_id}.{format}\"'},\n        )\n\n    @app.post(\"/upload\", dependencies=[Depends(require_auth)])\n    async def upload_file(file: UploadFile):\n        \"\"\"Upload any document or data file (max 50MB).","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/api/uploads_routes.py#L85-L121","documentation":"The shadow report endpoint validates shadow_id against a regex before using it in a filesystem path. IDs failing the pattern are rejected with 400 to prevent path traversal and unexpected file lookups.","triggerScenarios":"GET /uploads/shadow-report?shadow_id=../../etc/passwd or shadow_id with spaces, slashes, or characters outside the allowed ID pattern; also fabricated/guessed IDs.","commonSituations":"Client truncating or mutating the ID returned at report creation, copy-paste with whitespace/newlines, or injection attempts hitting the path construction.","solutions":["Use the exact shadow_id returned when the shadow report was generated","Trim whitespace/newlines from user-supplied IDs before the request","Check the ID matches the expected pattern (alphanumeric/dash style) client-side"],"exampleFix":"// before\nfetch(`/uploads/shadow-report?shadow_id=${rawInput}`)\n// after\nconst id = rawInput.trim();\nif (!/^[A-Za-z0-9_-]+$/.test(id)) throw new Error('bad shadow id');\nfetch(`/uploads/shadow-report?shadow_id=${id}`)","handlingStrategy":"type-guard","validationCode":"import re\nassert re.fullmatch(r'[A-Za-z0-9_-]+', shadow_id), 'invalid shadow_id'","typeGuard":"def is_valid_shadow_id(sid: str) -> bool:\n    return bool(re.fullmatch(r'[A-Za-z0-9_-]+', sid or ''))","tryCatchPattern":null,"preventionTips":["Pass through the exact ID returned at report creation","Trim user input before sending"],"tags":["validation","path-traversal","uploads","api"],"backgroundTag":"request-parameter-validation-failed","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}