You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(agents): refuse native code and raw memory reached through a value - #12167
The SECURITY profile had a gap that a second opinion found on 2026-10-14. A NumPy value exposes .ctypes, so code can reach libc (np.zeros(1).ctypes._ctypes.CDLL(None).system(...)) or read and write any address (type(...).from_address(0)). The private-name rule fires only on imported names, so the call result passed.
The fix bans these names as attributes on any object:ctypes, _ctypes, cffi, data_as, from_address, from_buffer(_copy), as_strided, dump(s), extbuild, get_rdataset and webuse.
.to_string() and .info() are refused only with a target (buf). Without one they return text and stay allowed. sliding_window_view stays allowed too.
Why now: the planned run_data_code tool would hand user-written Python to this validator. Today the code still comes from the adapter model, and the gVisor sandbox (no egress) contains the damage, but the validator must not have the gap.
Plan
N/A
Test plan
13 new escape cases in ESCAPES. pytest tests/unit tests/integration: 10,024 passed. ruff passes.
Catalogue eligibility sweep (agents.evals.eligibility) is identical before and after: 285 matplotlib and 287 seaborn files eligible, same SECURITY rule counts.
Second opinion of 2026-10-14: np.zeros(1).ctypes._ctypes.CDLL(None) and
type(...).from_address(0) passed the SECURITY profile, since the
private-name rule applies only to imported names. Ban ctypes, _ctypes,
cffi, data_as, from_address, from_buffer(_copy), as_strided, dump(s),
extbuild, get_rdataset and webuse on any object, and to_string()/info()
with a target. Catalogue eligibility unchanged (285 / 287).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…econd argument
Copilot review of #12167: a method bound to a name hid its target from
the call check, and info() takes verbose first. The buffer writers may now
only be called where they are named, and a call is refused when its buf
(position 0 for to_string, 1 for info, or the keyword) is not None, or a
starred argument could carry it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Include from_buffer_copy and dumps in the exhaustive validator list
changelog.d/validator-native-escape-names.md:8
This exhaustive list omits two names added by this PR: from_buffer_copy and dumps. Include them so the release note accurately describes the new validator behavior.
Copilot review of #12167: df.agg("to_string", buf=...) reached the
writer by name; _check_dispatch_name now includes the buffer writers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…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
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
.ctypes, so code can reach libc (np.zeros(1).ctypes._ctypes.CDLL(None).system(...)) or read and write any address (type(...).from_address(0)). The private-name rule fires only on imported names, so the call result passed.ctypes,_ctypes,cffi,data_as,from_address,from_buffer(_copy),as_strided,dump(s),extbuild,get_rdatasetandwebuse..to_string()and.info()are refused only with a target (buf). Without one they return text and stay allowed.sliding_window_viewstays allowed too.run_data_codetool would hand user-written Python to this validator. Today the code still comes from the adapter model, and the gVisor sandbox (no egress) contains the damage, but the validator must not have the gap.Plan
N/A
Test plan
ESCAPES.pytest tests/unit tests/integration: 10,024 passed. ruff passes.agents.evals.eligibility) is identical before and after: 285 matplotlib and 287 seaborn files eligible, same SECURITY rule counts.