HKUDS/Vibe-Trading · error · HTTPException

invalid shadow_id

Error message

invalid shadow_id

What it means

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.

Source

Thrown at agent/src/api/uploads_routes.py:103

        import sys as _sys

        host = _sys.modules.get("api_server") or _sys.modules.get("agent.api_server")
        return host.MAX_UPLOAD_SIZE if host else MAX_UPLOAD_SIZE

    def _host_chunk_size() -> int:
        import sys as _sys

        host = _sys.modules.get("api_server") or _sys.modules.get("agent.api_server")
        return host._UPLOAD_CHUNK_SIZE if host else _UPLOAD_CHUNK_SIZE

    @app.get("/shadow-reports/{shadow_id}", dependencies=[Depends(require_auth)])
    async def get_shadow_report(shadow_id: str, format: str = "html"):
        """Serve a rendered Shadow Account report.

        Reports live under ``~/.vibe-trading/shadow_reports/<shadow_id>.{html,pdf}``.
        """
        if not _SHADOW_ID_RE.match(shadow_id):
            raise HTTPException(status_code=400, detail="invalid shadow_id")
        if format not in ("html", "pdf"):
            raise HTTPException(status_code=400, detail="format must be html or pdf")

        reports_dir = Path.home() / ".vibe-trading" / "shadow_reports"
        path = reports_dir / f"{shadow_id}.{format}"
        if not path.exists():
            raise HTTPException(status_code=404, detail=f"Shadow report not found: {shadow_id}.{format}")

        media_type = "text/html; charset=utf-8" if format == "html" else "application/pdf"
        return FileResponse(
            path,
            media_type=media_type,
            headers={"Content-Disposition": f'inline; filename="{shadow_id}.{format}"'},
        )

    @app.post("/upload", dependencies=[Depends(require_auth)])
    async def upload_file(file: UploadFile):
        """Upload any document or data file (max 50MB).

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Use the exact shadow_id returned when the shadow report was generated
  2. Trim whitespace/newlines from user-supplied IDs before the request
  3. Check the ID matches the expected pattern (alphanumeric/dash style) client-side

Example fix

// before
fetch(`/uploads/shadow-report?shadow_id=${rawInput}`)
// after
const id = rawInput.trim();
if (!/^[A-Za-z0-9_-]+$/.test(id)) throw new Error('bad shadow id');
fetch(`/uploads/shadow-report?shadow_id=${id}`)
Defensive patterns

Strategy: type-guard

Validate before calling

import re
assert re.fullmatch(r'[A-Za-z0-9_-]+', shadow_id), 'invalid shadow_id'

Type guard

def is_valid_shadow_id(sid: str) -> bool:
    return bool(re.fullmatch(r'[A-Za-z0-9_-]+', sid or ''))

Prevention

When it happens

Trigger: 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.

Common situations: Client truncating or mutating the ID returned at report creation, copy-paste with whitespace/newlines, or injection attempts hitting the path construction.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/2be3f4687041c985. Report an issue: GitHub.