Repository navigation
fix(agents): refuse frame and generator introspection in plot code - #12169
Merged
Merged
Conversation
Security review of 2026-10-14: a generator's frame reached the builtins without a dunder name. Ban frame, generator, coroutine, traceback and code-object attributes on any object. Catalogue eligibility unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Twelve newly denied attributes lack direct regression coverage, including every async-generator attribute.
1 open finding
What changed in this PR
This PR closes a plot-code validator escape through Python runtime introspection.
Changes:
- Denies frame, generator, coroutine, traceback, and code-object attributes.
- Adds validator regression cases and permits
df["open"]. - Documents the security fix and bounded exposure.
| File | Description |
|---|---|
agents/anyplot/code/validate.py |
Adds introspection attributes to the security denylist. |
tests/unit/agents/code/test_validate.py |
Adds security and allowed-key cases. |
changelog.d/validator-frame-introspection.md |
Records the security fix. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Copilot review of #12169: cover all 21 denylist spellings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Copilot review of #12169: Python 3.14 adds frame.f_generator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 14, 2026
Brings in #12169 (frame, generator, coroutine, traceback and code-object attributes in ESCAPE_ATTRIBUTES, both profiles) and #12170 (the round's changes and the user's standing requests). No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 14, 2026
The escape names #12169 added (gi_frame, f_builtins, tb_frame, ...) reach the DATA profile through ESCAPE_ATTRIBUTES: the Fable review's bypasses through a generator's or a traceback's frame never validate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
4 of 5 tasks
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 14, 2026
…12172) ## Summary - **Data from code, offline (owner decision of 2026-10-14).** A user can paste or type a snippet that generates the data (Python, JavaScript, R, a formula from a maths or physics textbook), or name a computation in words ("draw the Lorenz attractor"). The root translates it into Python, and the new root tool `run_data_code(code, source_language)` runs it in the renderer sandbox. The resulting table becomes the next dataset version, which the user confirms. - **Which tool computes the data depends on what the computation needs.** A closed formula over one or two ranges stays on the safe formula language (`generate_data`). Numerical integration (ODEs), iteration, random numbers, cumulative sums or several dependent steps go to `run_data_code`. - **Layers, each designed and reviewed by a Fable second opinion before and after the build:** - **Validator data profile** (`validate_data_code`): - Every SECURITY rule except savefig, and no plotting stack. - Allowed: `random`, scipy computing modules. Not allowed: sklearn, statsmodels. - Every private attribute on any receiver is banned. - A top-level `df` is required. - Literal-data caps: 20 per container and 40 per file, for numbers and for text, counting list repetition. The model cannot write rows from memory. - The `__main__` guard is stripped first. - It inherits #12167 and #12169: native code, raw memory, and frame introspection are refused. - **Harness data mode** (`harness.py --data`): - Imports everything first, then arms an audit hook that refuses every `open` (read too), every new import, directory probes, ctypes, subprocess, sockets, exec/fork, marshal and pickle, and every route to a frame. - Catches everything, `SystemExit` included. - Takes `df`, resets a non-default index, refuses MultiIndex columns, and writes `out.csv` under the shared caps. - **Renderer route:** an additive `POST /run-data` with its own wire models (`extra="forbid"`), the same caller check, slot, watchdog and kill path, and source ≤ 16 KiB, timeout ≤ 30 s. An old renderer answers 404, which the tool maps to `unavailable`. The deploy smoke runs a 2-row snippet. - **No raw snippet output reaches the model.** Errors are a fixed vocabulary (exception class and line only); stdout is discarded. - **One ingestion guard for pastes and code tables.** `dataset_guard.check_dataset` (pre-filter + dataset judge) runs on both, so cells generated by code are judged too. - **Rate and budget.** One rated start per turn through `ledger.rate_start` / `RunQueue.rate`; the plot pipeline will use the same helper when the queue becomes pipeline-only. Also 30 data runs per user per day (`AGENT_DAILY_DATA_RUNS`), at most 2 calls per turn, and a 6,000-character code cap. - **Dataset route.** A classifier runs before parsing and answers `422 code` or `422 formula`. The page offers `.send()` to the chat for code within 2,000 characters. - **Scope rubric:** - Data code is in scope. - A general-purpose script is out of scope. - Code that touches files, the network or the system is an attack. - off-053 (a sine wave "for my homework") is now `in_scope` under the formula path. - **UI.** The data panel shows the Python that made a version in a collapsed `# code that made this data` block. ## Deploy order The renderer should deploy first (`/run-data`), then the agents service. Either order is safe: against an old renderer, the tool answers `unavailable` and runs nothing. ## Measurements - **Scope eval on the real judge** (Claude Haiku 5.5, `eu`, the combined state after merging main): passed. 219 cases, adversarial recall 100 %, false refusals 0 %, exact 99.1 %, and 4 cases unavailable (the judge's answer failed its schema twice). - **Prep eval for tool choice on the real model:** the three cases described in words (Lorenz ODE, logistic map, random walk) chose `run_data_code`, and the four formula cases chose `generate_data`. The fake renderer's fixture table made the root re-run, so table sizes are checked only with a real renderer, after deploy. ## Plan `docs/concepts/agent-network.md` (data tools, renderer API, threat table) and `agents/README.md`. ## Test plan - [x] `pytest tests/unit tests/integration`: 10,464 passed. ruff, ruff format, mypy and the changelog check pass. - [x] Frontend: lint, Prettier, type-check, 1,020 Vitest tests and build pass. - [x] Harness data mode was run in subprocesses against the escapes from both Fable reviews; each one fails the run. pandas, numpy and scipy work under the guard. - [x] Scope eval and the routing prep cases, as above. - [ ] After deploy: the renderer's `/run-data` smoke, then the live test round (Swiss BFS data, a JS snippet, Lorenz in words, an attempt at a general-purpose script). - **Not verified:** the audit hook in plot mode (it is armed in data mode only, because there is no local Docker for a catalogue render sweep), and the data panel in a browser on both viewports and themes. The live test round covers the panel. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
gi_frame, thenf_builtins) reached the interpreter's builtins without a dunder name, which the validator allowed.gi_frame,gi_code,gi_yieldfrom,cr_*,ag_*,f_back,f_builtins,f_globals,f_locals,f_code,f_trace,tb_frame,tb_next,co_code,co_consts,co_names).openis a common data column (df["open"]for share prices). Once the frame attributes are banned, nothing reaches a builtins mapping, so the key check is unnecessary.run_data_code, branchfeat/run-data-code), since it reuses these lists.Plan
N/A
Test plan
df['open']).pytest tests/unit tests/integration: 10,066 passed. ruff passes.