lfnovo/open-notebook · warning · InvalidInputError

Invalid order_by clause: '{clause.strip()}'

Error message

Invalid order_by clause: '{clause.strip()}'

What it means

_validate_order_by() rejects multi-token clauses that are malformed: either the field fails the allowed-field pattern or the direction token isn't one of the allowed ASC/DESC keywords; clauses with 3+ tokens always hit this branch. It is the injection/validation guard for the order_by string that gets interpolated into SurrealQL.

Source

Thrown at open_notebook/domain/base.py:63

        delegating to `get_all()`) must route through this so the allowlist
        can't silently drift between call sites.
        """
        allowed_field_pattern = re.compile(r"^[a-z_][a-z0-9_]*$")
        allowed_directions = {"asc", "desc"}

        clauses = [c.strip() for c in order_by.split(",")]
        validated_clauses = []
        for clause in clauses:
            parts = clause.strip().split()
            if len(parts) == 1:
                if not allowed_field_pattern.match(parts[0].lower()):
                    raise InvalidInputError(f"Invalid order_by field: '{parts[0]}'")
                validated_clauses.append(parts[0].lower())
            elif len(parts) == 2:
                if not allowed_field_pattern.match(
                    parts[0].lower()
                ) or parts[1].lower() not in allowed_directions:
                    raise InvalidInputError(
                        f"Invalid order_by clause: '{clause.strip()}'"
                    )
                validated_clauses.append(f"{parts[0].lower()} {parts[1].lower()}")
            else:
                raise InvalidInputError(f"Invalid order_by clause: '{clause.strip()}'")

        return ", ".join(validated_clauses)

    @classmethod
    async def get_all(cls: Type[T], order_by=None) -> List[T]:
        try:
            # If called from a specific subclass, use its table_name
            if cls.table_name:
                target_class = cls
                table_name = cls.table_name
            else:
                # This path is taken if called directly from ObjectModel
                raise InvalidInputError(

View on GitHub (pinned to a7de90d38a)

Solutions

  1. Format clauses strictly as '<field> asc|desc' (or bare field), comma-separated for multiple sorts
  2. Map UI sort choices to the two allowed direction keywords before calling get_all
  3. Whitelist the field against the model's fields and reject anything with extra tokens

Example fix

// before
order_by = f"{field} {direction}"  # direction could be 'ascending'
// after
DIRS = {'asc': 'asc', 'ascending': 'asc', 'desc': 'desc', 'descending': 'desc'}
order_by = f"{field} {DIRS[direction.lower()]}"
Defensive patterns

Strategy: validation

Validate before calling

DIRS = {'asc', 'desc'}
for clause in raw.split(','):
    parts = clause.strip().lower().split()
    ok = (len(parts) == 1 and parts[0] in allowed_fields) or \
         (len(parts) == 2 and parts[0] in allowed_fields and parts[1] in DIRS)
    if not ok:
        raise InvalidInputError(f'Invalid order_by clause: {clause}')

Type guard

def is_valid_order_clause(clause: str, allowed_fields: set[str]) -> bool:
    parts = clause.strip().lower().split()
    return ((len(parts) == 1 and parts[0] in allowed_fields) or
            (len(parts) == 2 and parts[0] in allowed_fields and parts[1] in ('asc', 'desc')))

Try / catch

try:
    items = await Model.get_all(order_by=raw)
except InvalidInputError:
    items = await Model.get_all()  # fall back to default ordering

Prevention

When it happens

Trigger: Passing 'created_at ASC; DROP' (3+ tokens), 'created-at asc' (bad field), or 'created_at ascending' (direction not in allowed set); concatenating user input into order_by without normalizing; sending empty middle tokens like 'created_at asc extra'.

Common situations: Frontend building sort strings from multiple params; users typing free-text sort expressions; direction values like 'ascending'/'up' instead of 'asc'; injection attempts targeting the ORDER BY clause.

Related errors


AI-assisted analysis of lfnovo/open-notebook@a7de90d38a (2026-08-27). Data as JSON: /api/errors/a7e6e7a80ad4713d. Report an issue: GitHub.