Skip to content

fix(core): bound arithmetic and sequence sizes in safe_expr - #947

Open
elijahbenizzy wants to merge 2 commits into
mainfrom
fix/safe-expr-bounds
Open

elijahbenizzy wants to merge 2 commits into
mainfrom
fix/safe-expr-bounds

Conversation

@elijahbenizzy

Copy link
Copy Markdown
Contributor

Condition.safe_expr() is documented for expressions from less-trusted sources, but its interpreter placed no bound on arithmetic. 10**10**10 or "a" * 10000000000 passed validation and then ran until the process was killed.

This change adds explicit bounds, documented in the docstring: expression size (500 nodes), exponent and base width for **, repeat count and result length for sequence */+, and a 4096-bit backstop on every integer result. Literal-only subtrees are checked at safe_expr() construction so the obvious cases fail early; everything else fails at run() with a plain ValueError. % with a string left operand is rejected for the same reason.

No existing test changed; 12 added.

Condition.safe_expr() allowed a handful of operators whose result size is
not bounded by the size of the expression: int ** int, sequence * n,
sequence + sequence, and printf-style % on strings. Bound them:

- at most 500 AST nodes per expression (checked before the recursive
  validator runs)
- int ** int: |exponent| <= 64 and base <= 64 bits; non-numeric operands
  rejected; float ** left to Python (bounded by float range)
- sequence * n: n <= 10_000 and result <= 1_000_000 elements; same result
  cap for sequence + sequence
- % is no longer applied to strings
- general backstop: no arithmetic step or int literal wider than 4096 bits

Literal-only subexpressions (e.g. 10 ** 10 ** 10) are checked at
safe_expr() call time; state-dependent values raise ValueError from run().
Docstring updated and tests added for each bound.
@elijahbenizzy
elijahbenizzy requested a review from skrawcz October 4, 2026 20:31
@github-actions github-actions Bot added the area/core Application, State, Graph, Actions label Oct 4, 2026
The node-count bound runs on the parsed tree, but on Python 3.11 the parser
itself gives up on a 3000-term chain with RecursionError before that check
is reached. Two complementary bounds, both applied before/around ast.parse:

- the expression string may be at most 10_000 characters
  (_SAFE_EXPR_MAX_SOURCE_CHARS)
- RecursionError / MemoryError raised by ast.parse are re-raised as
  ValueError('safe_expr: expression is too deeply nested'); SyntaxError
  is unchanged

Tests: the deep-chain node-count case now uses 600 terms (parses on every
supported Python), the 3000-term chain is asserted to raise ValueError
regardless of which bound trips first, and a 20_000-character expression
is rejected on length. Docstring updated.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Application, State, Graph, Actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant