{"record":{"id":"625de4d0bbf5ea38","repo":"lfnovo/open-notebook","slug":"invalid-order-by-field-order-by-allowed-fiel","errorCode":null,"errorMessage":"Invalid order_by field: '{order_by}'. Allowed fields: {', '.join(sorted(allowed_fields))}","messagePattern":"Invalid order_by field: '(.+?)'\\. Allowed fields: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"api/routers/notebooks.py","lineNumber":75,"sourceCode":"        last_viewed_at=str(row.get(\"last_viewed_at\", \"\")),\n    )\n\n\n@router.get(\"/notebooks\", response_model=List[NotebookResponse])\nasync def get_notebooks(\n    archived: Optional[bool] = Query(None, description=\"Filter by archived status\"),\n    order_by: str = Query(\"updated desc\", description=\"Order by field and direction\"),\n):\n    \"\"\"Get all notebooks with optional filtering and ordering.\"\"\"\n    try:\n        # Validate order_by against allowlist to prevent SurrealQL injection\n        allowed_fields = {\"name\", \"created\", \"updated\"}\n        allowed_directions = {\"asc\", \"desc\"}\n\n        parts = order_by.strip().lower().split()\n        if len(parts) == 1:\n            if parts[0] not in allowed_fields:\n                raise HTTPException(\n                    status_code=400,\n                    detail=f\"Invalid order_by field: '{order_by}'. Allowed fields: {', '.join(sorted(allowed_fields))}\",\n                )\n            validated_order_by = parts[0]\n        elif len(parts) == 2:\n            if parts[0] not in allowed_fields or parts[1] not in allowed_directions:\n                raise HTTPException(\n                    status_code=400,\n                    detail=f\"Invalid order_by: '{order_by}'. Allowed fields: {', '.join(sorted(allowed_fields))}. Allowed directions: asc, desc\",\n                )\n            validated_order_by = f\"{parts[0]} {parts[1]}\"\n        else:\n            raise HTTPException(\n                status_code=400,\n                detail=f\"Invalid order_by format: '{order_by}'. Expected 'field' or 'field direction'\",\n            )\n\n        # Build the query with counts","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/lfnovo/open-notebook/blob/a7de90d38aaf18ee85fd661854d35c11e44613e2/api/routers/notebooks.py#L57-L93","documentation":"400 from GET /api/v1/notebooks when the single-word order_by query param's field is not in {name, created, updated}. The router validates order_by manually: lowercased, split on whitespace, first token must be an allowed field.","triggerScenarios":"GET /notebooks?order_by=title, ?order_by=Name (actually works because of .lower(), but 'title', 'id', 'notebook' fail), or any single word not in the allow-list.","commonSituations":"Frontend sort dropdown using a column key that isn't one of the three allowed fields; copied API examples with wrong field names.","solutions":["Use only name, created, or updated as the field","Optional direction is supported as second word: 'created desc'","Map UI sort keys to these three fields in the client"],"exampleFix":"// before\nGET /api/v1/notebooks?order_by=title\n// after\nGET /api/v1/notebooks?order_by=name desc","handlingStrategy":"validation","validationCode":"const FIELDS = ['name','created','updated'];\nconst parts = orderBy.trim().toLowerCase().split(/\\s+/);\nif (!FIELDS.includes(parts[0])) throw new Error(`order_by field must be one of ${FIELDS.join(', ')}`);","typeGuard":"const isValidOrderBy = (s: string) =>\n  /^(name|created|updated)( (asc|desc))?$/.test(s.trim().toLowerCase());","tryCatchPattern":"try { await api.getNotebooks({order_by: q}); } catch (e) { if (e.status === 400 && /order_by/.test(e.detail)) refetchSorted('created'); }","preventionTips":["Constrain sort dropdowns to the three allowed fields","Validate order_by client-side with a regex","Fall back to default sort on 400"],"tags":["notebooks","validation","http-400","query-params"],"backgroundTag":"invalid-query-parameter","analyzedSha":"a7de90d38aaf18ee85fd661854d35c11e44613e2","analyzedAt":"2026-08-27T02:39:58.166Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}