Skip to content

fix(agents): refuse frame and generator introspection in plot code - #12169

Merged
MarkusNeusinger merged 4 commits into
mainfrom
fix/validator-frame-introspection
Oct 14, 2026
Merged

MarkusNeusinger merged 4 commits into
mainfrom
fix/validator-frame-introspection

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

Summary

  • A Fable security review on 2026-10-14 found a gap in the SECURITY profile. A generator's frame (gi_frame, then f_builtins) reached the interpreter's builtins without a dunder name, which the validator allowed.
  • The fix refuses these attributes on any object: frame, generator, coroutine, traceback and code-object attributes (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).
  • The exposure was bounded. The renderer's gVisor sandbox is read-only, has no egress and has no secrets in its environment.
  • No mapping-key check was added. open is 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.
  • The upcoming data profile picks this up (run_data_code, branch feat/run-data-code), since it reuses these lists.

Plan

N/A

Test plan

  • 9 new escape cases and one allowed case (df['open']). pytest tests/unit tests/integration: 10,066 passed. ruff passes.
  • Catalogue eligibility sweep is unchanged: 285 matplotlib and 287 seaborn files.

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
Copilot AI balanced review requested due to automatic review settings October 14, 2026 11:24
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Comment thread tests/unit/agents/code/test_validate.py Resolved
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

codecov Bot commented Oct 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Python 3.14’s f_generator frame-introspection attribute remains unblocked.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Comment thread agents/anyplot/code/validate.py Resolved
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
MarkusNeusinger merged commit d4f0024 into main Oct 14, 2026
15 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the fix/validator-frame-introspection branch October 14, 2026 12:04
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
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants